18using System.Collections.Generic;
19using System.Collections.ObjectModel;
20using System.Diagnostics;
25using System.Net.WebSockets;
26using System.Reflection;
27using System.Runtime.InteropServices;
29using System.Text.Json;
30using System.Text.RegularExpressions;
31using System.Threading;
32using System.Threading.Tasks;
33using System.Windows.Controls.Primitives;
34using System.Windows.Forms;
35using System.Windows.Threading;
40using OpenQA.Selenium.Interactions;
41using OpenQA.Selenium.Support.UI;
42using Org.BouncyCastle.Security;
46using Keys = OpenQA.Selenium.Keys;
51 internal class ElementHelper
53 public delegate ReadOnlyCollection<GPALElement> FindWebElementsByDelegate(Browser browser, SelectorPathEntry selectorPath, Selector selector, GPALElement webElement =
null,
bool isPersistent =
false);
56 static List<string> listOfMessages =
new List<string>();
57 static bool supressedMessage =
false;
58 static Selector lastSelector =
null;
61 const int VisibilitySettleChecks = 4;
62 const int VisibilitySettleMs = 100;
63 const int MaxScrollPages = 40;
66 public static ReadOnlyCollection<GPALElement> FindWebElements(
68 UnitOfWork currentUOW,
71 out List<GPALElement> matchedElements,
72 GPALElement webElement =
null,
73 bool callIfHandler =
true,
74 bool isPersistent =
false)
78 int withAllThatMatch =
int.MaxValue;
81 if (
null == selector.Browser)
82 selector.Browser = browser;
84 dynamic ourRetryElement = webElement;
85 ReadOnlyCollection<GPALElement> retElems =
new ReadOnlyCollection<GPALElement>(
new List<GPALElement>());
86 string message =
string.Empty;
88 currentUOW.CurrentSelector = selector;
90 if (
false == lastSelector?.Name.Equals(selector.Name))
92 listOfMessages.Clear();
93 supressedMessage =
false;
94 lastSelector = selector;
96 else if (
null == lastSelector)
97 lastSelector = selector;
99 if (browser.persistentUOW != currentUOW)
100 browser.CurrentUOW.CurrentSelector = selector;
103 matchedElements =
new List<GPALElement>();
105 List<FindWebElementsByDelegate> functionList =
new List<FindWebElementsByDelegate>
107 null, FindWebElementsByCss, FindWebElementsByImage, FindWebElementsByText,
108 FindWebElementsByValue, FindWebElementsByXPath, FindWebElementsByName,
109 null, FindWebElementsByClassName, FindWebElementsByPlaceholder, FindWebElementsByHRef
112 foreach (var selectorPathEntry
in selector.SelectorPaths)
115 int textLength = selectorPathEntry.SelectorPath.Length - 1;
116 string abbreviatedText = selectorPathEntry.SelectorPath.Substring(0, Math.Min(7, textLength)) +
"..." + selectorPathEntry.SelectorPath.Substring(Math.Max(0, textLength - 7));
117 if (18 > selectorPathEntry.SelectorPath.Length)
118 abbreviatedText = selectorPathEntry.SelectorPath;
120 int idx = GetIndexOfEnum<SelectorPathType>((
int)selectorPathEntry.SelectorPathType);
121 bool tryNextSelector =
false;
122 ReadOnlyCollection<GPALElement> allFoundElems =
new ReadOnlyCollection<GPALElement>(
new List<GPALElement>());
123 string errorMessage = $
"Error finding elements for selector [{selector.Name}][{abbreviatedText}]";
124 int selectorPathIdx = 0;
128 message = $
"Searching for elements for selector [{selector.Name}] using [{selectorPathEntry.SelectorPathType}] [{abbreviatedText}]";
130 if (
false == listOfMessages.Contains(message))
132 listOfMessages.Add(message);
133 GPAL.PublishSimpleEvent(GPALEventType.INFO, message, browser, GPALObjectType.Browser);
136 var tmpAllFoundElems = FindElements(browser, currentUOW, selectorPathEntry, selector, webElement, idx, functionList, isPersistent);
142 retElems = allFoundElems = tmpAllFoundElems ??
new ReadOnlyCollection<GPALElement>(
new List<GPALElement>());
143 matchedElements = allFoundElems.ToList();
145 if (0 < retElems?.Count)
148 if (
true == browser.UseSelenium)
149 foreach (GPALElement gPALElement
in allFoundElems)
150 gPALElement.Css = GenerateCssSelector(browser, gPALElement.WebElement);
152 var matchingResult = ApplyCustomMatchCriteria(selector, allFoundElems, browser, matchedElements, out tryNextSelector);
153 matchedAll = matchingResult;
157 retElems =
new ReadOnlyCollection<GPALElement>(
new List<GPALElement>());
158 matchedElements.Clear();
162 List<GPALElement> simpleMatches = matchedElements;
164 if (selector.MatchCriteria.Count > 0)
165 matchedAll = ApplySimpleMatching(selector, matchedElements, matchedElements, out simpleMatches);
169 matchedElements = simpleMatches;
171 selectorPathEntry.WebSelectorFoundResults = allFoundElems?.ToList();
172 selectorPathEntry.WebSelectorMatchedResults = simpleMatches;
177 if (
int.MaxValue != currentUOW.WithAllThatMatch && 0 < currentUOW.WithAllThatMatch)
178 withAllThatMatch = currentUOW.WithAllThatMatch;
179 else if (
int.MaxValue != browser.BrowserSettings.WithAllThatMatch && 0 < browser.BrowserSettings.WithAllThatMatch)
180 withAllThatMatch = browser.BrowserSettings.WithAllThatMatch;
181 else if (0 < GPAL.GPALSettings.WithAllThatMatch)
182 withAllThatMatch = GPAL.GPALSettings.WithAllThatMatch;
184 if (
int.MaxValue != withAllThatMatch && 0 < simpleMatches?.Count)
186 List<GPALElement> tmpList =
new List<GPALElement>();
189 tmpList.AddRange(simpleMatches.Take(Math.Min(withAllThatMatch, matchedElements?.Count ?? 0)));
191 matchedElements = tmpList;
194 foreach (GPALElement element
in retElems)
196 element.Browser = browser;
197 element.Selector = selector;
201 catch (StaleElementReferenceException sereex)
203 message = errorMessage + $
" StaleElementReferenceException: [{sereex.Message}]";
204 retry = ResolveWebElementIssue(browser, currentUOW, ref ourRetryElement, sereex, ref retries, message);
206 catch (ElementClickInterceptedException ecieex)
208 message = errorMessage + $
" ElementClickInterceptedException: [{ecieex.Message}]";
209 retry = ResolveWebElementIssue(browser, currentUOW, ref ourRetryElement, ecieex, ref retries, message);
211 catch (ElementNotInteractableException enieex)
213 message = errorMessage + $
" ElementNotInteractableException: [{enieex.Message}]";
214 retry = ResolveWebElementIssue(browser, currentUOW, ref ourRetryElement, enieex, ref retries, message);
216 catch (GPALException)
222 GPAL.PublishSimpleEvent(GPALEventType.EXCEPTION, errorMessage, browser, GPALObjectType.Browser, ex);
226 if (
true == retry && 0 < retries--)
232 selector.ElementsFoundAndMatchedCount = (int)(
null != matchedElements ? matchedElements?.Count() : 0);
234 message = $
"[{retElems?.Count ?? 0}] elements found for selector [{selector.Name}]";
236 if (
false == listOfMessages.Contains(message))
238 listOfMessages.Add(message);
239 GPAL.PublishSimpleEvent(GPALEventType.INFO, message, browser, GPALObjectType.Browser);
241 if (0 < retElems?.Count && (
true == selector.AnyMatchCriteria() || Int32.MaxValue != withAllThatMatch))
243 var matchCount = selector.MatchCriteria.Count > 0 ? selector.MatchCriteria.Count.ToString() : $
"WithAllThatMatch({withAllThatMatch})";
245 if ((
true == matchCount.Contains(
"WithAllThatMatch") && 0 < withAllThatMatch) || 0 < selector.MatchCriteria.Count)
246 GPAL.PublishSimpleEvent(GPALEventType.INFO, $
"[{matchedElements?.Count}] elements matched for selector [{selector.Name}] with [{matchCount}] match criteria", browser, GPALObjectType.Browser);
249 else if (
false == supressedMessage)
251 GPAL.PublishSimpleEvent(GPALEventType.INFO,
"Supressing repeat messages", browser, GPALObjectType.Browser);
252 supressedMessage =
true;
255 if (matchedAll || retElems?.Count > 0)
257 else if (++selectorPathIdx == selector.SelectorPaths.Count)
258 GPAL.PublishSimpleEvent(GPALEventType.DEBUG, $
"No more selector paths. No elements found.", browser, GPALObjectType.Browser);
262 if (
true == callIfHandler)
263 CallIfHandlers(browser, currentUOW, retElems, matchedElements, selector, matchedAll);
283 public static ReadOnlyCollection<GPALElement> FindElements(
285 UnitOfWork currentUOW,
286 SelectorSettings.SelectorPathEntry selectorPathEntry,
288 GPALElement gPalElement,
290 List<FindWebElementsByDelegate> functionList,
293 if (0 < browser.CurrentUOW.ContextPath?.Count &&
null == gPalElement)
296 if (
true == browser.UseOttoMagic)
297 browser.MagicHelper.SwitchToDefaultContent();
298 else if (
true == browser.UsePuppeteer)
299 browser.PuppeteerClient.SwitchToDefaultContent().Execute();
300 else if (
true == browser.UseSelenium)
301 browser.BrowserDriver.SwitchTo().DefaultContent();
303 IEnumerable<UnitOfWork.WebElementWithType> stepsToProcess;
306 stepsToProcess = browser.CurrentUOW.ContextPath;
309 foreach (UnitOfWork.WebElementWithType contextType in stepsToProcess)
311 if (ElementType.IFrame == contextType.elementType)
313 if (
true == browser.UseOttoMagic)
314 browser.MagicHelper.SwitchToElement(contextType.gPalElement.Css);
315 else if (
true == browser.UsePuppeteer)
316 browser.PuppeteerClient.SwitchToFrame(contextType.gPalElement.Css).Execute();
318 browser.BrowserDriver.SwitchTo().Frame((IWebElement)contextType.gPalElement.WebElement);
325 else if (ElementType.ShadowRoot == contextType.elementType)
327 if (
true == browser.UseOttoMagic)
328 browser.MagicHelper.SwitchToShadowRoot(contextType.gPalElement.Css);
329 else if (
true == browser.UsePuppeteer)
330 browser.PuppeteerClient.SwitchToShadowRoot(contextType.gPalElement.Css).Execute();
336 gPalElement = contextType.gPalElement;
338 else if (ElementType.Element == contextType.elementType)
341 gPalElement = contextType.CurrentElement;
347 while (retries-- > 0)
351 return functionList[idx](browser, selectorPathEntry, selector, gPalElement, isPersistent);
353 catch (GPALException)
359 GPAL.PublishSimpleEvent(GPALEventType.EXCEPTION, $
"Error finding elements for selector [{selector.Name}]. Trying next selector path if defined.", browser, GPALObjectType.Browser, ex);
364 return new ReadOnlyCollection<GPALElement>(
new List<GPALElement>());
380 private static bool ApplyCustomMatchCriteria(
382 ReadOnlyCollection<GPALElement> allFoundElems,
384 List<GPALElement> matchedElements,
385 out
bool tryNextSelector,
388 tryNextSelector =
false;
390 var universe = allFoundElems?.ToList();
393 var workingSet = matchedElements.Count > 0
394 ?
new List<GPALElement>(matchedElements)
395 : new List<GPALElement>(universe);
397 bool matchedAll =
true;
399 foreach (var matchCriteriaEntry
in selector.MatchCriteria)
401 if (matchCriteriaEntry.MatchType != MatchType.Custom)
405 var source = AndOrOr ? workingSet : universe;
407 GPAL.PublishSimpleEvent(GPALEventType.DEBUG,
408 $
"Applying custom match [{matchCriteriaEntry.WebMatchingFunction.GetInvocationList()[0].Method.Name}] " +
409 $
"to {(AndOrOr ? "working set
" : "full universe
")} ({source.Count} elements)",
411 GPALObjectType.Browser);
413 CallIfStatus handled = matchCriteriaEntry.WebMatchingFunction(
414 source.Cast<IGPALElement>().ToList(),
415 out List<IGPALElement> tempMatchedElemsRaw,
418 matchCriteriaEntry.ExactMatch);
419 List<GPALElement> tempMatchedElems = tempMatchedElemsRaw?.Cast<GPALElement>().ToList();
422 var matchedThisRound = new List<GPALElement>();
423 var roundSet = new HashSet<GPALElement>();
425 foreach (var elem in tempMatchedElems ?? new List<GPALElement>())
427 if (roundSet.Add(elem))
429 matchedThisRound.Add(elem);
431 GPAL.PublishSimpleEvent(GPALEventType.DEBUG,
432 $
"Element matched custom criteria [{matchCriteriaEntry.WebMatchingFunction.GetInvocationList()[0].Method.Name}] " +
433 $
"TagName: [{elem.TagName}], Text: [{elem.Text}]",
435 GPALObjectType.Browser);
439 if (CallIfStatus.Terminate == handled)
441 GPAL.PublishSimpleEvent(
442 GPALEventType.EXCEPTION,
443 $
"Custom match handler [{matchCriteriaEntry.WebMatchingFunction.GetInvocationList()[0].Method.Name}] requested program termination for selector [{selector.Name}].",
445 GPALObjectType.Browser);
447 throw new GPALException(
448 $
"Custom match handler [{matchCriteriaEntry.WebMatchingFunction.GetInvocationList()[0].Method.Name}] requested program termination for selector [{selector.Name}].");
454 workingSet = matchedThisRound;
459 var unionSet =
new HashSet<GPALElement>(workingSet);
460 foreach (var elem
in matchedThisRound)
462 if (unionSet.Add(elem))
464 workingSet.Add(elem);
466 GPAL.PublishSimpleEvent(GPALEventType.DEBUG,
467 $
"Element added to OR working set from custom match: TagName: [{elem.TagName}], Text: [{elem.Text}]",
469 GPALObjectType.Browser);
474 if (CallIfStatus.Handled == handled)
477 if (CallIfStatus.TryNext == handled)
479 tryNextSelector = true;
484 matchedElements.Clear();
485 matchedElements.AddRange(workingSet);
501 private static bool ApplySimpleMatching(
503 List<GPALElement> matchedElements,
504 List<GPALElement> allFoundElems,
505 out List<GPALElement> matchedElementsOut,
508 matchedElementsOut =
new List<GPALElement>();
509 var universe = allFoundElems?.ToList();
512 var workingSet = matchedElements.Count > 0
513 ?
new List<GPALElement>(matchedElements)
514 : new List<GPALElement>(universe);
516 foreach (var matchCriteriaEntry
in selector.MatchCriteria)
518 if (matchCriteriaEntry.MatchType == MatchType.None || matchCriteriaEntry.MatchType == MatchType.Custom)
522 var source = AndOrOr ? workingSet : universe;
523 var matchedThisRound = new List<GPALElement>();
524 var roundSet = new HashSet<GPALElement>();
526 foreach (var element in source)
528 string elementStringToMatch = null;
530 if (
"GPALElement" != element.TagName)
532 switch ((MatchType)((int)matchCriteriaEntry.MatchType & 0xFE))
535 elementStringToMatch = element.Text;
536 if (string.IsNullOrEmpty(elementStringToMatch))
537 elementStringToMatch = element.GetAttribute(
"title");
538 if (string.IsNullOrEmpty(elementStringToMatch))
539 elementStringToMatch = element.GetAttribute(
"value");
542 elementStringToMatch = element.GetAttribute(
"href");
545 elementStringToMatch = element.GetAttribute(
"src");
547 case MatchType.Placeholder:
548 elementStringToMatch = element.GetAttribute(
"placeholder");
550 case MatchType.Value:
551 elementStringToMatch = element.GetAttribute(
"value");
553 case MatchType.Attribute:
554 elementStringToMatch = element.GetAttribute(selector.AttributeName);
558 if (!string.IsNullOrEmpty(elementStringToMatch))
560 GPAL.PublishSimpleEvent(GPALEventType.DEBUG,
561 $
"String to match [{matchCriteriaEntry.StringToMatch}] element text [{elementStringToMatch}]");
563 bool isMatch = false;
565 if ((int)(matchCriteriaEntry.MatchType & MatchType.Regex) == 1)
567 var mc = Regex.Matches(elementStringToMatch, matchCriteriaEntry.StringToMatch);
573 if (false == matchCriteriaEntry.ExactMatch && elementStringToMatch.Contains(matchCriteriaEntry.StringToMatch))
575 else if (true == matchCriteriaEntry.ExactMatch && elementStringToMatch.Equals(matchCriteriaEntry.StringToMatch))
579 if (isMatch && roundSet.Add(element))
581 GPAL.PublishSimpleEvent(GPALEventType.DEBUG, $
"Adding [{element.TagName}][{element.Text}]");
582 matchedThisRound.Add(element);
588 if (roundSet.Add(element))
589 matchedThisRound.Add(element);
596 workingSet = matchedThisRound;
601 var unionSet =
new HashSet<GPALElement>(workingSet);
602 foreach (var elem
in matchedThisRound)
604 if (unionSet.Add(elem))
606 workingSet.Add(elem);
612 matchedElementsOut = workingSet;
613 return universe.Count == matchedElementsOut.Count;
631 private static void CallIfHandlers(Browser browser, UnitOfWork currentUOW, ReadOnlyCollection<GPALElement> foundElements, List<GPALElement> matchedElements, Selector selector,
bool matchedAll)
634 string currentMethodName =
null;
635 int selectorsFoundOrMatched = selector.AnyMatchCriteria() ? matchedElements?.Count ?? 0 : foundElements?.Count ?? 0;
644 if (0 < selectorsFoundOrMatched)
646 if (0 != (selector.SelectorSettings.CallIfFound?.Count ?? 0))
648 foreach (Browser.CallIfDelegate func in selector.SelectorSettings.CallIfFound)
650 if (
true == selector.RemovedMethods.Contains(func))
653 bool safeActionCalled = browser.CurrentUOW.ActionCalled;
654 safeUOW = browser.CurrentUOW;
655 browser.CurrentUOW.ActionCalled =
true;
657 currentMethodName = func.Method.Name;
658 str = $
"Browser Selector CallIfFound handler [{currentMethodName}] requested program termination on selector [{selector.Name}] [{selector.SelectorPaths[0].SelectorPath}]";
660 GPAL.PublishSimpleEvent(GPALEventType.INFO, $
"Invoking Selector CallIfFound [{func.Method.Name}]", browser, GPALObjectType.Browser);
662 selector.CallIfFoundHandled = func(browser, foundElements?.Cast<IGPALElement>().ToList(), matchedElements?.Cast<IGPALElement>().ToList(), selector, matchedAll);
664 browser.CurrentUOW = safeUOW;
665 browser.CurrentUOW.ActionCalled = safeActionCalled;
667 if (CallIfStatus.NotHandled == selector.CallIfFoundHandled)
673 if (CallIfStatus.Terminate == selector.CallIfFoundHandled)
675 GPAL.PublishSimpleEvent(GPALEventType.INFO, str, browser, GPALObjectType.Browser);
676 throw new GPALException(str);
681 if (CallIfStatus.NotHandled == selector.CallIfFoundHandled && 0 < (currentUOW.CallIfFound?.Count ?? 0))
683 foreach (Browser.CallIfDelegate func in currentUOW.CallIfFound)
685 if (
true == selector.RemovedMethods.Contains(func))
688 safeUOW = browser.CurrentUOW;
690 currentMethodName = func.Method.Name;
691 str = $
"Browser UOW Selector CallIfFound handler [{currentMethodName}] requested program termination on selector [{selector.Name}] [{selector.SelectorPaths[0].SelectorPath}]";
693 GPAL.PublishSimpleEvent(GPALEventType.INFO, $
"Invoking UOW CallIfFound [{func.Method.Name}]", browser, GPALObjectType.Browser);
695 currentUOW.CallIfFoundHandled = func(browser, foundElements?.Cast<IGPALElement>().ToList(), matchedElements?.Cast<IGPALElement>().ToList(), selector, matchedAll);
697 browser.CurrentUOW = safeUOW;
699 if (CallIfStatus.NotHandled == currentUOW.CallIfFoundHandled)
705 if (CallIfStatus.Terminate == currentUOW.CallIfFoundHandled)
707 GPAL.PublishSimpleEvent(GPALEventType.INFO, str, browser, GPALObjectType.Browser);
708 throw new GPALException(str);
713 if (CallIfStatus.NotHandled == selector.CallIfFoundHandled && 0 == currentUOW.CallIfFoundHandled && 0 != (GPAL.GPALSettings.CallIfFoundList?.Count ?? 0))
715 foreach (Browser.CallIfDelegate func in GPAL.GPALSettings.CallIfFoundList)
717 if (
true == selector.RemovedMethods.Contains(func))
720 safeUOW = browser.CurrentUOW;
722 currentMethodName = func.Method.Name;
723 str = $
"Gloabl CallIfFound handler [{currentMethodName}] requested program termination on selector [{selector.Name}] [{selector.SelectorPaths[0].SelectorPath}]";
725 GPAL.PublishSimpleEvent(GPALEventType.INFO, $
"Invoking Global CallIfFound [{func.Method.Name}]", browser, GPALObjectType.Browser);
727 GPAL.GPALSettings.CallIfFoundHandled = func(browser, foundElements?.Cast<IGPALElement>().ToList(), matchedElements?.Cast<IGPALElement>().ToList(), selector, matchedAll);
729 browser.CurrentUOW = safeUOW;
731 if (CallIfStatus.NotHandled == GPAL.GPALSettings.CallIfFoundHandled)
737 if (CallIfStatus.Terminate == (GPAL.GPALSettings.CallIfFoundHandled))
739 GPAL.PublishSimpleEvent(GPALEventType.INFO, str, browser, GPALObjectType.Browser);
740 throw new GPALException(str);
746 if (0 < selector.SelectorSettings.CallIfNotFound.Count)
748 foreach (Browser.CallIfDelegate func in selector.SelectorSettings.CallIfNotFound)
750 if (
true == selector.RemovedMethods.Contains(func))
753 safeUOW = browser.CurrentUOW;
755 currentMethodName = func.Method.Name;
756 str = $
"Browser Selector CallIfNotFound handler [{currentMethodName}] requested program termination on selector [{selector.Name}] [{selector.SelectorPaths[0].SelectorPath}[";
758 GPAL.PublishSimpleEvent(GPALEventType.INFO, $
"Invoking Selector CallIfNotFound [{func.Method.Name}]", browser, GPALObjectType.Browser);
760 selector.CallIfNotFoundHandled = func(browser, foundElements?.Cast<IGPALElement>().ToList(), matchedElements?.Cast<IGPALElement>().ToList(), selector, matchedAll);
762 browser.CurrentUOW = safeUOW;
764 if (CallIfStatus.NotHandled == selector.CallIfNotFoundHandled)
770 if (CallIfStatus.Terminate == selector.CallIfNotFoundHandled)
772 GPAL.PublishSimpleEvent(GPALEventType.INFO, str, browser, GPALObjectType.Browser);
773 throw new GPALException(str);
777 if (0 == selector.CallIfNotFoundHandled && 0 < currentUOW.CallIfNotFound.Count)
779 foreach (Browser.CallIfDelegate func in currentUOW.CallIfNotFound)
781 if (
true == selector.RemovedMethods.Contains(func))
784 safeUOW = browser.CurrentUOW;
786 currentMethodName = func.Method.Name;
787 str = $
"Browser UOW Selector CallIfNotFound handler [{currentMethodName}] requested program termination on selector [{selector.Name}] [{selector.SelectorPaths[0].SelectorPath}]";
789 GPAL.PublishSimpleEvent(GPALEventType.INFO, $
"Invoking UOW CallIfNotFound [{func.Method.Name}]", browser, GPALObjectType.Browser);
791 currentUOW.CallIfNotFoundHandled = func(browser, foundElements?.Cast<IGPALElement>().ToList(), matchedElements?.Cast<IGPALElement>().ToList(), selector, matchedAll);
793 browser.CurrentUOW = safeUOW;
795 if (CallIfStatus.NotHandled == currentUOW.CallIfNotFoundHandled)
801 if (CallIfStatus.Terminate == currentUOW.CallIfNotFoundHandled)
803 GPAL.PublishSimpleEvent(GPALEventType.INFO, str, browser, GPALObjectType.Browser);
804 throw new GPALException(str);
808 if (0 == selector.CallIfNotFoundHandled && 0 == currentUOW.CallIfNotFoundHandled && 0 < GPAL.GPALSettings.CallIfNotFoundList.Count)
810 foreach (Browser.CallIfDelegate func in GPAL.GPALSettings.CallIfNotFoundList)
812 if (
true == selector.RemovedMethods.Contains(func))
815 safeUOW = browser.CurrentUOW;
817 currentMethodName = func.Method.Name;
818 str = $
"Gloabl CallIfNotFound handler [{currentMethodName}] requested program termination on selector [{selector.Name}] [{selector.SelectorPaths[0].SelectorPath}]";
820 GPAL.PublishSimpleEvent(GPALEventType.INFO, $
"Invoking Global CallIfNotFound [{func.Method.Name}]", browser, GPALObjectType.Browser);
822 GPAL.GPALSettings.CallIfNotFoundHandled = func(browser, foundElements?.Cast<IGPALElement>().ToList(), matchedElements?.Cast<IGPALElement>().ToList(), selector, matchedAll);
824 browser.CurrentUOW = safeUOW;
826 if (CallIfStatus.NotHandled == GPAL.GPALSettings.CallIfNotFoundHandled)
832 if (CallIfStatus.Terminate == GPAL.GPALSettings.CallIfNotFoundHandled)
834 GPAL.PublishSimpleEvent(GPALEventType.INFO, str, browser, GPALObjectType.Browser);
835 throw new GPALException(str);
839 if (
true == selector.StopOnNotFound ||
true == GPAL.StopOnNotFound)
841 str =
$@"{(true == selector.StopOnNotFound ? "selector.
" : "GPAL.
")}StopOnNotFound set, terminating on Not Found element for [{selector.Name}][{selector.SelectorPaths[0].SelectorPath}]";
842 GPAL.PublishSimpleEvent(GPALEventType.INFO, str, browser, GPALObjectType.Browser);
843 throw new GPALException(str);
857 private static ReadOnlyCollection<GPALElement> FindWebElementsByCss(Browser browser, SelectorPathEntry selectorPath, Selector selector, GPALElement webElement =
null,
bool isPersistent =
false)
859 bool searchForElement = selector.SearchForSelector;
860 ReadOnlyCollection<IWebElement> elems;
861 ReadOnlyCollection<GPALElement> GPALelems =
null;
863 if (browser.UseOttoMagic)
865 List<GPALElement> result =
null;
867 if (
false == webElement?.IsShadowRoot)
868 browser.MagicHelper.SwitchToElement(webElement.Css);
870 if (
true == isPersistent)
871 result = browser.MagicHelper
872 .QueryPersistentSelectors(selectorPath.SelectorPath);
874 result = browser.MagicHelper
875 .QuerySelectors(selectorPath.SelectorPath);
878 GPALelems =
new ReadOnlyCollection<GPALElement>(result);
880 else if (
true == browser.UsePuppeteer)
882 if (
false == webElement?.IsShadowRoot)
883 browser.PuppeteerClient.InElement(webElement.Css).Execute();
885 var result2 = Task.Run(() => browser.PuppeteerCommunicator.EvaluateSelector(selectorPath.SelectorPath));
887 GPALelems =
new ReadOnlyCollection<GPALElement>(result2.Result);
891 string css = selectorPath.SelectorPath;
893 elems = webElement?.WebElement !=
null
894 ? webElement.WebElement.FindElements(By.CssSelector(css))
898 : browser.BrowserDriver.FindElements(By.CssSelector(css));
905 if (0 == elems.Count &&
true == webElement?.IsClosedShadowRoot &&
false == isPersistent)
906 return new ReadOnlyCollection<GPALElement>(
new List<GPALElement>() { webElement });
908 GPALelems = ToGPALElements(browser, selector, elems);
924 private static ReadOnlyCollection<GPALElement> FindWebElementsByImage(Browser browser, SelectorPathEntry selectorPath, Selector selector, GPALElement webElement =
null,
bool isPersistent =
false)
926 bool searchForElement = selector.SearchForSelector;
931 bool inPage =
true == browser.BrowserSettings.UseHeadless ||
true == browser.BrowserSettings.HiddenDesktop
932 ||
false == UsesScreenCoordinates(browser, selector.InteractionType);
934 ReadOnlyCollection<GPALElement> elems =
null;
936 Rectangle foundImageBounds =
new Rectangle();
937 List<GPALElement> retWebElement =
new List<GPALElement>(); ;
939 if (
null == selectorPath.Image)
943 selectorPath.Image = Image.FromFile(selectorPath.SelectorPath);
945 catch (GPALException)
951 GPAL.PublishSimpleEvent(GPALEventType.EXCEPTION, $
"Unable to load image for [{selector.Name}].", browser, GPALObjectType.Browser, ex);
958 browser.BrowserSettings.ImageSearchInPage = inPage;
960 foundImageBounds = ((ImageHelper)GPAL.ImageHelper).FindImage(selectorPath.SelectorPath, browser);
967 if (
true == searchForElement &&
null != GPAL.ImageMatcher)
970 if (
true == foundImageBounds.IsEmpty)
973 while (
true == foundImageBounds.IsEmpty &&
false == IsEndOfPage(browser, webElement))
975 _ = browser.PageDown;
977 foundImageBounds = ((ImageHelper)GPAL.ImageHelper).FindImage(selectorPath.SelectorPath, browser);
981 browser.BrowserSettings.ImageSearchInPage =
false;
983 if (
false == foundImageBounds.IsEmpty)
989 retWebElement.Add(
new GPALElement(foundImageBounds.Location, foundImageBounds.Size, selectorPath.SelectorPathType.ToString(), browser.BrowserDriver,
"GPALElement")
993 CoordinateSpace = true == inPage ? CoordinateSpace.Viewport : CoordinateSpace.Screen
995 elems =
new ReadOnlyCollection<GPALElement>(retWebElement);
1004 ReadOnlyCollection<GPALElement> underMatch = ElementUnderImage(browser, retWebElement[0], selector);
1006 if (0 < underMatch?.Count)
1024 private static ReadOnlyCollection<GPALElement> FindWebElementsByXPath(Browser browser, SelectorPathEntry selectorPath, Selector selector, GPALElement webElement =
null,
bool isPersistent =
false)
1026 bool searchForElement = selector.SearchForSelector;
1028 ReadOnlyCollection<IWebElement> elems;
1029 ReadOnlyCollection<GPALElement> GPALelems =
null;
1031 if (browser.UseOttoMagic)
1033 List<GPALElement> result =
null;
1035 if (
false == webElement?.IsShadowRoot)
1036 browser.MagicHelper.SwitchToElement(webElement.Css);
1038 if (
true == isPersistent)
1039 result = browser.MagicHelper
1040 .EvaluateAllPersistent(selectorPath.SelectorPath);
1042 result = browser.MagicHelper
1043 .EvaluateAll(selectorPath.SelectorPath);
1046 GPALelems =
new ReadOnlyCollection<GPALElement>(result);
1048 else if (
true == browser.UsePuppeteer)
1050 if (
false == webElement?.IsShadowRoot)
1051 browser.PuppeteerClient.InElement(webElement.Css).Execute();
1053 var result2 = Task.Run(() => browser.PuppeteerCommunicator.EvaluateSelector(selectorPath.SelectorPath));
1055 if (
null != result2)
1056 GPALelems =
new ReadOnlyCollection<GPALElement>(result2.Result);
1060 string xpath = selectorPath.SelectorPath;
1062 elems = webElement?.WebElement !=
null
1063 ? webElement.WebElement.FindElements(By.XPath(xpath))
1064 : browser.BrowserDriver.FindElements(By.XPath(xpath));
1068 if (0 == elems.Count &&
true == webElement?.IsClosedShadowRoot &&
false == isPersistent)
1069 return new ReadOnlyCollection<GPALElement>(
new List<GPALElement>() { webElement });
1071 GPALelems = ToGPALElements(browser, selector, elems);
1092 internal static ReadOnlyCollection<GPALElement> ToGPALElements(Browser browser, Selector selector, ReadOnlyCollection<IWebElement> elems)
1094 List<GPALElement> gPALElements =
new List<GPALElement>();
1096 if (0 < elems.Count)
1099 return arguments[0].map(function (element) {
1100 var rect = element.getBoundingClientRect();
1101 var style = window.getComputedStyle(element);
1104 tag: element.tagName ? element.tagName.toLowerCase() : null,
1105 text: element.innerText,
1106 enabled: false === element.disabled || undefined === element.disabled,
1107 selected: true === element.checked || true === element.selected,
1108 indeterminate: true === element.indeterminate,
1109 displayed: (0 < element.getClientRects().length) && 'hidden' !== style.visibility && 'none' !== style.display,
1110 x: Math.round(rect.left + window.pageXOffset),
1111 y: Math.round(rect.top + window.pageYOffset),
1112 w: Math.round(rect.width),
1113 h: Math.round(rect.height),
1114 href: element.getAttribute('href'),
1115 src: element.getAttribute('src'),
1116 value: undefined !== element.value ? element.value : element.getAttribute('value'),
1117 placeholder: element.getAttribute('placeholder'),
1118 type: element.getAttribute('type')
1123 string pageUrl = browser.BrowserDriver.Url;
1124 object answered = ((IJavaScriptExecutor)browser.BrowserDriver).ExecuteScript(script, elems.ToList());
1125 List<object> properties = (answered as IEnumerable<object>)?.ToList();
1127 for (
int idx = 0; idx < elems.Count; idx++)
1131 GPALElement gPALElement = properties?.Count == elems.Count && properties[idx] is Dictionary<string, object> bag
1132 ?
new GPALElement(elems[idx], selector.AttributeName, bag, pageUrl)
1133 : new GPALElement(elems[idx], selector.AttributeName);
1135 gPALElement.Browser = browser;
1136 gPALElement.Selector = selector;
1138 gPALElements.Add(gPALElement);
1142 return new ReadOnlyCollection<GPALElement>(gPALElements);
1155 private static ReadOnlyCollection<GPALElement> FindWebElementsByText(Browser browser, SelectorPathEntry selectorPath, Selector selector, GPALElement webElement =
null,
bool isPersistent =
false)
1157 bool searchForElement = selector.SearchForSelector;
1158 ReadOnlyCollection<IWebElement> elems;
1159 ReadOnlyCollection<GPALElement> GPALelems =
null;
1160 string xpathSelector = $
"//*[contains(text(), '{selectorPath.SelectorPath}')]";
1162 if (browser.UseOttoMagic)
1164 List<GPALElement> result =
null;
1166 if (
false == webElement?.IsShadowRoot)
1167 browser.MagicHelper.SwitchToElement(webElement.Css);
1169 if (
true == isPersistent)
1170 result = browser.MagicHelper
1171 .EvaluateAllPersistent(xpathSelector);
1173 result = browser.MagicHelper
1174 .EvaluateAll(xpathSelector);
1177 GPALelems =
new ReadOnlyCollection<GPALElement>(result);
1179 else if (
true == browser.UsePuppeteer)
1181 if (
false == webElement?.IsShadowRoot)
1182 browser.PuppeteerClient.InElement(webElement.Css).Execute();
1184 var result2 = Task.Run(() => browser.PuppeteerCommunicator.EvaluateSelector(xpathSelector));
1186 if (
null != result2)
1187 GPALelems =
new ReadOnlyCollection<GPALElement>(result2.Result);
1191 if (webElement !=
null)
1192 xpathSelector = $
".{xpathSelector}";
1194 elems = webElement?.WebElement !=
null
1195 ? webElement.WebElement.FindElements(By.XPath(xpathSelector))
1196 : browser.BrowserDriver.FindElements(By.XPath(xpathSelector));
1198 GPALelems = ToGPALElements(browser, selector, elems);
1214 private static ReadOnlyCollection<GPALElement> FindWebElementsByHRef(Browser browser, SelectorPathEntry selectorPath, Selector selector, GPALElement webElement =
null,
bool isPersistent =
false)
1216 bool searchForElement = selector.SearchForSelector;
1217 ReadOnlyCollection<IWebElement> elems;
1218 ReadOnlyCollection<GPALElement> GPALelems =
null;
1219 string cssSelector = $
"a[href*='{selectorPath.SelectorPath}']";
1221 if (browser.UseOttoMagic)
1223 List<GPALElement> result =
null;
1225 if (
false == webElement?.IsShadowRoot)
1226 browser.MagicHelper.SwitchToElement(webElement.Css);
1228 if (
true == isPersistent)
1229 result = browser.MagicHelper
1230 .QueryPersistentSelectors(cssSelector);
1232 result = browser.MagicHelper
1233 .QuerySelectors(cssSelector);
1236 GPALelems =
new ReadOnlyCollection<GPALElement>(result);
1238 else if (
true == browser.UsePuppeteer)
1240 if (
false == webElement?.IsShadowRoot)
1241 browser.PuppeteerClient.InElement(webElement.Css).Execute();
1243 var result2 = Task.Run(() => browser.PuppeteerCommunicator.EvaluateSelector(cssSelector));
1245 if (
null != result2)
1246 GPALelems =
new ReadOnlyCollection<GPALElement>(result2.Result);
1250 elems = webElement?.WebElement !=
null
1251 ? webElement.WebElement.FindElements(By.PartialLinkText(selectorPath.SelectorPath))
1252 : browser.BrowserDriver.FindElements(By.PartialLinkText(selectorPath.SelectorPath));
1254 GPALelems =
new ReadOnlyCollection<GPALElement>(
1255 elems.Select(e =>
new GPALElement(e, selector.AttributeName) { Browser = browser, Selector = selector }).ToList()
1271 private static ReadOnlyCollection<GPALElement> FindWebElementsByClassName(Browser browser, SelectorPathEntry selectorPath, Selector selector, GPALElement webElement =
null,
bool isPersistent =
false)
1273 bool searchForElement = selector.SearchForSelector;
1274 ReadOnlyCollection<IWebElement> elems;
1275 ReadOnlyCollection<GPALElement> GPALelems =
null;
1276 string cssSelector = $
".{selectorPath.SelectorPath}";
1278 if (browser.UseOttoMagic)
1280 List<GPALElement> result =
null;
1282 if (
false == webElement?.IsShadowRoot)
1283 browser.MagicHelper.SwitchToElement(webElement.Css);
1285 if (
true == isPersistent)
1286 result = browser.MagicHelper
1287 .QueryPersistentSelectors(cssSelector);
1289 result = browser.MagicHelper
1290 .QuerySelectors(cssSelector);
1293 GPALelems =
new ReadOnlyCollection<GPALElement>(result);
1295 else if (
true == browser.UsePuppeteer)
1297 if (
false == webElement?.IsShadowRoot)
1298 browser.PuppeteerClient.InElement(webElement.Css).Execute();
1300 var result2 = Task.Run(() => browser.PuppeteerCommunicator.EvaluateSelector(cssSelector));
1301 if (
null != result2)
1302 GPALelems =
new ReadOnlyCollection<GPALElement>(result2.Result);
1306 elems = webElement?.WebElement !=
null
1307 ? webElement.WebElement.FindElements(By.ClassName(selectorPath.SelectorPath))
1308 : browser.BrowserDriver.FindElements(By.ClassName(selectorPath.SelectorPath));
1310 GPALelems =
new ReadOnlyCollection<GPALElement>(
1311 elems.Select(e =>
new GPALElement(e, selector.AttributeName) { Browser = browser, Selector = selector }).ToList()
1327 private static ReadOnlyCollection<GPALElement> FindWebElementsByName(Browser browser, SelectorPathEntry selectorPath, Selector selector, GPALElement webElement =
null,
bool isPersistent =
false)
1329 bool searchForElement = selector.SearchForSelector;
1330 ReadOnlyCollection<IWebElement> elems;
1331 ReadOnlyCollection<GPALElement> GPALelems =
null;
1332 string cssSelector = $
"[name='{selectorPath.SelectorPath}']";
1334 if (browser.UseOttoMagic)
1336 List<GPALElement> result =
null;
1338 if (
false == webElement?.IsShadowRoot)
1339 browser.MagicHelper.SwitchToElement(webElement.Css);
1341 if (
true == isPersistent)
1342 result = browser.MagicHelper
1343 .QueryPersistentSelectors(cssSelector);
1345 result = browser.MagicHelper
1346 .QuerySelectors(cssSelector);
1349 GPALelems =
new ReadOnlyCollection<GPALElement>(result);
1351 else if (
true == browser.UsePuppeteer)
1353 if (
false == webElement?.IsShadowRoot)
1354 browser.PuppeteerClient.InElement(webElement.Css).Execute();
1356 var result2 = Task.Run(() => browser.PuppeteerCommunicator.EvaluateSelector(cssSelector));
1358 if (
null != result2)
1359 GPALelems =
new ReadOnlyCollection<GPALElement>(result2.Result);
1363 elems = webElement?.WebElement !=
null
1364 ? webElement.WebElement.FindElements(By.Name(selectorPath.SelectorPath))
1365 : browser.BrowserDriver.FindElements(By.Name(selectorPath.SelectorPath));
1367 GPALelems =
new ReadOnlyCollection<GPALElement>(
1368 elems.Select(e =>
new GPALElement(e, selector.AttributeName) { Browser = browser, Selector = selector }).ToList()
1385 private static ReadOnlyCollection<GPALElement> FindWebElementsByValue(Browser browser, SelectorPathEntry selectorPath, Selector selector, GPALElement webElement =
null,
bool isPersistent =
false)
1387 bool searchForElement = selector.SearchForSelector;
1388 ReadOnlyCollection<IWebElement> elems;
1389 ReadOnlyCollection<GPALElement> GPALelems =
null;
1390 string cssSelector = $
"[value='{selectorPath.SelectorPath}']";
1392 if (browser.UseOttoMagic)
1394 List<GPALElement> result =
null;
1396 if (
false == webElement?.IsShadowRoot)
1397 browser.MagicHelper.SwitchToElement(webElement.Css);
1399 if (
true == isPersistent)
1400 result = browser.MagicHelper
1401 .QueryPersistentSelectors(cssSelector);
1403 result = browser.MagicHelper
1404 .QuerySelectors(cssSelector);
1407 GPALelems =
new ReadOnlyCollection<GPALElement>(result);
1409 else if (
true == browser.UsePuppeteer)
1411 if (
false == webElement?.IsShadowRoot)
1412 browser.PuppeteerClient.InElement(webElement.Css).Execute();
1414 var result2 = Task.Run(() => browser.PuppeteerCommunicator.EvaluateSelector(cssSelector));
1415 if (
null != result2)
1416 GPALelems =
new ReadOnlyCollection<GPALElement>(result2.Result);
1420 elems = webElement?.WebElement !=
null
1421 ? webElement.WebElement.FindElements(By.CssSelector($
"[value='{selectorPath.SelectorPath}']"))
1422 : browser.BrowserDriver.FindElements(By.CssSelector($
"[value='{selectorPath.SelectorPath}']"));
1424 GPALelems =
new ReadOnlyCollection<GPALElement>(
1425 elems.Select(e =>
new GPALElement(e, selector.AttributeName) { Browser = browser, Selector = selector }).ToList()
1441 private static ReadOnlyCollection<GPALElement> FindWebElementsByPlaceholder(Browser browser, SelectorPathEntry selectorPath, Selector selector, GPALElement webElement =
null,
bool isPersistent =
false)
1443 bool searchForElement = selector.SearchForSelector;
1444 ReadOnlyCollection<IWebElement> elems;
1445 ReadOnlyCollection<GPALElement> GPALelems =
null;
1447 string cssSelector = $
"[placeholder='{selectorPath.SelectorPath.Replace("\\
", "\\\\
").Replace("'", "\\'")}']";
1449 if (browser.UseOttoMagic)
1451 List<GPALElement> result =
null;
1453 if (
false == webElement?.IsShadowRoot)
1454 browser.MagicHelper.SwitchToElement(webElement.Css);
1456 if (
true == isPersistent)
1457 result = browser.MagicHelper
1458 .QueryPersistentSelectors(cssSelector);
1460 result = browser.MagicHelper
1461 .QuerySelectors(cssSelector);
1464 GPALelems =
new ReadOnlyCollection<GPALElement>(result);
1466 else if (
true == browser.UsePuppeteer)
1468 if (
false == webElement?.IsShadowRoot)
1469 browser.PuppeteerClient.InElement(webElement.Css).Execute();
1471 var result2 = Task.Run(() => browser.PuppeteerCommunicator.EvaluateSelector(cssSelector));
1472 if (
null != result2)
1473 GPALelems =
new ReadOnlyCollection<GPALElement>(result2.Result);
1477 elems = webElement?.WebElement !=
null
1478 ? webElement.WebElement.FindElements(By.CssSelector(cssSelector))
1479 : browser.BrowserDriver.FindElements(By.CssSelector(cssSelector));
1481 GPALelems =
new ReadOnlyCollection<GPALElement>(
1482 elems.Select(e =>
new GPALElement(e, selector.AttributeName) { Browser = browser, Selector = selector }).ToList()
1498 public static void PublishToEventHandler(Browser browser, Selector selector, List<GPALElement> webElements)
1501 if (
true == selector.DeleteMe)
1502 msg = $
"Ignoring selector [{selector.Name}] marked for deletion. Continuing.";
1503 else if (
null == webElements || 0 == webElements.Count)
1504 msg = $
"No elements found for selector [{selector.Name}]. Continuing.";
1506 GPAL.PublishSimpleEvent(GPALEventType.WARNING, msg, browser, GPALObjectType.Browser);
1518 public static List<GPALElement> WaitFor(Browser browser,
int timeoutInTicks, out
bool matchedAll)
1520 bool matchedAll2 =
true;
1521 List<GPALElement> foundElements =
null;
1522 List<GPALElement> matchedElements;
1524 foundElements =
new List<GPALElement>();
1526 foreach (Selector selector
in browser.CurrentUOW.WithSelectorList)
1528 if (SelectorType.Selector != selector.SelectorType)
1531 WaitFor(browser, selector, timeoutInTicks, out matchedAll2, out matchedElements);
1535 if (
true == selector.DeleteMe ||
null == matchedElements)
1537 PublishToEventHandler(browser, selector, matchedElements);
1543 foreach (GPALElement webElement
in matchedElements)
1545 foundElements.Add(webElement);
1547 if (++rowCount >= browser.CurrentUOW.WithAllThatMatch)
1552 matchedAll = matchedAll2;
1553 return foundElements;
1568 private static List<GPALElement> WaitFor(Browser browser, Selector selector,
int timeoutInTicks, out
bool matchedAll, out List<GPALElement> matchedElements, GPALElement webElement =
null)
1570 bool matchedAll2 =
true;
1571 List<GPALElement> matchedElements2;
1572 ReadOnlyCollection<GPALElement> elements;
1573 int orgTimeoutInTicks = timeoutInTicks;
1575 if (WaitTime.Forever == timeoutInTicks)
1576 timeoutInTicks = Int32.MaxValue;
1578 GPAL.PublishSimpleEvent(GPALEventType.INFO, $
"Waiting up to [{timeoutInTicks}] ms for elements matching selector [{selector.Name}][{selector.SelectorPath}]", browser, GPALObjectType.Browser);
1580 var sw = Stopwatch.StartNew();
1583 elements = FindWebElements(browser, browser.CurrentUOW, selector,
1584 out matchedAll2, out matchedElements2, webElement,
false);
1587 if (0 == matchedElements2?.Count && WaitTime.Immediate != timeoutInTicks && sw.ElapsedMilliseconds < timeoutInTicks)
1588 Thread.Sleep((
int)Math.Min(1000, timeoutInTicks - sw.ElapsedMilliseconds));
1590 }
while (sw.ElapsedMilliseconds < timeoutInTicks &&
1591 (matchedElements2 ==
null || matchedElements2.Count == 0));
1593 matchedAll = matchedAll2;
1594 matchedElements = matchedElements2;
1597 if (ElementState.NotSet != browser.CurrentUOW.WaitForElementState)
1598 foreach (GPALElement gPALElement
in matchedElements2)
1599 matchedAll &= WaitFor(browser, gPALElement, browser.CurrentUOW.WaitForElementState, orgTimeoutInTicks);
1601 return elements?.ToList<GPALElement>();
1612 public static bool WaitFor(Browser browser, GPALElement element, ElementState state,
int timeoutInTicks)
1614 const int defaultStateWaitInTicks = 3000;
1616 if (WaitTime.Forever == timeoutInTicks)
1618 timeoutInTicks = Int32.MaxValue;
1620 else if (timeoutInTicks < 0)
1622 GPAL.PublishSimpleEvent(GPALEventType.WARNING, $
"WaitFor(ElementState.[{state}]) needs a positive wait time but got [{timeoutInTicks}] ms (no .WaitFor(timeInMs) was set for this unit of work) - using the default of [{defaultStateWaitInTicks}] ms instead.", element, GPALObjectType.Other);
1623 timeoutInTicks = defaultStateWaitInTicks;
1628 dynamic retryElement = element;
1629 int tmpTimeoutInTicks = timeoutInTicks;
1635 int sleepTime = Math.Min(1000, tmpTimeoutInTicks);
1637 Thread.Sleep(sleepTime);
1639 if (IsElementInState(browser, element, state))
1642 tmpTimeoutInTicks -= sleepTime;
1644 catch (StaleElementReferenceException sereex)
1646 string message = $
"StaleElementReferenceException while waiting for state [{state}]: [{sereex.Message}]";
1648 retry = ResolveWebElementIssue(browser, browser.CurrentUOW, ref retryElement, sereex, ref retries, message);
1650 element = retryElement;
1652 catch (GPALException)
1656 catch (Exception ex)
1658 GPAL.PublishSimpleEvent(GPALEventType.EXCEPTION, $
"Error while waiting for state [{state}]", element, GPALObjectType.Other, ex);
1661 }
while (tmpTimeoutInTicks > 0 && retry);
1663 GPAL.PublishSimpleEvent(GPALEventType.WARNING, $
"Element not in state [{state}] within [{timeoutInTicks}] ms.", element, GPALObjectType.Other);
1674 private static bool IsElementInState(Browser browser, GPALElement element, ElementState state)
1680 case ElementState.Visible:
1681 return element.Displayed;
1682 case ElementState.Hidden:
1683 return !element.Displayed;
1684 case ElementState.Enabled:
1685 return element.Enabled;
1686 case ElementState.Disabled:
1687 return !element.Enabled;
1688 case ElementState.Checked:
1689 return element.Selected || element.GetAttribute(
"checked") ==
"true";
1690 case ElementState.Unchecked:
1691 return !element.Selected && element.GetAttribute(
"checked") !=
"true";
1692 case ElementState.Selected:
1693 return element.Selected;
1694 case ElementState.Unselected:
1695 return !element.Selected;
1696 case ElementState.Clickable:
1697 return IsElementClickable(element);
1698 case ElementState.Editable:
1699 return IsElementEditable(element);
1701 GPAL.PublishSimpleEvent(GPALEventType.WARNING, $
"Unsupported element state [{state}].", element, GPALObjectType.Other);
1705 catch (GPALException)
1709 catch (Exception ex)
1711 GPAL.PublishSimpleEvent(GPALEventType.EXCEPTION, $
"Error checking state [{state}]", element, GPALObjectType.Other, ex);
1722 public static bool FillInFrom(Browser browser,
string useText, WriteMode writeMode, GPALElement element =
null)
1724 bool retVal =
false;
1725 ReadOnlyCollection<GPALElement> elems =
null;
1726 List<GPALElement> matchedElements =
null;
1728 foreach (Selector selector
in browser.CurrentUOW.WithSelectorList)
1730 if (SelectorType.Selector != selector.SelectorType)
1733 if (
null == element)
1734 elems = ElementHelper.FindWebElements(browser, browser.CurrentUOW, selector, out
bool matchedAll, out matchedElements);
1737 if (
true == selector.DeleteMe ||
null == elems)
1739 PublishToEventHandler(browser, selector, elems?.ToList());
1743 if (
null != element)
1745 var tmpElems =
new List<GPALElement>();
1746 tmpElems.Add(element);
1747 elems =
new ReadOnlyCollection<GPALElement>(tmpElems);
1749 else if (
null != matchedElements)
1750 elems =
new ReadOnlyCollection<GPALElement>(matchedElements);
1752 if (
true == elems.Any())
1753 handleFillIn(elems);
1755 if (
true == retVal &&
null != browser.CurrentUOW.CallAfterFillIn &&
true == elems.Any())
1757 UnitOfWork safeUOW = browser.CurrentUOW;
1758 CallIfStatus handled = 0;
1762 IGPALGrid<string> tokens = GPAL.GridForType<
string>();
1763 tokens.AddRow(
new List<string> { useText });
1765 GPAL.PublishSimpleEvent(GPALEventType.INFO, $
"Invoking CallAfterFillIn [{browser.CurrentUOW.CallAfterFillIn.Method.Name}]", browser, GPALObjectType.Browser);
1766 handled = browser.CurrentUOW.CallAfterFillIn(browser, tokens, 1);
1770 browser.CurrentUOW = safeUOW;
1783 if (CallIfStatus.Terminate == handled)
1785 string str = $
"CallAfterFillIn handler [{browser.CurrentUOW.AppCallAfterFillIn.GetInvocationList()[0].Method.Name}] requested program termination.";
1786 GPAL.PublishSimpleEvent(GPALEventType.INFO, str, browser, GPALObjectType.Browser);
1787 throw new GPALException($
"" + str);
1791 void handleFillIn(ReadOnlyCollection<GPALElement> elems)
1795 foreach (GPALElement elem
in elems)
1797 string type = elem.GetAttribute(
"type");
1798 string abbreviatedText = useText[0] +
"..." + useText[useText.Length - 1];
1800 if (
null == type ||
false == type.Equals(
"password"))
1802 if (GPALEventType.DEBUG == (GPALEventType.DEBUG & GPAL.GPALSettings.DebugEvents) || GPALEventType.DEBUG == (GPALEventType.DEBUG & GPAL.GPALSettings.ConsoleEvents))
1803 GPAL.PublishSimpleEvent(GPALEventType.DEBUG, $
"[{writeMode}] text [{useText}] in element [{elem.TagName}][{selector.Name}]", browser, GPALObjectType.Browser);
1805 GPAL.PublishSimpleEvent(GPALEventType.INFO, $
"[{writeMode}] text [{abbreviatedText}] in element [{elem.TagName}][{selector.Name}]", browser, GPALObjectType.Browser);
1808 GPAL.PublishSimpleEvent(GPALEventType.INFO, $
"[{writeMode}] XXXpasswordXXX in element [{elem.TagName}][{selector.Name}]", browser, GPALObjectType.Browser);
1816 if (
false == browser.BrowserSettings.UseHeadless &&
false == browser.BrowserSettings.HiddenDesktop
1817 && ((
"GPALElement" == elem.TagName && InteractionType.Hardware == selector.InteractionType)
1818 ||
true == browser.BrowserSettings.UseHardware ||
true == GPAL.GPALSettings.UseHardware))
1819 HardwareFillInFrom(browser, elem, selector.OffsetX, selector.OffsetY, useText, writeMode);
1820 else if (
"GPALElement" == elem.TagName)
1822 FillInFrom(browser, ToViewportPoint(browser, elem, selector), useText, writeMode);
1823 else if (browser.UseOttoMagic)
1825 if (
false == browser.BrowserSettings.UseHeadless && InteractionType.Hardware == selector.InteractionType)
1826 ElementHelper.HardwareFillInFrom(browser, elem, selector.OffsetX, selector.OffsetY, useText, writeMode);
1830 case WriteMode.Append:
1831 browser.MagicHelper.FillInAppend(elem.Css, useText);
1833 case WriteMode.Insert:
1834 browser.MagicHelper.FillInInsert(elem.Css, useText);
1836 case WriteMode.Overwrite:
1837 browser.MagicHelper.FillInOverwrite(elem.Css, useText);
1843 if (++rowCount >= browser.CurrentUOW.WithAllThatMatch)
1846 else if (
true == browser.UsePuppeteer)
1848 if (
false == browser.BrowserSettings.UseHeadless && InteractionType.Hardware == selector.InteractionType)
1849 ElementHelper.HardwareFillInFrom(browser, elem, selector.OffsetX, selector.OffsetY, useText, writeMode);
1853 case WriteMode.Append:
1854 browser.PuppeteerClient.FillInAppend(elem).WithText(useText).Execute();
1856 case WriteMode.Insert:
1857 browser.PuppeteerClient.FillInInsert(elem).WithText(useText).Execute();
1859 case WriteMode.Overwrite:
1860 browser.PuppeteerClient.FillInOverwrite(elem).WithText(useText).Execute();
1866 if (++rowCount >= browser.CurrentUOW.WithAllThatMatch)
1869 else if (
true == browser.UseSelenium)
1871 if (
false == browser.BrowserSettings.UseHeadless && InteractionType.Hardware == selector.InteractionType)
1872 ElementHelper.HardwareFillInFrom(browser, elem, selector.OffsetX, selector.OffsetY, useText, writeMode);
1874 SeleniumFillInFrom(browser, elem, useText, writeMode);
1890 public static void SeleniumSendKeys(Browser browser, GPALElement element,
byte keyCode)
1898 keyToSend = Keys.Alt;
1901 keyToSend = Keys.Delete;
1904 keyToSend = Keys.F10;
1907 keyToSend = Keys.PageUp;
1910 keyToSend = Keys.PageDown;
1913 keyToSend = Keys.End;
1916 keyToSend = Keys.Home;
1919 keyToSend = Keys.ArrowDown;
1922 keyToSend = Keys.Tab;
1925 keyToSend = Keys.Return;
1928 keyToSend = Keys.Shift;
1931 keyToSend = Keys.LeftShift;
1934 keyToSend = Keys.LeftControl;
1937 keyToSend = Keys.Control;
1940 keyToSend = Keys.Space;
1943 keyToSend = Keys.Backspace;
1947 keyToSend = ((char)keyCode).ToString();
1952 element.SendKeys(keyToSend);
1968 private static readonly HashSet<string> valueOnlyInputTypes =
new HashSet<string>
1970 "color",
"date",
"datetime-local",
"month",
"week",
"time",
1972 public static void SeleniumFillInFrom(Browser browser, GPALElement elem,
string textToUse, WriteMode writeMode)
1975 if (
true == valueOnlyInputTypes.Contains(elem.Type))
1977 JavaScriptFillInFrom(browser, elem, textToUse, writeMode);
1981 textToUse = ElementHelper.GetKeystrokeValueForInput(elem, textToUse);
1985 if (WriteMode.Overwrite == writeMode)
1987 else if (WriteMode.Append == writeMode)
1989 textToUse = elem.Text + textToUse;
1993 elem.SendKeys(textToUse);
1995 catch (GPALException)
1999 catch (Exception ex)
2001 if (
false == GPAL.NoFallbackRecoveryActions)
2003 GPAL.PublishSimpleEvent(GPALEventType.EXCEPTION,
"Unable to fill in text. Falling back on javascript.", browser, GPALObjectType.Browser, ex);
2004 JavaScriptFillInFrom(browser, elem, textToUse, writeMode);
2020 public static void HardwareFillInFrom(Browser browser, GPALElement webElement,
int offsetX,
int offsetY,
string text, WriteMode writeMode)
2022 string inputType = webElement.Type?.ToLowerInvariant() ??
"";
2025 bool isSpecialInput =
new[] {
"date",
"time",
"datetime-local",
"month",
"week",
"color" }
2026 .Contains(inputType);
2028 HardwareFocus(browser, webElement, offsetX, offsetY);
2033 if (
true == _lastPlacementQuestionable)
2035 GPAL.PublishSimpleEvent(GPALEventType.WARNING,
2036 $
"Skipping fill-in [{browser.CurrentUOW?.CurrentSelector?.Name}], questionable element placement.",
2037 browser, GPALObjectType.Browser);
2046 if (
"week" != inputType && BrowserType.FireFox != browser.BrowserSettings.BrowserType)
2049 HardwareHelper.SendChar(
'a', ModifierKeys.Control,
true);
2051 HardwareHelper.SendKey(GPAL.VK_DELETE);
2055 else if (WriteMode.Overwrite == writeMode)
2057 HardwareHelper.SendChar(
'a', ModifierKeys.Control,
true);
2061 else if (WriteMode.Append == writeMode)
2063 HardwareHelper.SendKey(GPAL.VK_END, ModifierKeys.Control);
2066 else if (WriteMode.Insert == writeMode)
2068 HardwareHelper.SendKey(GPAL.VK_HOME, ModifierKeys.Control);
2072 string textToType = GetKeystrokeValueForInput(webElement, text);
2077 HardwareFillInColor(textToType.TrimStart(
'#'), browser.BrowserSettings.BrowserType);
2079 case "datetime-local":
2080 HardwareFillInDateTimeLocal(textToType, browser.BrowserSettings.BrowserType);
2083 HardwareFillInWeek(textToType);
2086 HardwareFillInMonth(textToType);
2089 HardwareHelper.SendString(textToType);
2098 public static void HardwareFillInMonth(
string isoValue)
2100 var parts = isoValue.Split(
'/');
2101 string year = parts[0];
2102 string month = parts[1];
2103 HardwareHelper.SendString(month);
2105 HardwareHelper.SendKey(GPAL.VK_TAB);
2106 HardwareHelper.SendString(year);
2113 public static void HardwareFillInWeek(
string isoValue)
2115 if (isoValue.Contains(
"-W"))
2117 var parts = isoValue.Split(
'-');
2118 string year = parts[0];
2119 string week = parts[1].TrimStart(
'W');
2120 HardwareHelper.SendString($
"{week}{year}");
2128 public static void HardwareFillInDateTimeLocal(
string isoValue, BrowserType browserType)
2131 if (!DateTime.TryParseExact(isoValue,
2134 System.Globalization.DateTimeStyles.None,
2140 string datePart = dt.ToString(
"MM/dd/yyyy");
2141 string timePart = dt.ToString(
"hh:mm");
2142 string amPm = dt.ToString(
"tt");
2145 HardwareHelper.SendString(datePart);
2149 if (BrowserType.FireFox != browserType)
2152 HardwareHelper.SendKey(GPAL.VK_TAB);
2157 HardwareHelper.SendString(timePart);
2161 HardwareHelper.SendString(amPm);
2170 public static void HardwareFillInColor(
string hexValue, BrowserType browserType)
2172 if (hexValue.Length != 6)
2175 hexValue =
"000000";
2179 int r = Convert.ToInt32(hexValue.Substring(0, 2), 16);
2180 int g = Convert.ToInt32(hexValue.Substring(2, 2), 16);
2181 int b = Convert.ToInt32(hexValue.Substring(4, 2), 16);
2185 int tabsToFirstField = BrowserType.FireFox == browserType ? 7 : 3;
2187 for (
int tab = 0; tab < tabsToFirstField; tab++)
2188 HardwareHelper.SendKey(GPAL.VK_TAB);
2191 HardwareHelper.SendString(r.ToString());
2192 HardwareHelper.SendKey(GPAL.VK_TAB);
2195 HardwareHelper.SendString(g.ToString());
2196 HardwareHelper.SendKey(GPAL.VK_TAB);
2199 HardwareHelper.SendString(b.ToString());
2203 HardwareHelper.SendKey(GPAL.VK_RETURN);
2211 public static string GetKeystrokeValueForInput(GPALElement webElement,
string isoValue)
2213 if (
string.IsNullOrEmpty(isoValue) || webElement?.TagName?.ToLowerInvariant() !=
"input")
2216 string type = webElement.Type?.ToLowerInvariant() ??
"";
2224 if (DateTime.TryParseExact(isoValue,
"yyyy-MM-dd",
null, System.Globalization.DateTimeStyles.None, out DateTime dt))
2225 return dt.ToString(
"MM/dd/yyyy");
2228 case "datetime-local":
2230 if (DateTime.TryParseExact(isoValue.Replace(
"T",
" "),
"yyyy-MM-dd HH:mm",
null, System.Globalization.DateTimeStyles.None, out dt))
2231 return dt.ToString(
"MM/dd/yyyy HH:mm");
2232 return isoValue.Replace(
"T",
" ");
2236 return isoValue.Replace(
"-",
"/");
2246 if (DateTime.TryParseExact(isoValue,
new[] {
"HH:mm",
"H:mm",
"HH:mm:ss" }, System.Globalization.CultureInfo.InvariantCulture, System.Globalization.DateTimeStyles.None, out dt))
2247 return dt.ToString(
"hh:mm tt", System.Globalization.CultureInfo.InvariantCulture);
2266 public static void JavaScriptFillInFrom(Browser browser, GPALElement webElement,
string text, WriteMode writeMode)
2268 IWebDriver browserDriver = browser.BrowserDriver;
2270 BrowserHelper.ExecuteJavaScript(browser,
"arguments[0].focus();", webElement.WebElement);
2275 if (WriteMode.Overwrite == writeMode)
2276 BrowserHelper.ExecuteJavaScript(browser,
"arguments[0].value = arguments[1]", webElement.WebElement, text);
2277 else if (WriteMode.Append == writeMode)
2278 BrowserHelper.ExecuteJavaScript(browser,
"arguments[0].value = arguments[0].value + arguments[1]", webElement.WebElement, text);
2279 else if (WriteMode.Insert == writeMode)
2280 BrowserHelper.ExecuteJavaScript(browser,
"arguments[0].value = arguments[1] + arguments[0].value", webElement.WebElement, text);
2285 BrowserHelper.ExecuteJavaScript(browser,
2286 "arguments[0].dispatchEvent(new Event('input', { bubbles: true })); arguments[0].dispatchEvent(new Event('change', { bubbles: true }));",
2287 webElement.WebElement);
2295 public static async
void PuppeteerFillInFrom(Browser browser, GPALElement webElement,
string text, WriteMode writeMode)
2297 BrowserSettings browserSettings = browser.BrowserSettings;
2299 PuppeteerCommunicator puppeteerCommunicator = browserSettings.PuppeteerCommunicator;
2305 case WriteMode.Append:
2306 browser.PuppeteerClient.FillInAppend(webElement).WithText(text).Execute();
2308 case WriteMode.Insert:
2309 browser.PuppeteerClient.FillInInsert(webElement).WithText(text).Execute();
2311 case WriteMode.Overwrite:
2312 browser.PuppeteerClient.FillInOverwrite(webElement).WithText(text).Execute();
2315 VkCodeToDomKeyConverter.TryConvertAsciiToVkCode(
'\t', out
byte vkCode);
2316 VkCodeToDomKeyConverter.TryConvertVkCodeToDomKey(vkCode, out
string key, out
string code);
2317 await puppeteerCommunicator.SendKey(key, code, vkCode);
2327 public static void JavaScriptSendKey(Browser browser, GPALElement webElement,
byte keycode)
2329 IWebDriver browserDriver = browser.BrowserDriver;
2330 if (
true == VkCodeToDomKeyConverter.TryConvertVkCodeToDomKey(keycode, out
string key, out
string code))
2332 string sendKeyScript =
@"
2333 function isElementEditable(target) {
2334 if (!target || !(target instanceof Element)) return false;
2335 if (target.disabled || target.readOnly) return false;
2337 const tag = target.tagName.toUpperCase();
2338 if (tag === 'TEXTAREA') return true;
2339 if (tag === 'SELECT') return true;
2340 if (tag === 'INPUT') {
2341 const editableTypes = new Set(['text','email','password','search','number','url','tel',
2342 'date','datetime-local','month','time','week','color']);
2343 return editableTypes.has(target.type.toLowerCase());
2345 if (target.isContentEditable === true ||
2346 target.getAttribute('contenteditable') === 'true' ||
2347 target.ownerDocument?.designMode === 'on') {
2350 const style = window.getComputedStyle(target);
2351 if (style.userModify === 'read-write' || style.webkitUserModify === 'read-write' ||
2352 style.cursor === 'text') {
2358 // === Parameters passed from C# ===
2359 const key = arguments[0]; // e.g. 'a', 'Enter', 'Tab'
2360 const code = arguments[1]; // e.g. 'KeyA', 'Enter'
2361 const keyCode = arguments[2]; // numeric keyCode if needed (mostly legacy)
2363 let target = document.activeElement;
2364 if (!target || target === document.body) {
2365 target = document; // fallback
2378 // 1. Key events (for listeners)
2379 target.dispatchEvent(new KeyboardEvent('keydown', eventInit));
2381 if (key.length === 1) { // printable char
2382 target.dispatchEvent(new KeyboardEvent('keypress', eventInit));
2385 // 2. Actually insert text (this is what most sites need)
2386 const isEditable = isElementEditable(target);
2388 if (target.tagName === 'INPUT' || target.tagName === 'TEXTAREA') {
2389 const start = target.selectionStart ?? target.value.length;
2390 const end = target.selectionEnd ?? target.value.length;
2391 target.value = target.value.slice(0, start) + key + target.value.slice(end);
2392 target.selectionStart = target.selectionEnd = start + key.length;
2394 else if (target.isContentEditable) {
2395 // Simpler fallback for contenteditable
2396 document.execCommand('insertText', false, key); // still works in many places
2399 // Fire input event (React, Vue, Angular, etc. listen to this)
2400 target.dispatchEvent(new InputEvent('input', {
2404 inputType: 'insertText',
2408 // Also fire change for some frameworks
2409 target.dispatchEvent(new Event('change', { bubbles: true }));
2413 target.dispatchEvent(new KeyboardEvent('keyup', eventInit));
2415 // Special case: Enter on button/submit
2416 if (key === 'Enter' && (target.tagName === 'BUTTON' ||
2417 (target.tagName === 'INPUT' && (target.type === 'submit' || target.type === 'button')))) {
2422 console.error('SendKey script error:', e);
2427 BrowserHelper.ExecuteJavaScript(browser, sendKeyScript, key, code, keycode.ToString());
2438 public static void Focus(Browser browser)
2440 List<GPALElement> matchedElements;
2442 foreach (Selector selector
in browser.CurrentUOW.WithSelectorList)
2444 if (SelectorType.Selector != selector.SelectorType)
2447 ElementHelper.FindWebElements(browser, browser.CurrentUOW, selector, out
bool matchedAll, out matchedElements);
2450 if (
true == selector.DeleteMe ||
null == matchedElements)
2452 PublishToEventHandler(browser, selector, matchedElements?.ToList());
2458 foreach (GPALElement elem
in matchedElements)
2462 if (
false == browser.BrowserSettings.UseHeadless && (
true == browser.BrowserSettings.UseHardware ||
true == GPAL.GPALSettings.UseHardware))
2464 HardwareFocus(browser, elem, selector.OffsetX, selector.OffsetY);
2466 else if (
true == IsElementEditable(elem))
2469 Click(browser, selector, elem, ClickType.LeftClick, ModifierKeys.NONE);
2471 else if (
true == browser.UseOttoMagic)
2473 browser.MagicHelper.Focus(elem.Css);
2475 else if (
true == browser.UsePuppeteer)
2477 browser.PuppeteerClient.Focus(elem.Css).Execute();
2479 else if (
true == browser.UseSelenium)
2481 if (Enums.InteractionType.JavaScript == selector.InteractionType ||
true == browser.BrowserSettings.UseJavaScript)
2483 JavaScriptFocus(browser, elem);
2485 else if (elem.TagName.ToLower().Equals(
"input"))
2487 elem.SendKeys(OpenQA.Selenium.Keys.Shift);
2494 new Actions(browser.BrowserDriver).MoveToElement((IWebElement)elem.WebElement).Perform();
2496 catch (GPALException)
2500 catch (Exception ex)
2505 if (
false == GPAL.NoFallbackRecoveryActions)
2507 GPAL.PublishSimpleEvent(GPALEventType.WARNING, $
"Selenium threw on selector [{selector.Name}], falling back to javascript.", browser, GPALObjectType.Browser);
2508 ElementHelper.ScrollIntoView(browser, elem);
2511 GPAL.PublishSimpleEvent(GPALEventType.EXCEPTION, $
"Selenium threw on selector [{selector.Name}], and recovery actions are off, so nothing was done about it.", browser, GPALObjectType.Browser, ex);
2516 if (++rowCount >= browser.CurrentUOW.WithAllThatMatch)
2528 private static void JavaScriptFocus(Browser browser, GPALElement webElement)
2530 IWebDriver browserDriver = browser.BrowserDriver;
2532 string forceFocusScript =
$@"
2533 function forceFocus(el) {{
2534 // Remember original state
2535 const wasDisabled = el.disabled;
2536 const originalTabIndex = el.getAttribute('tabindex');
2539 // 1. Enable if disabled
2540 if (wasDisabled) el.disabled = false;
2542 // 2. Make sure it can receive focus
2543 if (el.tabIndex === -1 || !el.hasAttribute('tabindex')) {{
2544 el.setAttribute('tabindex', '0');
2548 el.focus({{ preventScroll: true }});
2550 // 4. Small delay + aggressive fallback (helps with buttons, React, Shadow DOM, etc.)
2552 if (document.activeElement !== el) {{
2555 // Dispatch synthetic focus events
2556 el.dispatchEvent(new FocusEvent('focusin', {{ bubbles: true }}));
2557 el.dispatchEvent(new FocusEvent('focus', {{ bubbles: false }}));
2562 return document.activeElement === el;
2567 // Restore original state
2568 if (wasDisabled) el.disabled = true;
2569 if (originalTabIndex !== null) {{
2570 el.setAttribute('tabindex', originalTabIndex);
2571 }} else if (el.getAttribute('tabindex') === '0') {{
2572 el.removeAttribute('tabindex');
2576 forceFocus(arguments[0]);
2578 BrowserHelper.ExecuteJavaScript(browser, forceFocusScript, webElement.WebElement);
2584 public static void Hide(Browser browser)
2586 List<GPALElement> matchedElements;
2588 foreach (Selector selector
in browser.CurrentUOW.WithSelectorList)
2590 if (SelectorType.Selector != selector.SelectorType)
2593 ReadOnlyCollection<GPALElement> elems = ElementHelper.FindWebElements(browser, browser.CurrentUOW, selector, out
bool matchedAll, out matchedElements);
2596 if (
true == selector.DeleteMe ||
null == elems || 0 == elems.Count)
2598 PublishToEventHandler(browser, selector, elems?.ToList());
2602 if (
null != matchedElements)
2603 elems =
new ReadOnlyCollection<GPALElement>(matchedElements);
2605 foreach (GPALElement elem
in elems)
2606 Hide(browser, elem);
2615 public static void Hide(Browser browser, GPALElement element)
2617 if (
true == browser.UseOttoMagic)
2618 browser.MagicHelper.HideElement(element.Css);
2619 else if (
true == browser.UsePuppeteer)
2620 browser.PuppeteerClient.HideElement(element.ElementBackendNodeId.ToString());
2622 BrowserHelper.ExecuteJavaScript(browser,
"arguments[0].style.display = 'none';", element.WebElement);
2631 public static void SetAttribute(Browser browser,
string attribute,
string value)
2633 List<GPALElement> matchedElements;
2635 foreach (Selector selector
in browser.CurrentUOW.WithSelectorList)
2637 if (SelectorType.Selector != selector.SelectorType)
2640 ReadOnlyCollection<GPALElement> elems = ElementHelper.FindWebElements(browser, browser.CurrentUOW, selector, out
bool matchedAll, out matchedElements);
2643 if (
true == selector.DeleteMe ||
null == elems || 0 == elems.Count)
2645 PublishToEventHandler(browser, selector, elems?.ToList());
2649 if (
null != matchedElements)
2650 elems =
new ReadOnlyCollection<GPALElement>(matchedElements);
2652 foreach (GPALElement elem
in elems)
2653 SetAttribute(browser, elem, attribute, value);
2665 public static void SetAttribute(Browser browser, GPALElement element,
string attribute,
string value)
2667 if (
true == browser.UseOttoMagic)
2668 browser.MagicHelper.SetAttribute(element.Css, attribute, value);
2669 else if (
true == browser.UsePuppeteer)
2670 browser.PuppeteerClient.SetAttribute(element.ElementBackendNodeId.ToString()).WithAttribute(attribute).WithValue(value).Execute();
2672 BrowserHelper.ExecuteJavaScript(browser,
"arguments[0].setAttribute(arguments[1], arguments[2]);", element.WebElement, attribute, value);
2680 public static void SetValueFromElement(Browser browser, Selector srcSelector)
2682 List<GPALElement> matchedElements;
2684 ReadOnlyCollection<GPALElement> srcElems = ElementHelper.FindWebElements(browser, browser.CurrentUOW, srcSelector, out
bool srcMatchedAll, out List<GPALElement> srcMatchedElements);
2686 if (
null != srcMatchedElements)
2687 srcElems =
new ReadOnlyCollection<GPALElement>(srcMatchedElements);
2689 if (
null == srcElems || 0 == srcElems.Count)
2692 GPALElement srcElem = srcElems[0];
2694 foreach (Selector selector
in browser.CurrentUOW.WithSelectorList)
2696 if (SelectorType.Selector != selector.SelectorType)
2699 ReadOnlyCollection<GPALElement> elems = ElementHelper.FindWebElements(browser, browser.CurrentUOW, selector, out
bool matchedAll, out matchedElements);
2702 if (
true == selector.DeleteMe ||
null == elems || 0 == elems.Count)
2704 PublishToEventHandler(browser, selector, elems?.ToList());
2708 if (
null != matchedElements)
2709 elems =
new ReadOnlyCollection<GPALElement>(matchedElements);
2711 foreach (GPALElement elem
in elems)
2712 SetValueFromElement(browser, elem, srcElem);
2723 public static void SetValueFromElement(Browser browser, GPALElement element, GPALElement srcElement)
2725 if (
true == browser.UseOttoMagic)
2726 browser.MagicHelper.SetValueFromElement(srcElement.Css, element.Css);
2727 else if (
true == browser.UsePuppeteer)
2728 browser.PuppeteerClient.WithElementId(srcElement.Css).SetValueFrom(element.Css).Execute();
2730 BrowserHelper.ExecuteJavaScript(browser,
2731 "arguments[0].value = arguments[1].value; arguments[0].dispatchEvent(new Event('input', {bubbles: true})); arguments[0].dispatchEvent(new Event('change', {bubbles: true}));",
2732 element.WebElement, srcElement.WebElement);
2752 public static void MoveTo(Browser browser)
2754 List<GPALElement> matchedElements;
2755 UnitOfWork currentUOW = browser.CurrentUOW;
2757 foreach (Selector selector
in currentUOW.WithSelectorList)
2759 if (SelectorType.Selector != selector.SelectorType)
2762 ReadOnlyCollection<GPALElement> elems = ElementHelper.FindWebElements(browser, currentUOW, selector, out
bool matchedAll, out matchedElements);
2765 if (
true == selector.DeleteMe ||
null == elems || 0 == elems.Count)
2767 PublishToEventHandler(browser, selector, elems?.ToList());
2771 if (
null != matchedElements)
2772 elems =
new ReadOnlyCollection<GPALElement>(matchedElements);
2776 foreach (GPALElement found
in elems)
2783 GPALElement elem =
null != found.ImageMatch &&
false == browser.UseOttoMagic ? found.ImageMatch : found;
2785 ScrollIntoView(browser, elem);
2787 if (0 == elem.Size.Width && 0 == elem.Size.Height)
2789 GPAL.PublishSimpleEvent(GPALEventType.ERROR, $
"Element has Zero Width and Height, skipping.", browser, GPALObjectType.Browser);
2797 if (
false == browser.BrowserSettings.UseHeadless
2798 && ((
"GPALElement" == elem.TagName &&
true == browser.UseOttoMagic)
2799 ||
true == browser.BrowserSettings.UseHardware
2800 ||
true == GPAL.GPALSettings.UseHardware
2801 || InteractionType.Hardware == selector.InteractionType))
2803 HardwareMoveTo(browser, elem, selector);
2805 else if (
"GPALElement" == elem.TagName)
2809 MoveTo(browser, elem, selector);
2811 else if (
true == browser.UseOttoMagic ||
true == browser.UsePuppeteer)
2813 MoveTo(elem, selector.OffsetX, selector.OffsetY, browser);
2815 else if (Enums.InteractionType.JavaScript == selector.InteractionType ||
true == browser.BrowserSettings.UseJavaScript ||
true == browser.BrowserSettings.UseHeadless)
2817 if (
null != currentUOW.ShadowRoot)
2818 JavaScriptHover(browser, currentUOW.ShadowRoot);
2820 JavaScriptHover(browser, elem);
2824 MoveTo(elem, selector.OffsetX, selector.OffsetY, browser);
2827 if (++rowCount >= browser.CurrentUOW.WithAllThatMatch)
2842 private static void MoveTo(Browser browser, GPALElement element, Selector selector)
2844 Point point = ToViewportPoint(browser, element, selector);
2846 if (
true == browser.UsePuppeteer)
2847 browser.PuppeteerClient.MoveTo(point).Execute();
2848 else if (
true == browser.UseSelenium)
2849 new Actions(browser.BrowserDriver).MoveToLocation(point.X, point.Y).Perform();
2851 GPAL.PublishSimpleEvent(GPALEventType.WARNING, $
"Cannot hover the image match for selector [{selector.Name}] - OttoMagic has no coordinate mouse move and there is no hardware mouse available.", browser, GPALObjectType.Browser);
2855 [ThreadStatic]
private static Random _jitter;
2856 private static Random Jitter => _jitter ?? (_jitter =
new Random(Guid.NewGuid().GetHashCode()));
2865 private static Point ImageCenterOffset(Size size)
2868 int jitterX = Math.Max(0, Math.Min(2, (size.Width / 2) - 1));
2869 int jitterY = Math.Max(0, Math.Min(2, (size.Height / 2) - 1));
2871 return new Point((size.Width / 2) + Jitter.Next(-jitterX, jitterX + 1),
2872 (size.Height / 2) + Jitter.Next(-jitterY, jitterY + 1));
2882 internal static string CssForXPath(Browser browser, Selector selector)
2884 string retVal =
null;
2885 string xpath = selector.SelectorPaths[0].SelectorPath;
2887 if (
true == browser.UseOttoMagic)
2889 ReadOnlyCollection<GPALElement> found = FindWebElementsByXPath(browser, selector.SelectorPaths[0], selector);
2891 retVal = 0 < (found?.Count ?? 0) ? found[0].Css : null;
2896 string css = BrowserHelper.ExecuteJavaScript(browser,
$@"
2897 return (function (xpath) {{
2898 var el = document.evaluate(xpath, document, null, XPathResult.FIRST_ORDERED_NODE_TYPE, null).singleNodeValue;
2901 while (el && el.nodeType === Node.ELEMENT_NODE) {{
2903 path.unshift('#' + CSS.escape(el.id));
2907 var part = el.nodeName.toLowerCase(), sibling = el, nth = 1;
2909 while ((sibling = sibling.previousElementSibling) != null)
2910 if (sibling.nodeName === el.nodeName)
2913 path.unshift(part + ':nth-of-type(' + nth + ')');
2914 el = el.parentElement;
2917 return path.join(' > ');
2918 }})({Newtonsoft.Json.JsonConvert.SerializeObject(xpath)});");
2920 retVal =
false ==
string.IsNullOrEmpty(css) ? css :
null;
2924 GPAL.PublishSimpleEvent(GPALEventType.WARNING, $
"XPath selector [{selector.Name}] matched nothing on the page, so it was left out", browser, GPALObjectType.Browser);
2940 internal static string CssUnderImage(Browser browser, Selector selector)
2942 string retVal =
null;
2943 ReadOnlyCollection<GPALElement> found =
new ReadOnlyCollection<GPALElement>(selector.WebSelectorFoundResults);
2944 GPALElement first = 0 < (found?.Count ?? 0) ? found[0] : null;
2947 if (
null != first &&
"GPALElement" == first.TagName)
2949 ReadOnlyCollection<GPALElement> under = ElementUnderImage(browser, first, selector);
2951 first = 0 < (under?.Count ?? 0) ? under[0] : null;
2954 if (
false ==
string.IsNullOrEmpty(first?.Css))
2957 GPAL.PublishSimpleEvent(GPALEventType.WARNING, $
"Image selector [{selector.Name}] found no element of its own on the page, so it was left out", browser, GPALObjectType.Browser);
2972 internal static string UrlUnderImage(GPALElement match)
2974 string retVal =
null;
2975 Browser browser = match.Browser;
2977 if (
null == browser)
2978 GPAL.PublishSimpleEvent(GPALEventType.WARNING, $
"The url behind image match [{match.Selector?.Name}] cannot be read, since the match carries no browser to ask",
null, GPALObjectType.None);
2981 Point aim = ToViewportPoint(browser, match, match.Selector);
2984 string url = RunPageScript(browser,
$@"
2986 var el = document.elementFromPoint(x, y);
2991 var link = el.closest('a[href], area[href]');
2993 return link ? link.href : (el.currentSrc || el.src || el.data || '');
2994 }})({aim.X}, {aim.Y})");
2996 if (
false ==
string.IsNullOrEmpty(url))
3013 private static string RunPageScript(Browser browser,
string expression)
3016 string retVal = BrowserHelper.ExecuteJavaScript(browser,
true == browser.UseSelenium ?
"return " + expression.Trim() : expression);
3018 if (
true == browser.UseOttoMagic &&
true == retVal?.TrimStart().StartsWith(
"{\"error\""))
3020 if (
false == _userScriptsWarned)
3022 _userScriptsWarned =
true;
3023 GPAL.PublishSimpleEvent(GPALEventType.WARNING, $
"OttoMagic could not run a page script [{retVal}]. Turn on Allow user scripts for the extension to use image matches as elements.", browser, GPALObjectType.Browser);
3028 else if (
"fail" == retVal)
3035 private static bool _userScriptsWarned;
3053 private static ReadOnlyCollection<GPALElement> ElementUnderImage(Browser browser, GPALElement match, Selector selector)
3055 ReadOnlyCollection<GPALElement> retVal =
null;
3056 Point aim = ToViewportPoint(browser, match, selector);
3059 string css = RunPageScript(browser,
$@"
3061 var el = document.elementFromPoint(x, y);
3063 if (!el || 'IFRAME' === el.tagName || 'FRAME' === el.tagName || el.shadowRoot)
3066 var actionable = el.closest('a[href], area[href], button, input, select, textarea, label, summary, ' +
3067 '[role=button], [role=link], [role=menuitem], [role=menuitemcheckbox], [role=menuitemradio], ' +
3068 '[role=tab], [role=checkbox], [role=radio], [role=switch], [role=option], [role=treeitem], ' +
3069 '[onclick], [contenteditable]:not([contenteditable=false])');
3076 while (el && el.nodeType === Node.ELEMENT_NODE) {{
3078 path.unshift('#' + CSS.escape(el.id));
3082 var part = el.nodeName.toLowerCase(), sibling = el, nth = 1;
3084 while ((sibling = sibling.previousElementSibling) != null)
3085 if (sibling.nodeName === el.nodeName)
3088 path.unshift(part + ':nth-of-type(' + nth + ')');
3089 el = el.parentElement;
3092 return path.join(' > ');
3093 }})({aim.X}, {aim.Y})");
3095 if (
false ==
string.IsNullOrEmpty(css))
3097 Selector cssSel = GPAL.Selector.WithCSS(css).ToGPALObject();
3099 retVal = FindWebElementsByCss(browser, cssSel.SelectorPaths[0], cssSel);
3101 if (0 < (retVal?.Count ?? 0))
3103 retVal[0].Css = css;
3104 retVal[0].ImageMatch = match;
3108 GPAL.PublishSimpleEvent(GPALEventType.DEBUG, 0 < retVal?.Count
3109 ? $
"Image match for [{selector.Name}] is element [{css}], clicked at the matched point"
3110 : $
"Image match for [{selector.Name}] has no element of its own under it, so only the point is used", browser, GPALObjectType.Browser);
3129 private static bool PointIsClear(Browser browser, GPALElement resolved, Selector selector, ref Point aim)
3133 string answer = RunPageScript(browser,
$@"
3134 (function (css, x, y) {{
3135 var el = document.querySelector(css);
3140 function hits(px, py) {{
3141 var hit = document.elementFromPoint(px, py);
3142 return !!hit && (hit === el || el.contains(hit));
3145 function named(px, py) {{
3146 var hit = document.elementFromPoint(px, py);
3151 var classes = 'string' === typeof hit.className ? hit.className.trim().split(/\s+/).filter(Boolean).slice(0, 3).join('.') : '';
3153 return hit.tagName.toLowerCase() + (hit.id ? '#' + hit.id : '') + (classes ? '.' + classes : '');
3157 return 'clear|0|0|';
3159 var covering = named(x, y);
3160 var before = el.getBoundingClientRect();
3162 el.scrollIntoView({{ block: 'center', inline: 'nearest', behavior: 'instant' }});
3164 var after = el.getBoundingClientRect();
3165 var dx = Math.round(after.left - before.left), dy = Math.round(after.top - before.top);
3167 return (hits(x + dx, y + dy) ? 'centred' : 'covered') + '|' + dx + '|' + dy + '|' + covering;
3168 }})({Newtonsoft.Json.JsonConvert.SerializeObject(resolved.Css)}, {aim.X}, {aim.Y})");
3171 string[] parts = answer?.Split(
new[] {
'|' }, 4);
3173 if (4 == parts?.Length && (
"centred" == parts[0] ||
"covered" == parts[0]))
3175 aim =
new Point(aim.X + Convert.ToInt32(parts[1]), aim.Y + Convert.ToInt32(parts[2]));
3177 if (
"centred" == parts[0])
3178 GPAL.PublishSimpleEvent(GPALEventType.INFO, $
"[{parts[3]}] covered selector [{selector.Name}] where its picture was, so the element was moved to the middle of the window and is clicked there", browser, GPALObjectType.Browser);
3183 GPAL.PublishSimpleEvent(GPALEventType.WARNING,
true == GPAL.NoFallbackRecoveryActions
3184 ? $
"[{parts[3]}] covers selector [{selector.Name}] even with it in the middle of the window, and recovery actions are off, so the point is clicked and [{parts[3]}] may take the click"
3185 : $
"[{parts[3]}] covers selector [{selector.Name}] even with it in the middle of the window, so the element itself is clicked instead of its picture", browser, GPALObjectType.Browser);
3202 private static void ClickCoveredElement(Browser browser, GPALElement element, ClickType clickType, ModifierKeys modifierKeys)
3204 if (
true == browser.UseOttoMagic)
3206 if (ModifierKeys.NONE != modifierKeys)
3207 browser.MagicHelper.PressModifierKey(modifierKeys);
3211 case ClickType.LeftDoubleClick:
3212 browser.MagicHelper.LeftDoubleClick(element.Css);
3214 case ClickType.MiddleClick:
3215 browser.MagicHelper.MiddleClick(element.Css);
3217 case ClickType.RightClick:
3218 browser.MagicHelper.RightClick(element.Css);
3221 browser.MagicHelper.LeftClick(element.Css);
3225 if (ModifierKeys.NONE != modifierKeys)
3226 browser.MagicHelper.ReleaseModifierKey(modifierKeys);
3229 JavaScriptClick(browser, element, clickType, modifierKeys);
3241 private static Point ToViewportPoint(Browser browser, GPALElement element, Selector selector)
3244 Point aim = (0 == selector.OffsetX && 0 == selector.OffsetY)
3245 ? ImageCenterOffset(element.Size)
3246 :
new Point(selector.OffsetX, selector.OffsetY);
3248 int pointX = element.Location.X + aim.X;
3249 int pointY = element.Location.Y + aim.Y;
3251 if (CoordinateSpace.Screen == element.CoordinateSpace)
3253 Rectangle content = GetWindowRectangle(browser);
3254 pointX -= content.X;
3255 pointY -= content.Y;
3258 return new Point(pointX, pointY);
3268 private static void Click(Browser browser, Point point, ClickType clickType, ModifierKeys modifierKeys)
3273 if (
true == browser.UseOttoMagic && ModifierKeys.NONE != modifierKeys
3274 && (ClickType.LeftClick == clickType || ClickType.MiddleClick == clickType))
3275 GPAL.PublishSimpleEvent(GPALEventType.WARNING, $
"[{Browser.GetModifierKeys(modifierKeys)}] cannot be held for a [{clickType}] at a point on OttoMagic, whose trusted click carries no keys, so the click is made without them.", browser, GPALObjectType.Browser);
3277 if (
true == browser.UsePuppeteer)
3281 case ClickType.LeftClick:
3282 browser.PuppeteerClient.LeftClick(point).WithModifiers(modifierKeys).Execute();
3284 case ClickType.LeftDoubleClick:
3285 browser.PuppeteerClient.LeftDoubleClick(point).WithModifiers(modifierKeys).Execute();
3287 case ClickType.MiddleClick:
3288 browser.PuppeteerClient.MiddleClick(point).WithModifiers(modifierKeys).Execute();
3290 case ClickType.RightClick:
3291 browser.PuppeteerClient.RightClick(point).WithModifiers(modifierKeys).Execute();
3295 else if (
true == browser.UseSelenium && ClickType.MiddleClick == clickType && browser.BrowserDriver is OpenQA.Selenium.Chromium.ChromiumDriver chromium)
3300 int cdpModifiers = (int)(modifierKeys & (ModifierKeys.Alt | ModifierKeys.Control | ModifierKeys.Windows | ModifierKeys.Shift));
3302 chromium.ExecuteCdpCommand(
"Input.dispatchMouseEvent",
new Dictionary<string, object>
3304 [
"type"] =
"mouseMoved", [
"x"] = point.X, [
"y"] = point.Y, [
"button"] =
"none"
3306 chromium.ExecuteCdpCommand(
"Input.dispatchMouseEvent",
new Dictionary<string, object>
3308 [
"type"] =
"mousePressed", [
"x"] = point.X, [
"y"] = point.Y, [
"button"] =
"middle", [
"buttons"] = 4, [
"clickCount"] = 1, [
"modifiers"] = cdpModifiers
3310 chromium.ExecuteCdpCommand(
"Input.dispatchMouseEvent",
new Dictionary<string, object>
3312 [
"type"] =
"mouseReleased", [
"x"] = point.X, [
"y"] = point.Y, [
"button"] =
"middle", [
"buttons"] = 0, [
"clickCount"] = 1, [
"modifiers"] = cdpModifiers
3315 else if (
true == browser.UseSelenium)
3317 Actions action =
new Actions(browser.BrowserDriver).MoveToLocation(point.X, point.Y);
3319 if (modifierKeys.HasFlag(ModifierKeys.Alt))
3320 action.KeyDown(OpenQA.Selenium.Keys.LeftAlt).KeyDown(OpenQA.Selenium.Keys.Alt);
3321 if (modifierKeys.HasFlag(ModifierKeys.Control))
3322 action.KeyDown(OpenQA.Selenium.Keys.LeftControl).KeyDown(OpenQA.Selenium.Keys.Control);
3323 if (modifierKeys.HasFlag(ModifierKeys.Shift))
3324 action.KeyDown(OpenQA.Selenium.Keys.LeftShift).KeyDown(OpenQA.Selenium.Keys.Shift);
3325 if (modifierKeys.HasFlag(ModifierKeys.Windows))
3326 action.KeyDown(OpenQA.Selenium.Keys.Meta);
3330 case ClickType.LeftClick:
3333 case ClickType.LeftDoubleClick:
3334 action.DoubleClick();
3336 case ClickType.RightClick:
3337 action.ContextClick();
3341 GPAL.PublishSimpleEvent(GPALEventType.WARNING, $
"[{clickType}] on an image match by coordinate is not supported by Selenium, performing a left click.", browser, GPALObjectType.Browser);
3346 if (modifierKeys.HasFlag(ModifierKeys.Alt))
3347 action.KeyUp(OpenQA.Selenium.Keys.LeftAlt).KeyUp(OpenQA.Selenium.Keys.Alt);
3348 if (modifierKeys.HasFlag(ModifierKeys.Control))
3349 action.KeyUp(OpenQA.Selenium.Keys.LeftControl).KeyUp(OpenQA.Selenium.Keys.Control);
3350 if (modifierKeys.HasFlag(ModifierKeys.Shift))
3351 action.KeyUp(OpenQA.Selenium.Keys.LeftShift).KeyUp(OpenQA.Selenium.Keys.Shift);
3352 if (modifierKeys.HasFlag(ModifierKeys.Windows))
3353 action.KeyUp(OpenQA.Selenium.Keys.Meta);
3355 ModifierKeys unsupported = modifierKeys & (ModifierKeys.Application | ModifierKeys.ScrollLock);
3356 if (ModifierKeys.NONE != unsupported)
3357 GPAL.PublishSimpleEvent(GPALEventType.WARNING, $
"[{clickType}]: [{Browser.GetModifierKeys(unsupported)}] is not supported via Selenium/WebDriver Actions and was ignored.", browser, GPALObjectType.Browser);
3359 action.Build().Perform();
3361 else if (ClickType.LeftClick != clickType && ClickType.MiddleClick != clickType)
3362 GPAL.PublishSimpleEvent(GPALEventType.WARNING, $
"[{clickType}] on an image is not supported by OttoMagic, which clicks a point with the left or middle button only.", browser, GPALObjectType.Browser);
3363 else if (
true == (ClickType.MiddleClick == clickType
3364 ? browser.MagicHelper.TrustedMiddleClick(point.X, point.Y)
3365 : browser.MagicHelper.TrustedLeftClick(point.X, point.Y)))
3366 GPAL.PublishSimpleEvent(GPALEventType.INFO, $
"[Trusted][{clickType}] clicked at [{point.X},{point.Y}].", browser, GPALObjectType.Browser);
3368 GPAL.PublishSimpleEvent(GPALEventType.WARNING, $
"[Trusted][{clickType}] at [{point.X},{point.Y}] was not made.", browser, GPALObjectType.Browser);
3379 private static void FillInFrom(Browser browser, Point point,
string text, WriteMode writeMode)
3382 Click(browser, point, ClickType.LeftClick, ModifierKeys.NONE);
3384 if (
true == browser.UsePuppeteer)
3386 if (WriteMode.Overwrite == writeMode)
3387 GPAL.PublishSimpleEvent(GPALEventType.WARNING,
"[Overwrite] cannot clear an image match by coordinate, the text will be added to whatever is already there.", browser, GPALObjectType.Browser);
3389 browser.PuppeteerClient.SendString(text).Execute();
3391 else if (
true == browser.UseSelenium)
3393 Actions actions =
new Actions(browser.BrowserDriver);
3395 if (WriteMode.Overwrite == writeMode)
3396 actions.KeyDown(Keys.Control).SendKeys(
"a").KeyUp(Keys.Control);
3398 actions.SendKeys(text).Perform();
3407 string answer = RunPageScript(browser,
$@"
3408 (function (text, mode) {{
3409 var el = document.activeElement;
3411 while (el && el.shadowRoot && el.shadowRoot.activeElement)
3412 el = el.shadowRoot.activeElement;
3414 if (!el || el === document.body)
3415 return 'nothing has focus';
3417 if (el.isContentEditable) {{
3418 if ('Overwrite' === mode)
3419 document.execCommand('selectAll', false, null);
3420 else if ('Append' === mode) {{
3421 var range = document.createRange();
3422 range.selectNodeContents(el);
3423 range.collapse(false);
3424 var selection = window.getSelection();
3425 selection.removeAllRanges();
3426 selection.addRange(range);
3429 document.execCommand('insertText', false, text);
3433 var descriptor = Object.getOwnPropertyDescriptor(Object.getPrototypeOf(el), 'value');
3435 if (!descriptor || !descriptor.set)
3436 return 'the focused ' + el.tagName.toLowerCase() + ' takes no text';
3438 var current = el.value || '';
3439 var next = 'Overwrite' === mode ? text : current + text;
3441 if ('Insert' === mode && typeof el.selectionStart === 'number' && null !== el.selectionStart)
3442 next = current.slice(0, el.selectionStart) + text + current.slice(el.selectionEnd);
3444 descriptor.set.call(el, next);
3445 el.dispatchEvent(new Event('input', {{ bubbles: true }}));
3446 el.dispatchEvent(new Event('change', {{ bubbles: true }}));
3448 }})({Newtonsoft.Json.JsonConvert.SerializeObject(text)}, {Newtonsoft.Json.JsonConvert.SerializeObject(writeMode.ToString())})");
3451 GPAL.PublishSimpleEvent(GPALEventType.WARNING, $
"Cannot fill in the image match by coordinate. OttoMagic focused it with a trusted click, and could not write into it [{answer ?? "no answer
"}].", browser, GPALObjectType.Browser);
3462 private static void MoveTo(GPALElement element,
int offsetX,
int offsetY, Browser browser)
3466 if (
true == browser.UseOttoMagic)
3468 browser.MagicHelper.MoveTo(element.Css);
3470 else if (
true == browser.UsePuppeteer)
3472 browser.PuppeteerClient.MoveTo(element.ElementHandle).Execute();
3475 new Actions(browser.BrowserDriver).MoveToElement(element.WebElement, offsetX, offsetY).Perform();
3477 catch (GPALException)
3481 catch (Exception ex)
3483 if (
false == GPAL.NoFallbackRecoveryActions)
3485 GPAL.PublishSimpleEvent(GPALEventType.WARNING, $
"Selenium threw during move to Selector [{browser.CurrentUOW.CurrentSelector?.Name}], falling back to javascript.", browser, GPALObjectType.Browser);
3486 ElementHelper.ScrollIntoView(browser, element);
3489 GPAL.PublishSimpleEvent(GPALEventType.EXCEPTION, $
"Selenium threw during move to Selector [{browser.CurrentUOW.CurrentSelector?.Name}], and recovery actions are off, so nothing was done about it.", browser, GPALObjectType.Browser, ex);
3502 private static void HardwareFocus(Browser browser, GPALElement webElement,
int OffsetX,
int OffsetY)
3507 ScrollIntoView(browser, webElement, Enums.InteractionType.Hardware);
3511 bool isPickerInput = valueOnlyInputTypes.Contains(webElement.Type?.ToLowerInvariant() ??
"");
3513 if (
true == ElementHelper.IsElementEditable(webElement) ||
true == isPickerInput)
3516 HardwareClick(browser, webElement, ClickType.LeftClick, ModifierKeys.NONE, browser.CurrentUOW.CurrentSelector);
3521 HardwareMoveTo(browser, webElement, browser.CurrentUOW.CurrentSelector);
3532 private static Rectangle HardwareMoveTo(Browser browser, dynamic element, Selector selector)
3534 Rectangle content =
new Rectangle(), iframeRect =
new Rectangle(0, 0, 0, 0);
3535 bool isImageMatch =
"GPALElement" == (string)element.TagName;
3540 if (
false == isImageMatch &&
true == browser.UsePuppeteer)
3541 RefreshLocation(browser, (GPALElement)element);
3543 Rectangle rect2 =
new Rectangle(element.Location, element.Size);
3544 int offsetX = selector.OffsetX;
3545 int offsetY = selector.OffsetY;
3552 Point scroll = Point.Empty;
3553 if (
false == isImageMatch)
3558 if (
true == browser.UseOttoMagic)
3560 pagex = browser.MagicHelper.WindowPageOffsetX();
3561 pagey = browser.MagicHelper.WindowPageOffsetY();
3562 scroll =
new Point(pagex, pagey);
3564 else if (
true == browser.UsePuppeteer)
3566 pagex = browser.PuppeteerClient.WindowPageOffsetX().Execute<
int>();
3567 pagey = browser.PuppeteerClient.WindowPageOffsetY().Execute<
int>();
3568 scroll =
new Point(pagex, pagey);
3572 scroll = GetPageOffset(browser);
3579 if (0 < browser.CurrentUOW.ContextPath?.Count)
3581 foreach (UnitOfWork.WebElementWithType contextType in browser.CurrentUOW.ContextPath)
3583 if (ElementType.IFrame == contextType.elementType)
3585 ClientRectangle tmpRect = contextType.gPalElement.BoundingRect;
3586 iframeRect.X += (int)contextType.gPalElement.Location.X;
3587 iframeRect.Y += (int)contextType.gPalElement.Location.Y;
3593 if (0 == offsetX && 0 == offsetY)
3595 if (
"GPALElement" == (
string)element.TagName)
3598 Point center = ImageCenterOffset(rect2.Size);
3608 int spreadX = Math.Max(1, (
int)rect2.Width / 10);
3609 int spreadY = Math.Max(1, (
int)rect2.Height / 10);
3611 offsetX = (int)rect2.Width / 2 + Jitter.Next(-spreadX, spreadX + 1);
3612 offsetY = (int)rect2.Height / 2 + Jitter.Next(-spreadY, spreadY + 1);
3616 content = GetWindowRectangle(browser);
3617 _lastContentArea = content;
3622 Point imageOrigin = Point.Empty;
3624 if (
"GPALElement" == (
string)element.TagName && CoordinateSpace.Viewport == ((GPALElement)element).CoordinateSpace)
3625 imageOrigin =
new Point(content.X, content.Y);
3628 int screenX = (int)((
"GPALElement" == element.TagName)
3629 ? rect2.Left + offsetX + imageOrigin.X
3630 : rect2.Left + offsetX + iframeRect.X + content.X - scroll.X);
3632 int screenY = (int)((
"GPALElement" == element.TagName)
3633 ? rect2.Top + offsetY + imageOrigin.Y
3634 : rect2.Top + offsetY + iframeRect.Y + content.Y - scroll.Y);
3640 if (
"GPALElement" != (
string)element.TagName
3641 &&
true == IsBiggerThanViewport(
new Size((
int)rect2.Width, (
int)rect2.Height), content)
3642 &&
false == content.Contains(screenX, screenY))
3647 Point origin =
new Point(screenX - offsetX, screenY - offsetY);
3648 Rectangle onScreen = Rectangle.Intersect(
new Rectangle(origin.X, origin.Y, (
int)rect2.Width, (
int)rect2.Height), content);
3650 if (
false == onScreen.IsEmpty)
3657 bool verified =
false;
3658 int tryY = onScreen.Y + onScreen.Height / 2;
3659 List<int> candidates =
new List<int>();
3661 foreach (
int inset
in hitTestInsets)
3662 if (inset < onScreen.Width)
3663 candidates.Add(onScreen.Right - inset);
3665 foreach (
float fraction
in hitTestFractions)
3666 candidates.Add(onScreen.X + (
int)(onScreen.Width * fraction));
3668 foreach (
int tryX
in candidates)
3669 if (
true == PointHitsElement(browser, (GPALElement)element,
new Point(tryX, tryY)))
3677 if (
false == verified)
3682 screenX = onScreen.X + onScreen.Width / 2;
3683 screenY = onScreen.Y + onScreen.Height / 2;
3689 _lastClampedPoint = $
"at [WithOffset({screenX - origin.X}, {screenY - origin.Y})]{(true == verified ? "" : ", which nothing on
this element answered to
")}, for window [{_lastOuterWindow.X}, {_lastOuterWindow.Y}, {_lastOuterWindow.Width}, {_lastOuterWindow.Height}], because the element measures [{(int)rect2.Width}x{(int)rect2.Height}], larger than the window";
3694 if (
true == GPAL.SimulateMouse ||
true == selector.SimulateMouse)
3695 HardwareHelper.MoveMouse(screenX, screenY, 10, 10);
3697 HardwareHelper.MoveMouse(screenX, screenY);
3699 return new Rectangle(screenX, screenY, (
int)rect2.Width, (
int)rect2.Height);
3708 internal static Point GetPageOffset(Browser browser)
3710 Point pageOffset = Point.Empty;
3712 if (
true == browser.UseOttoMagic)
3713 pageOffset =
new Point(browser.MagicHelper.WindowPageOffsetX(), browser.MagicHelper.WindowPageOffsetY());
3714 else if (
true == browser.UsePuppeteer)
3715 pageOffset =
new Point(browser.PuppeteerClient.WindowPageOffsetX().Execute<
int>(), browser.PuppeteerClient.WindowPageOffsetY().Execute<
int>());
3716 else if (BrowserHelper.ExecuteJavaScriptObj(
"return [window.top.pageXOffset, window.top.pageYOffset]", browser) is IList<object> offsets && 2 <= offsets.Count)
3717 pageOffset =
new Point(Convert.ToInt32(offsets[0]), Convert.ToInt32(offsets[1]));
3730 private static bool UsesScreenCoordinates(Browser browser, Enums.InteractionType? interactionType)
3732 Enums.InteractionType? useType = interactionType ?? browser.CurrentUOW?.CurrentSelector?.InteractionType;
3734 return true == browser.BrowserSettings.UseHardware
3735 ||
true == GPAL.GPALSettings.UseHardware
3736 || Enums.InteractionType.Hardware == useType;
3748 internal static void ScrollIntoView(Browser browser, dynamic element, Enums.InteractionType? interactionType =
null,
bool forVisibility =
false)
3750 if (
"GPALElement".Equals(((GPALElement)element).TagName))
3757 if (
true == browser.UseOttoMagic &&
false == forVisibility &&
false == UsesScreenCoordinates(browser, interactionType))
3762 if (
true == IsVisibleInViewport(browser, (GPALElement)element))
3765 if (
true == browser.UseOttoMagic)
3766 browser.MagicHelper.ScrollIntoView(((GPALElement)element).Css);
3767 else if (
true == browser.UsePuppeteer)
3768 browser.PuppeteerClient.ScrollIntoView((GPALElement)element).Execute();
3769 else if (
null != element)
3771 string debounceScript =
@"
3772 // Best JS replacement for scrollIntoViewIfNeeded - do not scroll to if it's already on-screen
3773 function scrollIntoViewIfNeeded(el) {
3774 return new Promise(resolve => {
3775 const io = new IntersectionObserver(([entry]) => {
3777 if (entry.intersectionRatio < 1) {
3778 el.scrollIntoView({ behavior: 'instant', block: 'center', inline: 'center' });
3781 }, { threshold: 1 });
3786 scrollIntoViewIfNeeded(arguments[0]);
3789 BrowserHelper.ExecuteJavaScript(browser, debounceScript, typeof(GPALElement) == element.GetType() ? ((GPALElement)element).WebElement : element);
3796 ScrollUntilVisible(browser, (GPALElement)element);
3801 if (
false == browser.UsePuppeteer &&
true == UsesScreenCoordinates(browser, interactionType))
3802 RefreshLocation(browser, (GPALElement)element);
3812 public static void LeftClick(Browser browser, ModifierKeys modifierKeys = ModifierKeys.NONE, ClickType clickType = ClickType.LeftClick,
bool trusted =
false)
3814 List<GPALElement> matchedElements;
3815 List<GPALElement> clickableElements;
3817 foreach (Selector selector
in browser.CurrentUOW.WithSelectorList)
3819 if (SelectorType.Selector != selector.SelectorType)
3822 ElementHelper.FindWebElements(browser, browser.CurrentUOW, selector, out
bool matchedAll, out matchedElements);
3825 if (
true == selector.DeleteMe ||
null == matchedElements)
3827 PublishToEventHandler(browser, selector, matchedElements);
3834 if (1 < matchedElements.Count)
3836 clickableElements =
new List<GPALElement>();
3837 foreach (GPALElement elem
in matchedElements)
3838 if (
true == elem.IsClickable(
false))
3839 clickableElements.Add(elem);
3841 if (0 == clickableElements.Count)
3842 clickableElements = matchedElements;
3845 clickableElements = matchedElements;
3849 foreach (GPALElement elem
in clickableElements)
3853 if (
true == elem.TagName.ToLower().Equals(
"option"))
3854 SelectClick(browser, selector, elem, SelectClickType.LeftClick, modifierKeys);
3856 Click(browser, selector, elem, clickType, modifierKeys, trusted);
3858 if (++rowCount >= browser.CurrentUOW.WithAllThatMatch)
3870 public static void MiddleClick(Browser browser)
3872 LeftClick(browser, ModifierKeys.NONE, ClickType.MiddleClick);
3882 public static void ScrollElement(Browser browser,
int amount, ScrollTypes scrollTypes)
3884 List<GPALElement> matchedElements;
3885 bool matchedAll =
false;
3887 foreach (Selector selector
in browser.CurrentUOW.WithSelectorList)
3889 if (SelectorType.Selector != selector.SelectorType)
3892 ElementHelper.FindWebElements(browser, browser.CurrentUOW, selector, out matchedAll, out matchedElements);
3894 if (
null != matchedElements)
3898 foreach (GPALElement elem
in matchedElements)
3902 int hAmount = ScrollTypes.Horizontal == scrollTypes ? amount : 0;
3903 int vAmount = ScrollTypes.Vertical == scrollTypes ? amount : 0;
3908 ScrollIntoView(browser, elem,
null,
true);
3910 if (
true == browser.UseOttoMagic)
3911 browser.MagicHelper.ScrollElement(elem.Css, hAmount, vAmount);
3912 else if (
true == browser.UsePuppeteer)
3913 browser.PuppeteerClient.ScrollElement(elem.Css).WithHPixels(hAmount).WithVPixels(vAmount).Execute();
3914 else if (
true == browser.UseSelenium)
3915 BrowserHelper.ExecuteJavaScript(browser, $
"arguments[0].scrollBy(left: {hAmount}, top: {vAmount}, behavior: 'instant')", elem.WebElement);
3917 if (++rowCount >= browser.CurrentUOW.WithAllThatMatch)
3931 public static bool GenericClick(Browser browser, ClickType clickType)
3933 List<GPALElement> matchedElements;
3934 bool matchedAll =
false;
3935 bool clickPerformed =
false;
3937 foreach (Selector selector
in browser.CurrentUOW.WithSelectorList)
3939 if (SelectorType.Selector != selector.SelectorType)
3942 ElementHelper.FindWebElements(browser, browser.CurrentUOW, selector, out matchedAll, out matchedElements);
3944 if (
null != matchedElements)
3947 foreach (GPALElement elem
in matchedElements)
3951 if (
true == elem.TagName.ToLower().Equals(
"option"))
3952 SelectClick(browser, selector, elem, SelectClickType.LeftClick, ModifierKeys.NONE);
3954 Click(browser, selector, elem, clickType, ModifierKeys.NONE);
3956 clickPerformed =
true;
3958 if (++rowCount >= browser.CurrentUOW.WithAllThatMatch)
3964 return clickPerformed;
3973 public static bool GenericClick(Browser browser, SelectClickType selectClickType)
3975 List<GPALElement> matchedElements;
3976 bool matchedAll =
false;
3977 bool clickPerformed =
false;
3979 foreach (Selector selector
in browser.CurrentUOW.WithSelectorList)
3981 if (SelectorType.Selector != selector.SelectorType)
3984 ElementHelper.FindWebElements(browser, browser.CurrentUOW, selector, out matchedAll, out matchedElements);
3986 if (
null != matchedElements)
3989 foreach (GPALElement elem
in matchedElements)
3993 if (
true == elem.TagName.ToLower().Equals(
"option"))
3994 SelectClick(browser, selector, elem, selectClickType, ModifierKeys.NONE);
3996 Click(browser, selector, elem, ClickType.LeftClick, ModifierKeys.NONE);
3998 clickPerformed =
true;
4000 if (++rowCount >= browser.CurrentUOW.WithAllThatMatch)
4006 return clickPerformed;
4012 internal static class SelectClickHelper
4015 internal static readonly Dictionary<string, int> _sequentialSelectIndices =
new Dictionary<string, int>();
4017 internal static string _lastParentNodeId =
null;
4026 internal static string GetElementAttributeHashCsharp(GPALElement element, Browser browser)
4031 var attrList =
new List<string>();
4033 foreach (var attrName
in element.Attributes.Keys)
4036 string value = element.GetAttribute(attrName);
4037 if (!
string.IsNullOrEmpty(value))
4039 attrList.Add($
"{attrName}={value}");
4045 string attrString =
string.Join(
"|", attrList);
4049 foreach (
char c
in attrString)
4051 hash = ((hash << 5) - hash + c) | 0;
4054 return hash.ToString();
4058 return element.GetHashCode().ToString();
4069 internal static string GetElementAttributeHashJavaScript(GPALElement element, Browser browser)
4073 if (
false == browser.UseOttoMagic)
4075 string jsScript =
@"
4076 let attrs = arguments[0].attributes;
4078 for (let attr of attrs) {
4079 attrList.push(attr.name + '=' + attr.value);
4081 attrList.sort(); // Sort attributes for consistent hashing
4082 let str = attrList.join('|');
4084 for (let i = 0; i < str.length; i++) {
4085 hash = ((hash << 5) - hash + str.charCodeAt(i)) | 0;
4087 return hash.toString();
4089 return (
string)BrowserHelper.ExecuteJavaScript(browser, jsScript, element.WebElement) ?? element.GetHashCode().ToString();
4092 return browser.OttoMagicClient.GetElementAttributeHash(element.Css).Execute().Replace(
"\"",
"");
4097 return element.GetHashCode().ToString();
4112 public static void SelectClick(Browser browser, Selector selector, GPALElement webElement, SelectClickType clickType, ModifierKeys modifierKeys)
4115 dynamic result =
null;
4116 dynamic parentElem =
null;
4117 SelectElement elem =
null;
4118 string selectedValue =
null;
4119 dynamic options =
null;
4120 string elementId =
null;
4124 if (
true == browser.UseOttoMagic)
4125 result = browser.MagicHelper.GetParentNode(webElement.Css);
4126 else if (
true == browser.UsePuppeteer)
4127 result = browser.PuppeteerClient.GetParentNode(webElement.ElementHandle).Execute<GPALElement>();
4129 result = BrowserHelper.ExecuteJavaScriptObj(
"return arguments[0].parentNode;", browser, webElement.WebElement);
4133 if (
true == browser.UseSelenium)
4135 parentElem = result;
4136 elementId = parentElem.GetAttribute(
"id");
4140 parentElem = (GPALElement)result;
4141 parentElem.Browser = browser;
4142 elementId = parentElem.GetAttribute(
"id");
4147 ScrollIntoView(browser, parentElem);
4150 if (
true == browser.UseSelenium)
4152 elem =
new SelectElement(parentElem);
4155 if (SelectClickType.LeftClick != clickType)
4157 options = elem.Options;
4158 if (options.Count == 0)
4160 GPAL.PublishSimpleEvent(GPALEventType.ERROR,
"No options found in select element.", webElement, GPALObjectType.Other);
4166 if (
string.IsNullOrEmpty(elementId))
4171 elementId = SelectClickHelper.GetElementAttributeHashJavaScript(parentElem, browser);
4176 if (SelectClickType.LeftClick != clickType)
4178 Selector optionsSelector = GPAL.CssSelector($
"{parentElem.Css} > option").WithSelectorName(
"options").ToGPALObject();
4180 options = FindWebElementsByCss(browser, optionsSelector.SelectorPaths[0], optionsSelector);
4182 if (0 == options.Count)
4184 Click(browser, selector, webElement, ClickType.LeftClick, modifierKeys);
4189 if (
string.IsNullOrEmpty(elementId))
4191 if (
true == browser.UseOttoMagic)
4192 elementId = browser.MagicHelper.GetElementAttributeHash(parentElem.Css);
4193 else if (
true == browser.UsePuppeteer)
4194 elementId = browser.PuppeteerClient.GetElementAttributeHash(parentElem.Css).Execute();
4200 if (SelectClickHelper._lastParentNodeId != elementId &&
4201 (clickType == SelectClickType.SelectNext || clickType == SelectClickType.SelectNextWithWrap))
4203 SelectClickHelper._sequentialSelectIndices[elementId] = -1;
4206 SelectClickHelper._lastParentNodeId = elementId;
4208 if (
false == browser.BrowserSettings.UseHeadless && (
true == browser.BrowserSettings.UseHardware ||
true == GPAL.GPALSettings.UseHardware))
4212 case SelectClickType.SelectRandom:
4213 Random rand =
new Random();
4214 int randomIndex = rand.Next(0, options.Count);
4216 SelectClickHelper._sequentialSelectIndices[elementId] = randomIndex;
4217 HardwareClick(browser, parentElem, ClickType.LeftClick, modifierKeys, browser.CurrentUOW.CurrentSelector);
4218 HardwareHelper.SendKey(GPAL.VK_HOME);
4219 for (
int i = 0; i < randomIndex; i++)
4222 HardwareHelper.SendKey(GPAL.VK_DOWN);
4224 HardwareHelper.SendKey(GPAL.VK_RETURN);
4227 case SelectClickType.SelectNext:
4228 case SelectClickType.SelectNextWithWrap:
4229 case SelectClickType.SelectPrevious:
4230 case SelectClickType.SelectPreviousWithWrap:
4233 int incOrDec = (clickType == SelectClickType.SelectPrevious ||
4234 clickType == SelectClickType.SelectPreviousWithWrap)
4237 bool shouldWrap = (clickType == SelectClickType.SelectNextWithWrap ||
4238 clickType == SelectClickType.SelectPreviousWithWrap);
4241 if (!SelectClickHelper._sequentialSelectIndices.TryGetValue(elementId, out
int currentIndex))
4244 SelectClickHelper._sequentialSelectIndices[elementId] = currentIndex;
4252 nextIndex = (currentIndex + incOrDec % options.Count + options.Count) % options.Count;
4257 nextIndex = currentIndex + incOrDec;
4260 else if (nextIndex >= options.Count)
4261 nextIndex = options.Count - 1;
4265 HardwareClick(browser, parentElem, ClickType.LeftClick, modifierKeys,
4266 browser.CurrentUOW.CurrentSelector);
4269 HardwareHelper.SendKey(GPAL.VK_HOME);
4275 for (
int i = 0; i < nextIndex; i++)
4278 HardwareHelper.SendKey(GPAL.VK_DOWN);
4285 HardwareHelper.SendKey(GPAL.VK_END);
4288 int stepsUp = options.Count - 1 - nextIndex;
4289 for (
int i = 0; i < stepsUp; i++)
4292 HardwareHelper.SendKey(GPAL.VK_UP);
4297 HardwareHelper.SendKey(GPAL.VK_RETURN);
4300 SelectClickHelper._sequentialSelectIndices[elementId] = nextIndex;
4305 HardwareClick(browser, parentElem, ClickType.LeftClick, modifierKeys, browser.CurrentUOW.CurrentSelector);
4308 int index = Int32.Parse(webElement.GetAttribute(
"index"));
4310 SelectClickHelper._sequentialSelectIndices[elementId] = index;
4311 HardwareHelper.SendKey(GPAL.VK_HOME);
4312 for (
int cnt = 0; cnt < index; cnt++)
4315 HardwareHelper.SendKey(GPAL.VK_DOWN);
4317 HardwareHelper.SendKey(GPAL.VK_RETURN);
4321 else if (
true == browser.UseOttoMagic)
4325 case SelectClickType.SelectRandom:
4326 Random rand =
new Random();
4327 int randomIndex = rand.Next(0, options.Count);
4328 selectedValue = options[randomIndex].GetAttribute(
"value");
4330 SelectClickHelper._sequentialSelectIndices[elementId] = randomIndex;
4331 browser.MagicHelper.SelectClick(parentElem.Css, selectedValue);
4334 case SelectClickType.SelectNext:
4335 case SelectClickType.SelectNextWithWrap:
4336 case SelectClickType.SelectPrevious:
4337 case SelectClickType.SelectPreviousWithWrap:
4340 int incOrDec = (clickType == SelectClickType.SelectPrevious ||
4341 clickType == SelectClickType.SelectPreviousWithWrap)
4345 bool shouldWrap = (clickType == SelectClickType.SelectNextWithWrap ||
4346 clickType == SelectClickType.SelectPreviousWithWrap);
4349 if (!SelectClickHelper._sequentialSelectIndices.TryGetValue(elementId, out
int currentIndex))
4352 SelectClickHelper._sequentialSelectIndices[elementId] = currentIndex;
4360 nextIndex = (currentIndex + incOrDec % options.Count + options.Count) % options.Count;
4365 nextIndex = currentIndex + incOrDec;
4368 else if (nextIndex >= options.Count)
4369 nextIndex = options.Count - 1;
4373 SelectClickHelper._sequentialSelectIndices[elementId] = nextIndex;
4374 selectedValue = options[nextIndex].GetAttribute(
"value");
4375 browser.MagicHelper.SelectClick(parentElem.Css, selectedValue);
4380 int index = Int32.Parse(webElement.GetAttribute(
"index"));
4382 SelectClickHelper._sequentialSelectIndices[elementId] = index;
4383 browser.MagicHelper.SelectClick(parentElem.Css, webElement.Value);
4387 else if (
true == browser.UsePuppeteer)
4391 case SelectClickType.SelectRandom:
4392 Random rand =
new Random();
4393 int randomIndex = rand.Next(0, options.Count);
4394 selectedValue = options[randomIndex].GetAttribute(
"value");
4396 SelectClickHelper._sequentialSelectIndices[elementId] = randomIndex;
4398 ((PuppeteerClient)browser.PuppeteerClient).SelectClick((dynamic)parentElem.ElementHandle).WithSelectValue(selectedValue).Execute();
4401 case SelectClickType.SelectNext:
4402 case SelectClickType.SelectNextWithWrap:
4403 case SelectClickType.SelectPrevious:
4404 case SelectClickType.SelectPreviousWithWrap:
4407 int incOrDec = (clickType == SelectClickType.SelectPrevious ||
4408 clickType == SelectClickType.SelectPreviousWithWrap)
4412 bool shouldWrap = (clickType == SelectClickType.SelectNextWithWrap ||
4413 clickType == SelectClickType.SelectPreviousWithWrap);
4416 if (!SelectClickHelper._sequentialSelectIndices.TryGetValue(elementId, out
int currentIndex))
4419 SelectClickHelper._sequentialSelectIndices[elementId] = currentIndex;
4427 nextIndex = (currentIndex + incOrDec % options.Count + options.Count) % options.Count;
4432 nextIndex = currentIndex + incOrDec;
4435 else if (nextIndex >= options.Count)
4436 nextIndex = options.Count - 1;
4440 SelectClickHelper._sequentialSelectIndices[elementId] = nextIndex;
4441 selectedValue = options[nextIndex].GetAttribute(
"value");
4443 ((PuppeteerClient)browser.PuppeteerClient).SelectClick((dynamic)parentElem.ElementHandle).WithSelectValue(selectedValue).Execute();
4448 int index = Int32.Parse(webElement.GetAttribute(
"index"));
4449 SelectClickHelper._sequentialSelectIndices[elementId] = index;
4452 ((PuppeteerClient)browser.PuppeteerClient).SelectClick((dynamic)parentElem.ElementHandle).WithSelectIndex(index).Execute();
4456 else if (
true == browser.UseSelenium)
4459 if (Enums.InteractionType.JavaScript == selector.InteractionType || browser.BrowserSettings.UseJavaScript)
4463 case SelectClickType.SelectRandom:
4464 Random rand =
new Random();
4465 int randomIndex = rand.Next(0, options.Count);
4467 SelectClickHelper._sequentialSelectIndices[elementId] = randomIndex;
4468 selectedValue = options[randomIndex].GetAttribute(
"value");
4469 BrowserHelper.ExecuteJavaScript(browser,
"arguments[0].value = arguments[1];", parentElem, selectedValue);
4472 case SelectClickType.SelectNext:
4473 case SelectClickType.SelectNextWithWrap:
4474 case SelectClickType.SelectPrevious:
4475 case SelectClickType.SelectPreviousWithWrap:
4478 int incOrDec = (clickType == SelectClickType.SelectPrevious ||
4479 clickType == SelectClickType.SelectPreviousWithWrap)
4483 bool shouldWrap = (clickType == SelectClickType.SelectNextWithWrap ||
4484 clickType == SelectClickType.SelectPreviousWithWrap);
4487 if (!SelectClickHelper._sequentialSelectIndices.TryGetValue(elementId, out
int currentIndex))
4490 SelectClickHelper._sequentialSelectIndices[elementId] = currentIndex;
4498 nextIndex = (currentIndex + incOrDec % options.Count + options.Count) % options.Count;
4503 nextIndex = currentIndex + incOrDec;
4506 else if (nextIndex >= options.Count)
4507 nextIndex = options.Count - 1;
4511 SelectClickHelper._sequentialSelectIndices[elementId] = nextIndex;
4512 selectedValue = options[nextIndex].GetAttribute(
"value");
4513 BrowserHelper.ExecuteJavaScript(browser,
"arguments[0].value = arguments[1];", parentElem, selectedValue);
4517 int index = Int32.Parse(webElement.WebElement.GetAttribute(
"index"));
4519 SelectClickHelper._sequentialSelectIndices[elementId] = index;
4520 BrowserHelper.ExecuteJavaScript(browser,
"arguments[0].value = arguments[1].value", parentElem, webElement.WebElement);
4527 case SelectClickType.SelectRandom:
4528 Random rand =
new Random();
4529 int randomIndex = rand.Next(0, options.Count);
4531 SelectClickHelper._sequentialSelectIndices[elementId] = randomIndex;
4532 elem.SelectByIndex(randomIndex);
4535 case SelectClickType.SelectNext:
4536 case SelectClickType.SelectNextWithWrap:
4537 case SelectClickType.SelectPrevious:
4538 case SelectClickType.SelectPreviousWithWrap:
4541 int incOrDec = (clickType == SelectClickType.SelectPrevious ||
4542 clickType == SelectClickType.SelectPreviousWithWrap)
4546 bool shouldWrap = (clickType == SelectClickType.SelectNextWithWrap ||
4547 clickType == SelectClickType.SelectPreviousWithWrap);
4550 if (!SelectClickHelper._sequentialSelectIndices.TryGetValue(elementId, out
int currentIndex))
4553 SelectClickHelper._sequentialSelectIndices[elementId] = currentIndex;
4561 nextIndex = (currentIndex + incOrDec % options.Count + options.Count) % options.Count;
4566 nextIndex = currentIndex + incOrDec;
4569 else if (nextIndex >= options.Count)
4570 nextIndex = options.Count - 1;
4574 elem.SelectByIndex(nextIndex);
4575 SelectClickHelper._sequentialSelectIndices[elementId] = nextIndex;
4580 int index = Int32.Parse(webElement.GetAttribute(
"index"));
4582 SelectClickHelper._sequentialSelectIndices[elementId] = index;
4583 elem.SelectByValue(webElement.GetAttribute(
"value"));
4588 catch (GPALException)
4592 catch (Exception ex)
4594 if (!GPAL.NoFallbackRecoveryActions)
4596 GPAL.PublishSimpleEvent(GPALEventType.EXCEPTION, $
"Unable to Select using [{selector.InteractionType}]. Falling back on JavaScript.", webElement, GPALObjectType.Other, ex);
4597 BrowserHelper.ExecuteJavaScript(browser,
"arguments[0].value = arguments[1].value",
true == browser.UseSelenium ? parentElem : ((GPALElement)parentElem).WebElement, webElement.WebElement);
4601 GPAL.PublishSimpleEvent(GPALEventType.EXCEPTION, $
"Unable to Select using [{selector.InteractionType}].", webElement, GPALObjectType.Other, ex);
4604 selectedValue =
null != options ? options[SelectClickHelper._sequentialSelectIndices[elementId]].GetAttribute(
"value") : webElement.GetAttribute(
"value");
4605 string selectedText =
null != options ? options[SelectClickHelper._sequentialSelectIndices[elementId]].Text : webElement.Text;
4606 GPAL.PublishSimpleEvent(GPALEventType.INFO, $
"[{selector.InteractionType}] selected [{selectedText}] value [{selectedValue}] index [{SelectClickHelper._sequentialSelectIndices[elementId]}]");
4610 GPAL.PublishSimpleEvent(GPALEventType.WARNING, $
"Unable to click Parent Select for Option using [{selector.InteractionType}]. Clicking element [{selector.Name}].", webElement, GPALObjectType.Other);
4611 webElement.Click(modifierKeys);
4626 public static void Click(Browser browser, Selector selector, GPALElement webElement, ClickType clickType, ModifierKeys modifierKeys,
bool trusted =
false)
4628 bool dontPublish =
false;
4632 GPALElement resolved =
null;
4634 if (
null != webElement.ImageMatch)
4636 resolved = webElement;
4637 webElement = webElement.ImageMatch;
4640 ScrollIntoView(browser, webElement, selector.InteractionType);
4647 if (
false == browser.BrowserSettings.UseHeadless &&
false == browser.BrowserSettings.HiddenDesktop
4648 && (Enums.InteractionType.Hardware == selector.InteractionType
4649 ||
true == browser.BrowserSettings.UseHardware ||
true == GPAL.GPALSettings.UseHardware)
4654 HardwareClick(browser, webElement, clickType, modifierKeys, selector,
true);
4657 else if (
"GPALElement" == webElement.TagName)
4662 Point aim = ToViewportPoint(browser, webElement, selector);
4664 if (
null != resolved &&
false == PointIsClear(browser, resolved, selector, ref aim) &&
false == GPAL.NoFallbackRecoveryActions)
4665 ClickCoveredElement(browser, resolved, clickType, modifierKeys);
4667 Click(browser, aim, clickType, modifierKeys);
4671 dontPublish = browser.UseOttoMagic;
4673 else if (
true == browser.UseOttoMagic)
4675 bool clicked =
false;
4679 if (
true == trusted && ClickType.LeftClick == clickType && ModifierKeys.NONE == modifierKeys)
4681 Rectangle rect = browser.MagicHelper.GetBoundingClientRect(webElement.Css);
4683 Point aim = (0 == selector.OffsetX && 0 == selector.OffsetY)
4684 ?
new Point(rect.X + rect.Width / 2, rect.Y + rect.Height / 2)
4685 :
new Point(rect.X + selector.OffsetX, rect.Y + selector.OffsetY);
4687 clicked = 0 < rect.Width && 0 < rect.Height &&
true == browser.MagicHelper.TrustedLeftClick(aim.X, aim.Y);
4689 if (
false == clicked)
4690 GPAL.PublishSimpleEvent(GPALEventType.WARNING, $
"[Trusted][{clickType}] on selector [{selector.Name}] was not made, falling back to [{clickType}].", browser, GPALObjectType.Browser);
4693 if (
false == clicked)
4695 if (modifierKeys != ModifierKeys.NONE)
4696 browser.MagicHelper.PressModifierKey(modifierKeys);
4700 case ClickType.LeftClick:
4701 browser.MagicHelper.LeftClick(webElement.Css);
4703 case ClickType.LeftDoubleClick:
4704 browser.MagicHelper.LeftDoubleClick(webElement.Css);
4706 case ClickType.MiddleClick:
4707 browser.MagicHelper.MiddleClick(webElement.Css);
4709 case ClickType.RightClick:
4710 browser.MagicHelper.RightClick(webElement.Css);
4714 if (modifierKeys != ModifierKeys.NONE)
4715 browser.MagicHelper.ReleaseModifierKey(modifierKeys);
4718 else if (Enums.InteractionType.JavaScript == selector.InteractionType ||
true == browser.BrowserSettings.UseJavaScript)
4720 if (
false == JavaScriptClick(browser, webElement, clickType, modifierKeys) &&
false == GPAL.NoFallbackRecoveryActions)
4722 if (
true == browser.UseSelenium)
4724 GPAL.PublishSimpleEvent(GPALEventType.ERROR, $
"JavaScript click failed on selector [{selector.Name}], falling back to selenium click.", browser, GPALObjectType.Browser);
4725 SeleniumClick(browser, webElement, clickType, modifierKeys);
4729 GPAL.PublishSimpleEvent(GPALEventType.ERROR, $
"JavaScript click failed on selector [{selector.Name}], falling back to Puppeteer click.", browser, GPALObjectType.Browser);
4730 PuppeteerClick(browser, webElement, clickType, modifierKeys);
4735 else if (
true == browser.UsePuppeteer)
4737 PuppeteerClick(browser, webElement, clickType, modifierKeys);
4741 SeleniumClick(browser, webElement, clickType, modifierKeys);
4743 if (
false == dontPublish)
4744 GPAL.PublishSimpleEvent(GPALEventType.INFO, $
"Element [{webElement.TagName}] for selector [{selector.Name}] [{selector.InteractionType.ToString()}][{clickType.ToString()}] clicked with modifiers [{GetEnumNames(modifierKeys)}].", browser, GPALObjectType.Browser);
4747 catch (GPALException)
4751 catch (Exception ex)
4753 GPAL.PublishSimpleEvent(GPALEventType.EXCEPTION, $
"Unable to [{clickType.ToString()}] click element [{webElement.TagName}] for selector [{selector.Name}]", browser, GPALObjectType.Browser, ex);
4769 public static void PuppeteerClick(Browser browser, GPALElement webElement, ClickType clickType, ModifierKeys modifierKeys = ModifierKeys.NONE)
4773 case ClickType.LeftClick:
4774 browser.PuppeteerClient.LeftClick(webElement).WithModifiers(modifierKeys).Execute();
4776 case ClickType.LeftDoubleClick:
4777 browser.PuppeteerClient.LeftDoubleClick(webElement).WithModifiers(modifierKeys).Execute();
4779 case ClickType.MiddleClick:
4780 browser.PuppeteerClient.MiddleClick(webElement).WithModifiers(modifierKeys).Execute();
4782 case ClickType.RightClick:
4783 browser.PuppeteerClient.RightClick(webElement).WithModifiers(modifierKeys).Execute();
4795 public static void SeleniumClick(Browser browser, GPALElement webElement, ClickType clickType, ModifierKeys modifierKeys = ModifierKeys.NONE)
4799 Actions action =
new Actions(browser.BrowserDriver);
4800 string errorMessage = $
"Failed to {clickType.ToString()} element with text [{webElement.Text}]";
4808 case ClickType.LeftClick:
4809 if (ModifierKeys.NONE == modifierKeys)
4811 webElement.iWebElement.Click();
4815 if (modifierKeys.HasFlag(ModifierKeys.Alt))
4816 action.KeyDown(OpenQA.Selenium.Keys.LeftAlt).KeyDown(OpenQA.Selenium.Keys.Alt);
4817 if (modifierKeys.HasFlag(ModifierKeys.Control))
4818 action.KeyDown(OpenQA.Selenium.Keys.LeftControl).KeyDown(OpenQA.Selenium.Keys.Control);
4819 if (modifierKeys.HasFlag(ModifierKeys.Shift))
4820 action.KeyDown(OpenQA.Selenium.Keys.LeftShift).KeyDown(OpenQA.Selenium.Keys.Shift);
4821 if (modifierKeys.HasFlag(ModifierKeys.Windows))
4822 action.KeyDown(OpenQA.Selenium.Keys.Meta);
4824 action.Click(webElement.iWebElement);
4826 if (modifierKeys.HasFlag(ModifierKeys.Alt))
4827 action.KeyUp(OpenQA.Selenium.Keys.LeftAlt).KeyUp(OpenQA.Selenium.Keys.Alt);
4828 if (modifierKeys.HasFlag(ModifierKeys.Control))
4829 action.KeyUp(OpenQA.Selenium.Keys.LeftControl).KeyUp(OpenQA.Selenium.Keys.Control);
4830 if (modifierKeys.HasFlag(ModifierKeys.Shift))
4831 action.KeyUp(OpenQA.Selenium.Keys.LeftShift).KeyUp(OpenQA.Selenium.Keys.Shift);
4832 if (modifierKeys.HasFlag(ModifierKeys.Windows))
4833 action.KeyUp(OpenQA.Selenium.Keys.Meta);
4835 ModifierKeys unsupported = modifierKeys & (ModifierKeys.Application | ModifierKeys.ScrollLock);
4836 if (ModifierKeys.NONE != unsupported)
4837 GPAL.PublishSimpleEvent(GPALEventType.WARNING, $
"LeftClick: [{Browser.GetModifierKeys(unsupported)}] is not supported via Selenium/WebDriver Actions and was ignored.", browser, GPALObjectType.Browser);
4839 action.Build().Perform();
4842 case ClickType.LeftDoubleClick:
4843 action.DoubleClick(webElement.iWebElement).Perform();
4845 case ClickType.MiddleClick:
4850 PointerInputDevice mouse =
new PointerInputDevice(PointerKind.Mouse,
"default mouse");
4851 ActionSequence middleClick =
new ActionSequence(mouse);
4853 middleClick.AddAction(mouse.CreatePointerMove(webElement.iWebElement, 0, 0, TimeSpan.Zero));
4854 middleClick.AddAction(mouse.CreatePointerDown(MouseButton.Middle));
4855 middleClick.AddAction(mouse.CreatePointerUp(MouseButton.Middle));
4857 ((IActionExecutor)browser.BrowserDriver).PerformActions(
new List<ActionSequence> { middleClick });
4859 if (ModifierKeys.NONE != modifierKeys)
4860 GPAL.PublishSimpleEvent(GPALEventType.WARNING, $
"Selenium cannot press [{Browser.GetModifierKeys(modifierKeys)}] with a middle click.", browser, GPALObjectType.Browser);
4864 case ClickType.RightClick:
4865 action.ContextClick(webElement.iWebElement).Perform();
4870 catch (StaleElementReferenceException sereex)
4872 GPAL.PublishSimpleEvent(GPALEventType.EXCEPTION, errorMessage, webElement, GPALObjectType.Other, sereex);
4873 retry = ResolveWebElementIssue(browser, browser.CurrentUOW, ref webElement.iWebElement, sereex, ref retries, errorMessage);
4876 catch (ElementClickInterceptedException ecieex)
4878 GPAL.PublishSimpleEvent(GPALEventType.EXCEPTION, errorMessage, webElement, GPALObjectType.Other, ecieex);
4879 if (
false == GPAL.NoFallbackRecoveryActions)
4880 retry = JavaScriptClick(browser, webElement, clickType, modifierKeys);
4885 catch (ElementNotInteractableException enieex)
4887 GPAL.PublishSimpleEvent(GPALEventType.EXCEPTION, errorMessage, webElement, GPALObjectType.Other, enieex);
4888 retry = ResolveWebElementIssue(browser, browser.CurrentUOW, ref webElement.iWebElement, enieex, ref retries, errorMessage);
4891 catch (GPALException)
4895 catch (Exception ex)
4899 GPAL.PublishSimpleEvent(GPALEventType.EXCEPTION, errorMessage, webElement, GPALObjectType.Other, ex);
4901 }
while (retry && 0 < retries);
4903 if (retries == 0 &&
false == GPAL.NoFallbackRecoveryActions)
4905 GPAL.PublishSimpleEvent(GPALEventType.INFO,
"Falling back on javascript click", webElement, GPALObjectType.Other);
4906 JavaScriptClick(browser, webElement, clickType, modifierKeys);
4917 public static bool JavaScriptClick(Browser browser, GPALElement webElement, ClickType clickType, ModifierKeys modifierKeys = ModifierKeys.NONE)
4919 string clickString = clickType
switch
4921 ClickType.LeftClick =>
"click",
4922 ClickType.LeftDoubleClick =>
"dblclick",
4923 ClickType.RightClick =>
"contextmenu",
4924 ClickType.MiddleClick =>
"auxclick",
4927 int button = (
true ==
"contextmenu".Equals(clickString) ? 2 : 0);
4928 bool shiftPressed = 0 != (modifierKeys & ModifierKeys.Shift);
4929 bool controlPressed = 0 != (modifierKeys & ModifierKeys.Control);
4930 bool altPressed = 0 != (modifierKeys & ModifierKeys.Alt);
4933 var random =
new Random();
4935 var clickAreaWidth = (float)webElement.BoundingRect.Width * (1f - 2 * margin);
4936 var clickAreaHeight = (float)webElement.BoundingRect.Height * (1f - 2 * margin);
4937 var x = (float)webElement.BoundingRect.X + (
float)webElement.BoundingRect.Width * margin + (float)random.NextDouble() * clickAreaWidth;
4938 var y = (float)webElement.BoundingRect.Y + (
float)webElement.BoundingRect.Height * margin + (float)random.NextDouble() * clickAreaHeight;
4940 if (
true == browser.UseSelenium)
4942 IWebDriver browserDriver = browser.BrowserDriver;
4944 string clickScript =
$@"
4945 var theEvent = document.createEvent(""MouseEvent"");
4946 theEvent.initMouseEvent(""{clickString}"", true, true, window, 0, 0, 0, {x}, {y}, {controlPressed.ToString().ToLower()}, {altPressed.ToString().ToLower()}, {shiftPressed.ToString().ToLower()}, false, 0, null);
4947 arguments[0].dispatchEvent(theEvent);
4954 JavaScriptFocus(browser, webElement);
4956 if (
false == browser.BrowserSettings.UseHeadless)
4957 HardwareHelper.SendKey(GPAL.VK_F10, ModifierKeys.Shift);
4959 new Actions(browserDriver).ContextClick((IWebElement)webElement.WebElement).Perform();
4962 BrowserHelper.ExecuteJavaScript(browser, clickScript, webElement.WebElement);
4964 catch (GPALException)
4968 catch (Exception ex)
4970 GPAL.PublishSimpleEvent(GPALEventType.EXCEPTION, $
"Failed to [{clickString}] element [{webElement.Text}]", webElement, GPALObjectType.Other, ex);
4974 else if (
true == browser.UsePuppeteer)
4976 string sessionId = browser.PuppeteerCommunicator.GetEffectiveSessionId();
4978 string selector = webElement.Css;
4980 string clickScript =
null;
4986 const sel = `{selector.Replace("'", "\\'")}`;
4988 const el = document.querySelector(sel) || document.evaluate(sel, document, null, XPathResult.FIRST_ORDERED_NODE_TYPE, null).singleNodeValue;
4989 if (!el) return false;
4991 const rect = el.getBoundingClientRect();
4992 const cx = rect.left + rect.width / 2 + window.scrollX;
4993 const cy = rect.top + rect.height / 2 + window.scrollY;
4995 const opts = {{ bubbles: true, cancelable: true, clientX: cx, clientY: cy, view: window }};
4997 // === LEFT CLICK (or double) ===
4998 if ({(clickType == ClickType.LeftClick || clickType == ClickType.LeftDoubleClick).ToString().ToLower()}) {{
4999 const count = {(clickType == ClickType.LeftDoubleClick ? 2 : 1)};
5000 for (let i = 0; i < count; i++) {{
5001 el.dispatchEvent(new MouseEvent('mousedown', {{...opts, button: 0, buttons: 1}}));
5002 el.dispatchEvent(new MouseEvent('mouseup', {{...opts, button: 0, buttons: 0}}));
5003 el.dispatchEvent(new MouseEvent('click', {{...opts, button: 0, detail: i+1}}));
5004 if (i === 0) Atomics.wait(new Int32Array(new SharedArrayBuffer(4)), 0, 0, 80); // ~80ms between clicks
5009 // === MIDDLE CLICK (eBay pop-under) ===
5010 if ({(clickType == ClickType.MiddleClick).ToString().ToLower()}) {{
5011 el.dispatchEvent(new MouseEvent('mousedown', {{...opts, button: 1, buttons: 4}}));
5012 el.dispatchEvent(new MouseEvent('mouseup', {{...opts, button: 1, buttons: 0}}));
5013 el.dispatchEvent(new MouseEvent('click', {{...opts, button: 1}}));
5017 // === RIGHT CLICK (context menu) ===
5018 if ({(clickType == ClickType.RightClick).ToString().ToLower()}) {{
5019 el.dispatchEvent(new MouseEvent('mousedown', {{...opts, button: 2, buttons: 2}}));
5020 el.dispatchEvent(new MouseEvent('mouseup', {{...opts, button: 2, buttons: 0}}));
5021 el.dispatchEvent(new MouseEvent('contextmenu', {{...opts, button: 2}}));
5032 browser.PuppeteerCommunicator.SendCommand<
object>(DevToolsMethods.RuntimeEvaluate,
new
5034 expression = clickScript,
5035 returnByValue =
true
5036 }, sessionId).GetAwaiter().GetResult();
5083 internal static void RefreshLocation(Browser browser, GPALElement element)
5087 Rectangle live =
new Rectangle(0, 0, 0, 0);
5089 if (
true == browser.UsePuppeteer && 0 != element.ElementBackendNodeId)
5095 live = browser.PuppeteerClient.GetBoundingClientRect(element.ElementBackendNodeId.ToString()).Execute<Rectangle>();
5101 bool inPuppeteerFrame = browser.CurrentUOW?.ContextPath?.Any(context => ElementType.IFrame == context.elementType) ??
false;
5103 if (0 < live.Width && 0 < live.Height &&
false == inPuppeteerFrame)
5104 live.Offset(browser.PuppeteerClient.WindowPageOffsetX().Execute<
int>(),
5105 browser.PuppeteerClient.WindowPageOffsetY().Execute<
int>());
5107 else if (
true == browser.UseOttoMagic)
5109 live = browser.MagicHelper.GetBoundingClientRect(element.Css);
5116 bool inFrame = browser.CurrentUOW?.ContextPath?.Any(context => ElementType.IFrame == context.elementType) ??
false;
5118 if (0 < live.Width && 0 < live.Height &&
false == inFrame)
5119 live.Offset(browser.MagicHelper.WindowPageOffsetX(), browser.MagicHelper.WindowPageOffsetY());
5121 else if (
null != element.WebElement)
5122 live =
new Rectangle(element.WebElement.Location, element.WebElement.Size);
5124 if (0 < live.Width && 0 < live.Height)
5126 element.Location = live.Location;
5127 element.Size = live.Size;
5128 element.BoundingRect =
new ClientRectangle(element.Location, element.Size);
5133 GPAL.PublishSimpleEvent(GPALEventType.WARNING, $
"Could not re-read where [{element.TagName}][{element.Css}] is, so it is still where it was when it was found", browser, GPALObjectType.Browser);
5135 catch (GPALException)
5139 catch (Exception ex)
5141 GPAL.PublishSimpleEvent(GPALEventType.EXCEPTION, $
"Could not re-read where [{element.TagName}] is, using the reading from when it was found", browser, GPALObjectType.Browser, ex);
5157 internal static bool PointHitsElement(Browser browser, GPALElement element, Point screenPoint)
5159 bool retVal =
false;
5164 Rectangle content = GetWindowRectangle(browser);
5165 int x = screenPoint.X - content.X;
5166 int y = screenPoint.Y - content.Y;
5168 if (
true == browser.UsePuppeteer)
5169 retVal = browser.BrowserSettings.PuppeteerCommunicator.ElementFromPoint(element, x, y).GetAwaiter().GetResult();
5170 else if (
true == browser.UseOttoMagic)
5171 retVal = browser.MagicHelper.ElementFromPoint(element.Css, x, y);
5174 var result = BrowserHelper.ExecuteJavaScriptObj(
5175 "var elem = arguments[0]; " +
5176 "var hit = document.elementFromPoint(arguments[1], arguments[2]); " +
5177 "return hit === elem || elem.contains(hit);", browser, element.WebElement, x, y);
5179 retVal =
null != result &&
true == Convert.ToBoolean(result);
5184 catch (GPALException)
5188 catch (Exception ex)
5190 GPAL.PublishSimpleEvent(GPALEventType.EXCEPTION, $
"Could not ask what is at [{screenPoint.X}, {screenPoint.Y}] for [{element.TagName}]", browser, GPALObjectType.Browser, ex);
5201 private static readonly
int[] hitTestInsets = { 24, 40, 60, 12 };
5205 private static readonly
float[] hitTestFractions = { 0.75f, 0.50f, 0.25f, 0.10f };
5207 public static bool IsVisibleInViewport(Browser browser, GPALElement element)
5209 bool retval =
false;
5211 if (
true == browser.UsePuppeteer)
5212 retval = browser.PuppeteerClient.IsVisibleInViewport(element).Execute<
bool>();
5213 else if (
true == browser.UseOttoMagic)
5214 retval = browser.MagicHelper.IsVisibleInViewport(element.Css);
5217 IWebDriver browserDriver = browser.BrowserDriver;
5219 var result = BrowserHelper.ExecuteJavaScriptObj(
5220 "var elem = arguments[0]; " +
5221 "const rect = elem.getBoundingClientRect(); " +
5223 " rect.top >= 0 && " +
5224 " rect.left >= 0 && " +
5225 " rect.bottom <= (window.innerHeight || document.documentElement.clientHeight) && " +
5226 " rect.right <= (window.innerWidth || document.documentElement.clientWidth) " +
5227 ");", browser, element.WebElement);
5230 retval = (bool)result;
5241 public static bool IsEndOfPage(Browser browser, GPALElement element)
5245 if (
true == browser.UsePuppeteer)
5246 retval = browser.PuppeteerClient.IsEndOfPage().Execute<
bool>();
5247 else if (
true == browser.UseOttoMagic)
5248 retval = browser.MagicHelper.IsEndOfPage();
5251 IWebDriver browserDriver = browser.BrowserDriver;
5254 var result = BrowserHelper.ExecuteJavaScriptObj(
"if ((window.innerHeight + window.scrollY) >= document.documentElement.scrollHeight) { return true; } else { return false; }", browser,
null);
5257 retval = (bool)result;
5271 private static bool SettledIsVisibleInViewport(Browser browser, GPALElement element)
5273 bool visible = IsVisibleInViewport(browser, element);
5274 int checksLeft = VisibilitySettleChecks;
5276 while (
false == visible && 0 < checksLeft--)
5278 Thread.Sleep(VisibilitySettleMs);
5279 visible = IsVisibleInViewport(browser, element);
5300 private static bool IsBiggerThanViewport(Size elementSize, Rectangle viewport)
5302 return elementSize.Width > viewport.Width || elementSize.Height > viewport.Height;
5304 internal static bool ScrollUntilVisible(Browser browser, GPALElement element)
5306 bool visible = SettledIsVisibleInViewport(browser, element);
5310 if (
false == visible &&
true == IsBiggerThanViewport(element.Size, GetWindowRectangle(browser)))
5313 if (
false == visible)
5315 int pagesLeft = MaxScrollPages;
5317 _ = browser.PageTop;
5318 visible = SettledIsVisibleInViewport(browser, element);
5322 while (
false == visible && 0 < pagesLeft-- &&
false == IsEndOfPage(browser, element))
5324 _ = browser.PageDown;
5325 visible = SettledIsVisibleInViewport(browser, element);
5328 if (
false == visible)
5330 _ = browser.PageTop;
5331 GPAL.PublishSimpleEvent(GPALEventType.WARNING, $
"Could not scroll element [{element.TagName}][{element.Css}] into the viewport. Left the page at the top and continuing.", browser, GPALObjectType.Browser);
5343 private static IntPtr GetWindowHandle(BrowserType browserType)
5345 IntPtr hWnd = IntPtr.Zero;
5346 Process[] processes =
null;
5348 if (BrowserType.Chrome == browserType)
5349 processes = Process.GetProcessesByName(
"chrome");
5350 else if (BrowserType.Edge == browserType)
5351 processes = Process.GetProcessesByName(
"msedge");
5352 else if (BrowserType.FireFox == browserType)
5353 processes = Process.GetProcessesByName(
"firefox");
5355 if (processes.Length > 0)
5357 foreach (Process proc
in processes)
5359 if (proc.MainWindowHandle == IntPtr.Zero)
5363 hWnd = proc.MainWindowHandle;
5386 [ThreadStatic]
private static bool _lastPlacementQuestionable;
5390 [ThreadStatic]
private static string _lastClampedPoint;
5393 [ThreadStatic]
private static Rectangle _lastContentArea;
5399 [ThreadStatic]
private static Rectangle _lastOuterWindow;
5404 private static readonly
object hardwareMouse =
new object();
5406 public static void HardwareClick(Browser browser, dynamic element, ClickType clickType, ModifierKeys modifierKeys, Selector selector,
bool isClickable =
false)
5408 Rectangle rect, iframeRect =
new Rectangle(0, 0, 0, 0);
5409 bool clicked =
false;
5410 _lastPlacementQuestionable =
false;
5411 _lastClampedPoint =
null;
5414 lock (hardwareMouse)
5417 BrowserHelper.TopBrowser(browser,
false);
5419 if (ModifierKeys.NONE != modifierKeys)
5420 HardwareHelper.PressModifierKey(modifierKeys);
5424 rect = HardwareMoveTo(browser, element, selector);
5432 Rectangle browserRect = _lastContentArea;
5433 bool insideBrowser = 0 < rect.Width && 0 < rect.Height
5434 && rect.X > browserRect.Left && rect.X < browserRect.Right
5435 && rect.Y > browserRect.Top && rect.Y < browserRect.Bottom;
5437 if (
"GPALElement" == element.TagName ||
true == insideBrowser)
5440 HardwareHelper.HardwareClick(rect.X, rect.Y, clickType);
5447 _lastPlacementQuestionable =
true;
5449 if (
false == GPAL.NoFallbackRecoveryActions)
5451 GPAL.PublishSimpleEvent(GPALEventType.WARNING, $
"Element [{element.TagName}] for selector [{selector?.Name}] computed to [{rect.X}, {rect.Y}, {rect.Width}, {rect.Height}], outside the browser window [{browserRect.Left}, {browserRect.Top}, {browserRect.Right}, {browserRect.Bottom}]. No hardware click, falling back on javascript click.", browser, GPALObjectType.Browser);
5453 if (ModifierKeys.NONE != modifierKeys)
5454 HardwareHelper.ReleaseModifierKeys(modifierKeys);
5456 if (
true == browser.UseOttoMagic)
5457 browser.MagicHelper.LeftClick(element.Css);
5458 else if (
true == browser.UsePuppeteer)
5459 browser.PuppeteerClient.LeftClick(element).Execute();
5461 JavaScriptClick(browser, element, clickType, modifierKeys);
5466 GPAL.PublishSimpleEvent(GPALEventType.WARNING,
"Unable to get screen coordinates for webelement, continuing.", browser, GPALObjectType.Browser);
5477 $
"Element [{element.TagName}] for selector [{selector.Name}] [{selector.InteractionType.ToString()}][{clickType.ToString()}]{(true == clicked ? "" : " [NOT]
")} clicked{(null == _lastClampedPoint ? "" : " " + _lastClampedPoint)}.", browser, GPALObjectType.Browser);
5479 if (ModifierKeys.NONE != modifierKeys)
5480 HardwareHelper.ReleaseModifierKeys(modifierKeys);
5492 public static void DragAndDrop(
IBrowser browser, ModifierKeys modifierKeys = ModifierKeys.NONE)
5494 var driver = browser.BrowserDriver;
5495 Rectangle iframeRect =
new Rectangle(0, 0, 0, 0);
5497 List<GPALElement> matchedElements;
5499 foreach (
Selector selector
in ((
Browser)browser).CurrentUOW.WithSelectorList)
5501 if (SelectorType.Selector != selector.SelectorType)
5504 ReadOnlyCollection<GPALElement> elems = ElementHelper.FindWebElements((
Browser)browser, ((
Browser)browser).CurrentUOW, selector, out
bool matchedAll, out matchedElements);
5507 if (
true == selector.DeleteMe ||
null == elems || 0 == elems.Count)
5509 PublishToEventHandler((
Browser)browser, selector, elems?.ToList());
5513 if (
null != matchedElements)
5514 elems =
new ReadOnlyCollection<GPALElement>(matchedElements);
5520 if (
true == ((
Browser)browser).BrowserSettings.UseHardware || InteractionType.Hardware == selector.
InteractionType)
5526 lock (hardwareMouse)
5529 BrowserHelper.TopBrowser((
Browser)browser,
false);
5533 Rectangle rect = HardwareMoveTo((
Browser)browser, elem, selector);
5535 if (
"GPALElement" == elem.
TagName || (0 <= rect.Width && 0 <= rect.Height && 0 < rect.X && 0 < rect.Y))
5537 int endX = rect.X + selector.
DeltaX;
5538 int endY = rect.Y + selector.
DeltaY;
5540 HardwareHelper.MouseDown(rect.X, rect.Y, ClickType.LeftClick);
5541 HardwareHelper.MoveMouse(endX, endY, 4, 4, 0.5);
5542 HardwareHelper.MouseUp(endX, endY, ClickType.LeftClick);
5545 GPAL.PublishSimpleEvent(GPALEventType.WARNING, $
"Odd screen coordinates [{rect.X}, {rect.Y}, {rect.Width}, {rect.Height}] for hardware drag and drop, skipping element.", browser, GPALObjectType.Browser);
5552 else if (
true == browser.UseOttoMagic)
5556 else if (
true == browser.UsePuppeteer)
5558 browser.PuppeteerClient.DragAndDrop(elem.ElementHandle).WithDeltaX(selector.
DeltaX).WithDeltaY(selector.
DeltaY).WithOffsetX(selector.
OffsetX).WithOffsetY(selector.
OffsetY).Execute();
5560 else if (
true == browser.UseSelenium)
5567 if (BrowserType.FireFox == ((Browser)browser).BrowserSettings.BrowserType
5568 &&
"true" == elem.
WebElement?.GetAttribute(
"draggable"))
5571 new Actions(driver).ClickAndHold(elem.
WebElement).MoveByOffset(selector.
DeltaX, selector.
DeltaY).Release().Perform();
5574 if (++rowCount >= ((Browser)browser).CurrentUOW.WithAllThatMatch)
5593 public static bool JavaScriptDragAndDrop(Browser browser, GPALElement element,
int deltaX,
int deltaY,
int offsetX,
int offsetY)
5596 if (
null == element?.WebElement)
5599 ScrollIntoView(browser, element);
5602 string javaScript =
@"
5603 var src = arguments[0];
5604 var offsetX = arguments[1], offsetY = arguments[2];
5605 var deltaX = arguments[3], deltaY = arguments[4];
5607 var rect = src.getBoundingClientRect();
5608 var startX = rect.left + rect.width / 2 + offsetX;
5609 var startY = rect.top + rect.height / 2 + offsetY;
5610 var endX = startX + deltaX;
5611 var endY = startY + deltaY;
5613 var target = document.elementFromPoint(endX, endY);
5616 return 'nothing at drop point ' + endX + ',' + endY;
5618 var dt = new DataTransfer();
5620 function fire(type, el, x, y) {
5621 el.dispatchEvent(new DragEvent(type, {
5622 bubbles: true, cancelable: true, composed: true,
5623 clientX: x, clientY: y, dataTransfer: dt
5627 fire('dragstart', src, startX, startY);
5628 fire('dragenter', target, endX, endY);
5629 fire('dragover', target, endX, endY);
5630 fire('drop', target, endX, endY);
5631 fire('dragend', src, endX, endY);
5636 string result = BrowserHelper.ExecuteJavaScript(browser, javaScript, element.WebElement, offsetX, offsetY, deltaX, deltaY);
5638 if (
"ok" != result?.Trim())
5640 GPAL.PublishSimpleEvent(GPALEventType.WARNING, $
"HTML5 drag and drop did not complete for selector [{element.Selector?.Name}] [{result}].", browser, GPALObjectType.Browser);
5655 public static void JavaScriptHover(Browser browser, GPALElement element)
5658 if (
null == element.WebElement)
5661 ScrollIntoView(browser, element);
5664 String javaScript =
"var evObj = document.createEvent('MouseEvents');"
5665 +
"evObj.initMouseEvent(\"mouseover\",true, false, window, 0, 0, 0, 0, 0, false, false, false, false, 0, null);"
5666 +
"arguments[0].dispatchEvent(evObj);";
5668 BrowserHelper.ExecuteJavaScript(browser, javaScript, element.WebElement);
5677 private static Rectangle GetWindowRect(
string windowTitle)
5679 IntPtr hWnd = IntPtr.Zero;
5680 foreach (Process pList
in Process.GetProcesses())
5682 if (pList.MainWindowTitle.Contains(windowTitle))
5684 hWnd = pList.MainWindowHandle;
5689 if (hWnd != IntPtr.Zero)
5692 GetWindowRect(hWnd, out rect);
5693 return new Rectangle(rect.Left, rect.Top, rect.Right - rect.Left, rect.Bottom - rect.Top);
5697 return Rectangle.Empty;
5707 [DllImport(
"user32.dll")]
5708 [
return: MarshalAs(UnmanagedType.Bool)]
5709 private static extern bool GetWindowRect(IntPtr hWnd, out RECT lpRect);
5711 [StructLayout(LayoutKind.Sequential)]
5733 private static void RestoreFrameContext(Browser browser)
5735 if (
null == browser.CurrentUOW?.ContextPath)
5738 foreach (UnitOfWork.WebElementWithType contextType in browser.CurrentUOW.ContextPath)
5739 if (ElementType.IFrame == contextType.elementType &&
null != contextType.gPalElement?.WebElement)
5740 browser.BrowserDriver.SwitchTo().Frame((IWebElement)contextType.gPalElement.WebElement);
5749 public static Rectangle GetWindowRectangle(Browser browser, out Rectangle outerWindow)
5751 Rectangle retVal = GetWindowRectangle(browser);
5754 outerWindow = _lastOuterWindow;
5759 public static Rectangle GetWindowRectangle(Browser browser)
5762 bool jsSuccess =
false;
5763 int screenX = 0, screenY = 0, outerHeight = 0, innerHeight = 0, outerWidth = 0, innerWidth = 0;
5767 bool withReturn = !browser.UsePuppeteer;
5768 string prefix = withReturn ?
"return " :
"";
5769 dynamic value =
null;
5771 if (
true == browser.UseOttoMagic)
5773 Rectangle rect = browser.MagicHelper.GetWindowRectangle();
5775 outerHeight = rect.Height;
5776 innerHeight = rect.Y;
5777 outerWidth = rect.Width;
5778 innerWidth = rect.X;
5779 screenX = browser.MagicHelper.WindowScreenLeft();
5780 screenY = browser.MagicHelper.WindowScreenTop();
5783 else if (
true == browser.UsePuppeteer)
5785 outerHeight = browser.PuppeteerClient.WindowOuterHeight().Execute<
int>();
5786 innerHeight = browser.PuppeteerClient.WindowInnerHeight().Execute<
int>();
5787 outerWidth = browser.PuppeteerClient.WindowOuterWidth().Execute<
int>();
5788 innerWidth = browser.PuppeteerClient.WindowInnerWidth().Execute<
int>();
5789 screenX = browser.PuppeteerClient.WindowScreenLeft().Execute<
int>();
5790 screenY = browser.PuppeteerClient.WindowScreenTop().Execute<
int>();
5795 string jsScript = $
"{prefix} ["
5796 +
"window.screenLeft, "
5797 +
"window.screenTop, "
5798 +
"window.outerHeight, window.innerHeight, window.outerWidth, window.innerWidth]";
5804 bool inFrame = browser.CurrentUOW?.ContextPath?.Any(context => ElementType.IFrame == context.elementType) ??
false;
5808 if (
true == inFrame)
5809 browser.BrowserDriver.SwitchTo().DefaultContent();
5811 value = BrowserHelper.ExecuteJavaScriptObj(jsScript, browser,
null);
5815 if (
true == inFrame)
5816 RestoreFrameContext(browser);
5818 IList<object> values;
5820 if (value is Newtonsoft.Json.Linq.JArray jArray)
5822 values = jArray.Select(token => (
object)token).ToList();
5824 else if (value is IList<object> list)
5831 values =
new List<object> { 0, 0, 0, 0, 0, 0 };
5834 screenX = values.Count > 0 ? Convert.ToInt32(values[0]) : 0;
5835 screenY = values.Count > 1 ? Convert.ToInt32(values[1]) : 0;
5836 outerHeight = values.Count > 2 ? Convert.ToInt32(values[2]) : 0;
5837 innerHeight = values.Count > 3 ? Convert.ToInt32(values[3]) : 0;
5838 outerWidth = values.Count > 4 ? Convert.ToInt32(values[4]) : 0;
5839 innerWidth = values.Count > 5 ? Convert.ToInt32(values[5]) : 0;
5843 if (jsSuccess && innerWidth > 0 && innerHeight > 0)
5845 _lastOuterWindow =
new Rectangle(screenX, screenY, outerWidth, outerHeight);
5847 int navHeight = outerHeight - innerHeight;
5848 int navWidth = outerWidth - innerWidth;
5854 int border = navWidth / 2;
5855 int contentLeft = screenX + border;
5856 int contentTop = screenY + navHeight - border;
5857 return new Rectangle(contentLeft, contentTop, innerWidth, innerHeight);
5860 catch (GPALException)
5864 catch (Exception ex)
5866 GPAL.PublishSimpleEvent(GPALEventType.EXCEPTION, $
"JS execution failed for browser [{browser.BrowserSettings.BrowserType}]", browser, GPALObjectType.Browser, ex);
5870 GPAL.PublishSimpleEvent(GPALEventType.ERROR, $
"Failed to get rectangle for [{browser.BrowserSettings.BrowserType}].", browser, GPALObjectType.Browser);
5872 return Rectangle.Empty;
5881 public static Rectangle GetWindowOuterRectangle(Browser browser)
5883 int screenX = 0, screenY = 0, outerWidth = 0, outerHeight = 0;
5887 if (
true == browser.UseOttoMagic)
5890 Rectangle rect = browser.MagicHelper.GetWindowRectangle();
5891 outerWidth = rect.Width;
5892 outerHeight = rect.Height;
5893 screenX = browser.MagicHelper.WindowScreenLeft();
5894 screenY = browser.MagicHelper.WindowScreenTop();
5896 else if (
true == browser.UsePuppeteer)
5898 outerWidth = browser.PuppeteerClient.WindowOuterWidth().Execute<
int>();
5899 outerHeight = browser.PuppeteerClient.WindowOuterHeight().Execute<
int>();
5900 screenX = browser.PuppeteerClient.WindowScreenLeft().Execute<
int>();
5901 screenY = browser.PuppeteerClient.WindowScreenTop().Execute<
int>();
5905 var value = BrowserHelper.ExecuteJavaScriptObj(
"return [window.screenLeft, window.screenTop, window.outerWidth, window.outerHeight]", browser,
null);
5906 IList<object> values;
5908 if (value is Newtonsoft.Json.Linq.JArray jArray)
5909 values = jArray.Select(token => (
object)token).ToList();
5910 else if (value is IList<object> list)
5913 values =
new List<object> { 0, 0, 0, 0 };
5915 screenX = values.Count > 0 ? Convert.ToInt32(values[0]) : 0;
5916 screenY = values.Count > 1 ? Convert.ToInt32(values[1]) : 0;
5917 outerWidth = values.Count > 2 ? Convert.ToInt32(values[2]) : 0;
5918 outerHeight = values.Count > 3 ? Convert.ToInt32(values[3]) : 0;
5921 return new Rectangle(screenX, screenY, outerWidth, outerHeight);
5923 catch (GPALException)
5927 catch (Exception ex)
5929 GPAL.PublishSimpleEvent(GPALEventType.EXCEPTION, $
"Failed to get outer window rectangle for [{browser.BrowserSettings.BrowserType}]", browser, GPALObjectType.Browser, ex);
5930 return new Rectangle(0, 0, 0, 0);
5939 public static Dictionary<string, object> GetCssAttributes(Browser browser, IWebElement webElement)
5942 const el = arguments[0];
5943 const styles = window.getComputedStyle(el);
5946 // 2. Values that are ""noise"" - we never want them in the result
5947 const EMPTY_VALUES = new Set([
5948 '', 'none', 'normal', 'inherit', 'initial', 'unset',
5949 'auto', '0px', '0', 'transparent'
5952 // 3. The only properties we ever care about
5953 const RELEVANT = new Set([
5954 'display', 'visibility', 'opacity', 'pointer-events', 'overflow', 'clip-path',
5955 'position', 'top', 'left', 'right', 'bottom', 'z-index',
5956 'width', 'height', 'min-width', 'min-height', 'max-width', 'max-height',
5957 'margin', 'margin-top', 'margin-right', 'margin-bottom', 'margin-left',
5958 'padding', 'padding-top', 'padding-right', 'padding-bottom', 'padding-left',
5959 'border', 'border-width', 'border-style', 'border-color', 'box-sizing',
5960 'flex', 'flex-direction', 'flex-wrap', 'flex-grow', 'flex-shrink', 'flex-basis',
5961 'justify-content', 'align-items', 'align-self', 'order',
5962 'grid', 'grid-template', 'grid-area', 'grid-column', 'grid-row',
5963 'transform', 'transform-origin', 'transition', 'animation',
5964 'cursor', 'user-select', 'content', 'filter'
5967 // 4. Iterate only the properties that exist on the style object
5968 for (let i = 0; i < styles.length; i++) {
5969 const prop = styles[i]; // e.g. ""display""
5970 if (!RELEVANT.has(prop)) { continue; } // skip everything else
5972 const value = styles.getPropertyValue(prop).trim(); // ""block"", ""0.5"", etc.
5974 // Skip empty / default values
5975 if (!value || EMPTY_VALUES.has(value)) { continue; }
5977 props[prop] = value;
5982 var dict = BrowserHelper.ExecuteJavaScriptObj(script, browser, webElement) as Dictionary<string, object>;
5984 return dict?.ToDictionary(k => k.Key, v => v.Value ??
"");
5990 public static Dictionary<string, object> GetDomAttributes(
5992 IWebElement webElement)
5994 const string script =
@"
5995 const cleanText = (str) => (str || '').replace(/\s+/g, ' ').trim();
5996 const el = arguments[0];
5997 const attrs = el.attributes;
5999 for (let i = 0; i < attrs.length; i++) {
6001 result[a.name] = a.value; // value is always a string or empty
6003 // Also add a few handy built-in properties that are not attributes
6004 result['id'] = el.id;
6005 result['className'] = el.className;
6006 result['tagName'] = el.tagName;
6007 result['innerText'] = cleanText(el.innerText);
6008 result['textContent'] = cleanText(el.textContent);
6012 var raw = BrowserHelper.ExecuteJavaScriptObj(script, browser, webElement)
6013 as Dictionary<string, object>;
6016 return raw?.ToDictionary(
6018 kvp => kvp.Value ??
""
6019 ) ??
new Dictionary<string, object>();
6025 public static Dictionary<string, object> GetDomProperties(
6027 IWebElement webElement)
6029 const string script =
@"
6030 const el = arguments[0];
6033 if (""textContent"" in el) props.textContent = el.textContent;
6034 if (""innerHTML"" in el) props.innerHTML = el.innerHTML;
6035 if (""innerText"" in el) props.innerText = el.innerText;
6036 if (""outerHTML"" in el) props.outerHTML = el.outerHTML;
6037 if (""className"" in el) props.className = el.className;
6038 if (""disabled"" in el) props.disabled = el.disabled;
6043 var raw = BrowserHelper.ExecuteJavaScriptObj(script, browser, webElement)
6044 as Dictionary<string, object>;
6047 return raw?.ToDictionary(
6049 kvp => kvp.Value ??
""
6050 ) ??
new Dictionary<string, object>();
6064 internal static bool ResolveWebElementIssue(Browser browser, UnitOfWork currentUOW, ref dynamic webOrGpalElement, Exception exception, ref
int retries,
string errorMessage)
6067 if (
true == GPAL.NoFallbackRecoveryActions)
6070 bool shouldRetry =
false;
6071 IWebElement iWebElement;
6072 GPALElement gPalElement =
null;
6074 if (webOrGpalElement is GPALElement)
6076 gPalElement = webOrGpalElement;
6077 iWebElement = gPalElement.WebElement;
6081 iWebElement = webOrGpalElement;
6082 gPalElement =
new GPALElement() { WebElement = iWebElement };
6086 string recoveryMessage = $
"Attempting recovery for [{exception.GetType().Name}]. Retries left: [{retries}]";
6087 GPAL.PublishSimpleEvent(GPALEventType.INFO, recoveryMessage, iWebElement, GPALObjectType.Other);
6091 if (exception is StaleElementReferenceException)
6094 if (
null != currentUOW.CurrentSelector.WebSelectorMatchedResults ||
null != currentUOW.CurrentSelector.WebSelectorFoundResults)
6096 currentUOW.CurrentSelector.WebSelectorMatchedResults =
null;
6097 currentUOW.CurrentSelector.WebSelectorFoundResults =
null;
6101 _ = browser.Refresh;
6103 if (
null != webOrGpalElement)
6105 var elems = FindWebElements(browser, currentUOW, currentUOW.CurrentSelector, out
bool matchedAll, out List<GPALElement> matched,
null,
false);
6107 if (0 < elems.Count())
6108 webOrGpalElement = elems[0].WebElement;
6112 else if (exception is ElementClickInterceptedException)
6115 ElementHelper.MoveTo(gPalElement, currentUOW.CurrentSelector.OffsetX, currentUOW.CurrentSelector.OffsetY, browser);
6118 if (
false == (shouldRetry = IsElementClickable(iWebElement)))
6124 var overlays = browser.BrowserDriver.FindElements(By.XPath(
"//div[contains(@class, 'modal') or contains(@class, 'overlay') or contains(@class, 'popup')]"));
6127 foreach (var overlay
in overlays)
6129 if (overlay.Displayed)
6131 var closeButton = browser.BrowserDriver.FindElements(By.XPath(
".//button[contains(@class, 'close') or contains(text(), 'Close') or @aria-label='Close']"));
6132 if (closeButton.Any())
6134 closeButton.First().Click();
6135 shouldRetry = IsElementClickable(iWebElement);
6139 BrowserHelper.ExecuteJavaScriptObj(
"arguments[0].style.display='none';", browser, overlay);
6145 catch (NoSuchElementException)
6151 else if (exception is ElementNotInteractableException)
6154 ElementHelper.MoveTo(gPalElement, currentUOW.CurrentSelector.OffsetX, currentUOW.CurrentSelector.OffsetY, browser);
6158 if (
false == IsElementClickable(iWebElement))
6160 browser.BrowserSettings.SuppressJavaScriptFailures =
true;
6161 browser.ExecuteJavaScriptStr(
"window.scrollBy(0, -100, behavior: 'instant');");
6162 browser.BrowserSettings.SuppressJavaScriptFailures =
false;
6165 shouldRetry = IsElementClickable(iWebElement);
6168 catch (GPALException)
6172 catch (Exception ex)
6175 GPAL.PublishSimpleEvent(GPALEventType.EXCEPTION, $
"Recovery failed", iWebElement, GPALObjectType.Other, ex);
6176 shouldRetry =
false;
6179 return shouldRetry && retries > 0;
6186 internal static bool IsElementClickable(IWebElement webElement)
6188 return webElement.Displayed && webElement.Enabled;
6190 internal static bool IsElementClickable(GPALElement element)
6192 return element.Displayed && element.Enabled;
6200 public static bool IsElementEditable(GPALElement elem)
6205 string tag = elem?.WebElement?.TagName.ToUpperInvariant() ?? elem?.TagName.ToUpperInvariant();
6206 string type =
string.Empty;
6209 type = elem.GetAttribute(
"type")?.ToLower();
6213 string cursor = elem?.GetCssValue(
"cursor")?.ToLower();
6214 if (cursor ==
"text")
6218 bool retval = (tag ==
"INPUT" &&
6219 (type ==
"text" || type ==
"email" || type ==
"password" || type ==
"search"))
6220 || tag ==
"TEXTAREA"
6221 || (tag ==
"DIV" && elem.GetAttribute(
"contenteditable")?.ToLower() ==
"true");
6226 string classAttr = elem?.GetAttribute(
"class") ??
"";
6227 if (
null != classAttr)
6228 retval = classAttr.Split(
' ').Contains(
"Editable");
6254 public static void SendString(Browser browser,
string textToSend)
6256 int delayMs = GPAL.TypingDelay;
6258 if (
true == browser.BrowserSettings.UseHardware ||
true == GPAL.GPALSettings.UseHardware)
6259 HardwareHelper.SendString(textToSend, delayMs);
6260 else if (
true == browser.BrowserSettings.UseOttoMagic)
6261 browser.MagicHelper.SendString(textToSend, delayMs);
6262 else if (
true == browser.BrowserSettings.UsePuppeteer)
6263 browser.PuppeteerClient.SendString(textToSend, delayMs);
6266 foreach (SelectorSettings.SelectorPathEntry selectorPathEntry in browser.CurrentUOW.CurrentSelector?.SelectorPaths)
6267 if (
null != selectorPathEntry.WebSelectorFoundResults)
6268 foreach (GPALElement webElement
in selectorPathEntry.WebSelectorFoundResults)
6269 if (InteractionType.JavaScript == browser.CurrentUOW.CurrentSelector?.InteractionType ||
true == GPAL.GPALSettings.UseJavaScript)
6272 BrowserHelper.ExecuteJavaScriptObj(
@"
6273 function sendStringBlindly(text) {
6274 for (let char of text) {
6275 document.dispatchEvent(new KeyboardEvent('keydown', {{
6279 document.dispatchEvent(new KeyboardEvent('keyup', {{
6285 $
"sendStringBlindly({textToSend});", browser);
6289 foreach (
char c
in textToSend)
6292 Thread.Sleep(HardwareHelper.GetTypingDelay(delayMs));
6293 BrowserHelper.ExecuteJavaScriptObj(
@"
6294 var char = arguments[0];
6295 document.dispatchEvent(new KeyboardEvent('keydown', { key: char, bubbles: true }));
6296 document.dispatchEvent(new KeyboardEvent('keyup', { key: char, bubbles: true }));",
6297 browser, c.ToString());
6304 IWebElement body = browser.BrowserSettings.BrowserDriver.FindElement(By.TagName(
"body"));
6306 Actions actions =
new Actions(browser.BrowserSettings.BrowserDriver);
6308 foreach (
char c
in textToSend)
6310 if (
false == first && 0 < delayMs)
6311 actions.Pause(TimeSpan.FromMilliseconds(HardwareHelper.GetTypingDelay(delayMs)));
6313 .KeyDown(body, c.ToString())
6314 .KeyUp(body, c.ToString());
6317 actions.Build().Perform();
6329 public static void SendKey(Browser browser,
byte VKCode)
6331 if (
true == browser.BrowserSettings.UseHardware ||
true == GPAL.GPALSettings.UseHardware)
6333 if (GPAL.VK_RETURN == VKCode)
6335 var matched = browser.CurrentUOW.CurrentSelector?.WebSelectorMatchedResults?.FirstOrDefault();
6336 if (
false == ElementHelper.IsElementEditable(matched))
6343 HardwareHelper.SendKey(VKCode);
6345 else if (
true == browser.BrowserSettings.UseOttoMagic)
6346 browser.MagicHelper.SendKey(VKCode);
6347 else if (
true == browser.BrowserSettings.UsePuppeteer)
6348 browser.PuppeteerClient.SendKey(VKCode).Execute();
6352 foreach (SelectorSettings.SelectorPathEntry selectorPathEntry in browser.CurrentUOW.CurrentSelector?.SelectorPaths)
6353 if (
null != selectorPathEntry.WebSelectorFoundResults)
6354 foreach (GPALElement webElement
in selectorPathEntry.WebSelectorFoundResults)
6355 if (InteractionType.JavaScript == browser.CurrentUOW.CurrentSelector?.InteractionType)
6356 ElementHelper.JavaScriptSendKey(browser, webElement, VKCode);
6358 ElementHelper.SeleniumSendKeys(browser, webElement, VKCode);
6362 public static string GetEnumNames<T>(T value) where T :
struct, Enum
6364 if (!typeof(T).IsDefined(typeof(FlagsAttribute),
false))
6365 return value.ToString();
6367 var flags = Enum.GetValues(typeof(T))
6369 .Where(flag => !Equals(flag,
default(T)) && value.HasFlag(flag))
6370 .Select(flag => flag.ToString());
6372 string retVal =
string.Join(
", ", flags);
6374 return false ==
string.IsNullOrEmpty(retVal) ? retVal :
"NONE";
6383 public static string GenerateCssSelector(IBrowser browser, IWebElement webElement)
6385 return BrowserHelper.ExecuteJavaScript(browser,
@"
6386 function getCssPath(el) {
6387 if (!(el instanceof Element))
6392 while (el && el.nodeType === Node.ELEMENT_NODE) {
6395 path.unshift('#' + CSS.escape(el.id));
6399 var selector = el.nodeName.toLowerCase();
6404 while ((sibling = sibling.previousElementSibling) != null) {
6405 if (sibling.nodeName === el.nodeName)
6409 selector += ':nth-of-type(' + nth + ')';
6411 path.unshift(selector);
6413 el = el.parentElement;
6416 return path.join(' > ');
6418 return getCssPath(arguments[0]);
Browser object that contains the fluent methods to create your Browser workflow. Instatiated using G...
Pseudo element used in Applications and Browser workflows for image matching and unified automation....
string TagName
HTML tag name of the element.
string Css
CSS selector used to locate this element.
dynamic WebElement
The wrapped Selenium IWebElement or internal dynamic element.
Everything starts here, all the GPAL controls and global settings using fluent syntax are here....
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...
GPAL Selector used to locate Application and Browser elements. Instantiated with GPAL....
int DeltaX
Drag-and-drop deltaX to move from the elemenet.X + OffsetX.
int DeltaY
Drag-and-drop deltaY to move from the elemenet.Y + OffsetY.
int OffsetY
Y offset fromm the top left corner to interact with the element, image match included....
InteractionType InteractionType
Defined method to interact with this element. NOTE: This can be overridden in the workflow.
int OffsetX
X offset from the top left corner to interact with the element, image match included....
Settings for the current selector. Use this only for debugging.