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;
81 public static ReadOnlyCollection<GPALElement> FindWebElements(
83 UnitOfWork currentUOW,
86 out List<GPALElement> matchedElements,
87 GPALElement webElement =
null,
88 bool callIfHandler =
true,
89 bool isPersistent =
false)
93 int withAllThatMatch =
int.MaxValue;
96 if (
null == selector.Browser)
97 selector.Browser = browser;
99 dynamic ourRetryElement = webElement;
100 ReadOnlyCollection<GPALElement> retElems =
new ReadOnlyCollection<GPALElement>(
new List<GPALElement>());
101 string message =
string.Empty;
103 currentUOW.CurrentSelector = selector;
105 if (
false == lastSelector?.Name.Equals(selector.Name))
107 listOfMessages.Clear();
108 supressedMessage =
false;
109 lastSelector = selector;
111 else if (
null == lastSelector)
112 lastSelector = selector;
114 if (browser.persistentUOW != currentUOW)
115 browser.CurrentUOW.CurrentSelector = selector;
118 matchedElements =
new List<GPALElement>();
120 List<FindWebElementsByDelegate> functionList =
new List<FindWebElementsByDelegate>
122 null, FindWebElementsByCss, FindWebElementsByImage, FindWebElementsByText,
123 FindWebElementsByValue, FindWebElementsByXPath, FindWebElementsByName,
124 null, FindWebElementsByClassName, FindWebElementsByPlaceholder, FindWebElementsByHRef
127 foreach (var selectorPathEntry
in selector.SelectorPaths)
130 int textLength = selectorPathEntry.SelectorPath.Length - 1;
131 string abbreviatedText = selectorPathEntry.SelectorPath.Substring(0, Math.Min(7, textLength)) +
"..." + selectorPathEntry.SelectorPath.Substring(Math.Max(0, textLength - 7));
132 if (18 > selectorPathEntry.SelectorPath.Length)
133 abbreviatedText = selectorPathEntry.SelectorPath;
135 int idx = GetIndexOfEnum<SelectorPathType>((
int)selectorPathEntry.SelectorPathType);
136 bool tryNextSelector =
false;
137 ReadOnlyCollection<GPALElement> allFoundElems =
new ReadOnlyCollection<GPALElement>(
new List<GPALElement>());
138 string errorMessage = $
"Error finding elements for selector [{selector.Name}][{abbreviatedText}]";
139 int selectorPathIdx = 0;
143 message = $
"Searching for elements for selector [{selector.Name}] using [{selectorPathEntry.SelectorPathType}] [{abbreviatedText}]";
145 if (
false == listOfMessages.Contains(message))
147 listOfMessages.Add(message);
148 GPAL.PublishSimpleEvent(GPALEventType.INFO, message, browser, GPALObjectType.Browser);
151 var tmpAllFoundElems = FindElements(browser, currentUOW, selectorPathEntry, selector, webElement, idx, functionList, isPersistent);
157 retElems = allFoundElems = tmpAllFoundElems ??
new ReadOnlyCollection<GPALElement>(
new List<GPALElement>());
158 matchedElements = allFoundElems.ToList();
160 if (0 < retElems?.Count)
163 if (
true == browser.UseSelenium)
164 foreach (GPALElement gPALElement
in allFoundElems)
165 gPALElement.Css = GenerateCssSelector(browser, gPALElement.WebElement);
167 var matchingResult = ApplyCustomMatchCriteria(selector, allFoundElems, browser, matchedElements, out tryNextSelector);
168 matchedAll = matchingResult;
172 retElems =
new ReadOnlyCollection<GPALElement>(
new List<GPALElement>());
173 matchedElements.Clear();
177 List<GPALElement> simpleMatches = matchedElements;
179 if (selector.MatchCriteria.Count > 0)
180 matchedAll = ApplySimpleMatching(selector, matchedElements, matchedElements, out simpleMatches);
184 matchedElements = simpleMatches;
186 selectorPathEntry.WebSelectorFoundResults = allFoundElems?.ToList();
187 selectorPathEntry.WebSelectorMatchedResults = simpleMatches;
192 if (
int.MaxValue != currentUOW.WithAllThatMatch && 0 < currentUOW.WithAllThatMatch)
193 withAllThatMatch = currentUOW.WithAllThatMatch;
194 else if (
int.MaxValue != browser.BrowserSettings.WithAllThatMatch && 0 < browser.BrowserSettings.WithAllThatMatch)
195 withAllThatMatch = browser.BrowserSettings.WithAllThatMatch;
196 else if (0 < GPAL.GPALSettings.WithAllThatMatch)
197 withAllThatMatch = GPAL.GPALSettings.WithAllThatMatch;
199 if (
int.MaxValue != withAllThatMatch && 0 < simpleMatches?.Count)
201 List<GPALElement> tmpList =
new List<GPALElement>();
204 tmpList.AddRange(simpleMatches.Take(Math.Min(withAllThatMatch, matchedElements?.Count ?? 0)));
206 matchedElements = tmpList;
209 foreach (GPALElement element
in retElems)
211 element.Browser = browser;
212 element.Selector = selector;
216 catch (StaleElementReferenceException sereex)
218 message = errorMessage + $
" StaleElementReferenceException: [{sereex.Message}]";
219 retry = ResolveWebElementIssue(browser, currentUOW, ref ourRetryElement, sereex, ref retries, message);
221 catch (ElementClickInterceptedException ecieex)
223 message = errorMessage + $
" ElementClickInterceptedException: [{ecieex.Message}]";
224 retry = ResolveWebElementIssue(browser, currentUOW, ref ourRetryElement, ecieex, ref retries, message);
226 catch (ElementNotVisibleException enveex)
228 message = errorMessage + $
" ElementNotVisibleException: [{enveex.Message}]";
229 retry = ResolveWebElementIssue(browser, currentUOW, ref ourRetryElement, enveex, ref retries, message);
231 catch (GPALException)
237 GPAL.PublishSimpleEvent(GPALEventType.EXCEPTION, errorMessage, browser, GPALObjectType.Browser, ex);
241 if (
true == retry && 0 < retries--)
247 selector.ElementsFoundAndMatchedCount = (int)(
null != matchedElements ? matchedElements?.Count() : 0);
249 message = $
"[{retElems?.Count ?? 0}] elements found for selector [{selector.Name}]";
251 if (
false == listOfMessages.Contains(message))
253 listOfMessages.Add(message);
254 GPAL.PublishSimpleEvent(GPALEventType.INFO, message, browser, GPALObjectType.Browser);
256 if (0 < retElems?.Count && (
true == selector.AnyMatchCriteria() || Int32.MaxValue != withAllThatMatch))
258 var matchCount = selector.MatchCriteria.Count > 0 ? selector.MatchCriteria.Count.ToString() : $
"WithAllThatMatch({withAllThatMatch})";
260 if ((
true == matchCount.Contains(
"WithAllThatMatch") && 0 < withAllThatMatch) || 0 < selector.MatchCriteria.Count)
261 GPAL.PublishSimpleEvent(GPALEventType.INFO, $
"[{matchedElements?.Count}] elements matched for selector [{selector.Name}] with [{matchCount}] match criteria", browser, GPALObjectType.Browser);
264 else if (
false == supressedMessage)
266 GPAL.PublishSimpleEvent(GPALEventType.INFO,
"Supressing repeat messages", browser, GPALObjectType.Browser);
267 supressedMessage =
true;
270 if (matchedAll || retElems?.Count > 0)
272 else if (++selectorPathIdx == selector.SelectorPaths.Count)
273 GPAL.PublishSimpleEvent(GPALEventType.DEBUG, $
"No more selector paths. No elements found.", browser, GPALObjectType.Browser);
277 if (
true == callIfHandler)
278 CallIfHandlers(browser, currentUOW, retElems, matchedElements, selector, matchedAll);
298 public static ReadOnlyCollection<GPALElement> FindElements(
300 UnitOfWork currentUOW,
301 SelectorSettings.SelectorPathEntry selectorPathEntry,
303 GPALElement gPalElement,
305 List<FindWebElementsByDelegate> functionList,
308 if (0 < browser.CurrentUOW.ContextPath?.Count &&
null == gPalElement)
311 if (
true == browser.UseOttoMagic)
312 browser.MagicHelper.SwitchToDefaultContent();
313 else if (
true == browser.UsePuppeteer)
314 browser.PuppeteerClient.SwitchToDefaultContent().Execute();
315 else if (
true == browser.UseSelenium)
316 browser.BrowserDriver.SwitchTo().DefaultContent();
318 IEnumerable<UnitOfWork.WebElementWithType> stepsToProcess;
337 stepsToProcess = browser.CurrentUOW.ContextPath;
341 foreach (UnitOfWork.WebElementWithType contextType in stepsToProcess)
343 if (ElementType.IFrame == contextType.elementType)
345 if (
true == browser.UseOttoMagic)
346 browser.MagicHelper.SwitchToElement(contextType.gPalElement.Css);
347 else if (
true == browser.UsePuppeteer)
348 browser.PuppeteerClient.SwitchToFrame(contextType.gPalElement.Css).Execute();
350 browser.BrowserDriver.SwitchTo().Frame((IWebElement)contextType.gPalElement.WebElement);
357 else if (ElementType.ShadowRoot == contextType.elementType)
359 if (
true == browser.UseOttoMagic)
360 browser.MagicHelper.SwitchToShadowRoot(contextType.gPalElement.Css);
361 else if (
true == browser.UsePuppeteer)
362 browser.PuppeteerClient.SwitchToShadowRoot(contextType.gPalElement.Css).Execute();
368 gPalElement = contextType.gPalElement;
370 else if (ElementType.Element == contextType.elementType)
372 gPalElement = contextType.gPalElement;
378 while (retries-- > 0)
382 return functionList[idx](browser, selectorPathEntry, selector, gPalElement, isPersistent);
384 catch (GPALException)
390 GPAL.PublishSimpleEvent(GPALEventType.EXCEPTION, $
"Error finding elements for selector [{selector.Name}]. Trying next selector path if defined.", browser, GPALObjectType.Browser, ex);
395 return new ReadOnlyCollection<GPALElement>(
new List<GPALElement>());
411 private static bool ApplyCustomMatchCriteria(
413 ReadOnlyCollection<GPALElement> allFoundElems,
415 List<GPALElement> matchedElements,
416 out
bool tryNextSelector,
419 tryNextSelector =
false;
421 var universe = allFoundElems?.ToList();
424 var workingSet = matchedElements.Count > 0
425 ?
new List<GPALElement>(matchedElements)
426 : new List<GPALElement>(universe);
428 bool matchedAll =
true;
430 foreach (var matchCriteriaEntry
in selector.MatchCriteria)
432 if (matchCriteriaEntry.MatchType != MatchType.Custom)
436 var source = AndOrOr ? workingSet : universe;
438 GPAL.PublishSimpleEvent(GPALEventType.DEBUG,
439 $
"Applying custom match [{matchCriteriaEntry.WebMatchingFunction.GetInvocationList()[0].Method.Name}] " +
440 $
"to {(AndOrOr ? "working set
" : "full universe
")} ({source.Count} elements)",
442 GPALObjectType.Browser);
444 CallIfStatus handled = matchCriteriaEntry.WebMatchingFunction(
445 source.Cast<IGPALElement>().ToList(),
446 out List<IGPALElement> tempMatchedElemsRaw,
449 matchCriteriaEntry.ExactMatch);
450 List<GPALElement> tempMatchedElems = tempMatchedElemsRaw?.Cast<GPALElement>().ToList();
453 var matchedThisRound = new List<GPALElement>();
454 var roundSet = new HashSet<GPALElement>();
456 foreach (var elem in tempMatchedElems ?? new List<GPALElement>())
458 if (roundSet.Add(elem))
460 matchedThisRound.Add(elem);
462 GPAL.PublishSimpleEvent(GPALEventType.DEBUG,
463 $
"Element matched custom criteria [{matchCriteriaEntry.WebMatchingFunction.GetInvocationList()[0].Method.Name}] " +
464 $
"TagName: [{elem.TagName}], Text: [{elem.Text}]",
466 GPALObjectType.Browser);
470 if (CallIfStatus.Terminate == handled)
472 GPAL.PublishSimpleEvent(
473 GPALEventType.EXCEPTION,
474 $
"Custom match handler [{matchCriteriaEntry.WebMatchingFunction.GetInvocationList()[0].Method.Name}] requested program termination for selector [{selector.Name}].",
476 GPALObjectType.Browser);
478 throw new GPALException(
479 $
"Custom match handler [{matchCriteriaEntry.WebMatchingFunction.GetInvocationList()[0].Method.Name}] requested program termination for selector [{selector.Name}].");
485 workingSet = matchedThisRound;
490 var unionSet =
new HashSet<GPALElement>(workingSet);
491 foreach (var elem
in matchedThisRound)
493 if (unionSet.Add(elem))
495 workingSet.Add(elem);
497 GPAL.PublishSimpleEvent(GPALEventType.DEBUG,
498 $
"Element added to OR working set from custom match: TagName: [{elem.TagName}], Text: [{elem.Text}]",
500 GPALObjectType.Browser);
505 if (CallIfStatus.Handled == handled)
508 if (CallIfStatus.TryNext == handled)
510 tryNextSelector = true;
515 matchedElements.Clear();
516 matchedElements.AddRange(workingSet);
532 private static bool ApplySimpleMatching(
534 List<GPALElement> matchedElements,
535 List<GPALElement> allFoundElems,
536 out List<GPALElement> matchedElementsOut,
539 matchedElementsOut =
new List<GPALElement>();
540 var universe = allFoundElems?.ToList();
543 var workingSet = matchedElements.Count > 0
544 ?
new List<GPALElement>(matchedElements)
545 : new List<GPALElement>(universe);
547 foreach (var matchCriteriaEntry
in selector.MatchCriteria)
549 if (matchCriteriaEntry.MatchType == MatchType.None || matchCriteriaEntry.MatchType == MatchType.Custom)
553 var source = AndOrOr ? workingSet : universe;
554 var matchedThisRound = new List<GPALElement>();
555 var roundSet = new HashSet<GPALElement>();
557 foreach (var element in source)
559 string elementStringToMatch = null;
561 if (
"GPALElement" != element.TagName)
563 switch ((MatchType)((int)matchCriteriaEntry.MatchType & 0xFE))
566 elementStringToMatch = element.Text;
567 if (string.IsNullOrEmpty(elementStringToMatch))
568 elementStringToMatch = element.GetAttribute(
"title");
569 if (string.IsNullOrEmpty(elementStringToMatch))
570 elementStringToMatch = element.GetAttribute(
"value");
573 elementStringToMatch = element.GetAttribute(
"href");
576 elementStringToMatch = element.GetAttribute(
"src");
578 case MatchType.Placeholder:
579 elementStringToMatch = element.GetAttribute(
"placeholder");
581 case MatchType.Value:
582 elementStringToMatch = element.GetAttribute(
"value");
584 case MatchType.Attribute:
585 elementStringToMatch = element.GetAttribute(selector.AttributeName);
589 if (!string.IsNullOrEmpty(elementStringToMatch))
591 GPAL.PublishSimpleEvent(GPALEventType.DEBUG,
592 $
"String to match [{matchCriteriaEntry.StringToMatch}] element text [{elementStringToMatch}]");
594 bool isMatch = false;
596 if ((int)(matchCriteriaEntry.MatchType & MatchType.Regex) == 1)
598 var mc = Regex.Matches(elementStringToMatch, matchCriteriaEntry.StringToMatch);
604 if (false == matchCriteriaEntry.ExactMatch && elementStringToMatch.Contains(matchCriteriaEntry.StringToMatch))
606 else if (true == matchCriteriaEntry.ExactMatch && elementStringToMatch.Equals(matchCriteriaEntry.StringToMatch))
610 if (isMatch && roundSet.Add(element))
612 GPAL.PublishSimpleEvent(GPALEventType.DEBUG, $
"Adding [{element.TagName}][{element.Text}]");
613 matchedThisRound.Add(element);
619 if (roundSet.Add(element))
620 matchedThisRound.Add(element);
627 workingSet = matchedThisRound;
632 var unionSet =
new HashSet<GPALElement>(workingSet);
633 foreach (var elem
in matchedThisRound)
635 if (unionSet.Add(elem))
637 workingSet.Add(elem);
643 matchedElementsOut = workingSet;
644 return universe.Count == matchedElementsOut.Count;
662 private static void CallIfHandlers(Browser browser, UnitOfWork currentUOW, ReadOnlyCollection<GPALElement> foundElements, List<GPALElement> matchedElements, Selector selector,
bool matchedAll)
665 string currentMethodName =
null;
666 int selectorsFoundOrMatched = selector.AnyMatchCriteria() ? matchedElements?.Count ?? 0 : foundElements?.Count ?? 0;
675 if (0 < selectorsFoundOrMatched)
677 if (0 != (selector.SelectorSettings.CallIfFound?.Count ?? 0))
679 foreach (Browser.CallIfDelegate func in selector.SelectorSettings.CallIfFound)
681 if (
true == selector.RemovedMethods.Contains(func))
684 bool safeActionCalled = browser.CurrentUOW.ActionCalled;
685 safeUOW = browser.CurrentUOW;
686 browser.CurrentUOW.ActionCalled =
true;
688 currentMethodName = func.Method.Name;
689 str = $
"Browser Selector CallIfFound handler [{currentMethodName}] requested program termination on selector [{selector.Name}] [{selector.SelectorPaths[0].SelectorPath}]";
691 GPAL.PublishSimpleEvent(GPALEventType.INFO, $
"Invoking Selector CallIfFound [{func.Method.Name}]", browser, GPALObjectType.Browser);
693 selector.CallIfFoundHandled = func(browser, foundElements?.Cast<IGPALElement>().ToList(), matchedElements?.Cast<IGPALElement>().ToList(), selector, matchedAll);
695 browser.CurrentUOW = safeUOW;
696 browser.CurrentUOW.ActionCalled = safeActionCalled;
698 if (CallIfStatus.NotHandled == selector.CallIfFoundHandled)
704 if (CallIfStatus.Terminate == selector.CallIfFoundHandled)
706 GPAL.PublishSimpleEvent(GPALEventType.INFO, str, browser, GPALObjectType.Browser);
707 throw new GPALException(str);
712 if (CallIfStatus.NotHandled == selector.CallIfFoundHandled && 0 < (currentUOW.CallIfFound?.Count ?? 0))
714 foreach (Browser.CallIfDelegate func in currentUOW.CallIfFound)
716 if (
true == selector.RemovedMethods.Contains(func))
719 safeUOW = browser.CurrentUOW;
721 currentMethodName = func.Method.Name;
722 str = $
"Browser UOW Selector CallIfFound handler [{currentMethodName}] requested program termination on selector [{selector.Name}] [{selector.SelectorPaths[0].SelectorPath}]";
724 GPAL.PublishSimpleEvent(GPALEventType.INFO, $
"Invoking UOW CallIfFound [{func.Method.Name}]", browser, GPALObjectType.Browser);
726 currentUOW.CallIfFoundHandled = func(browser, foundElements?.Cast<IGPALElement>().ToList(), matchedElements?.Cast<IGPALElement>().ToList(), selector, matchedAll);
728 browser.CurrentUOW = safeUOW;
730 if (CallIfStatus.NotHandled == currentUOW.CallIfFoundHandled)
736 if (CallIfStatus.Terminate == currentUOW.CallIfFoundHandled)
738 GPAL.PublishSimpleEvent(GPALEventType.INFO, str, browser, GPALObjectType.Browser);
739 throw new GPALException(str);
744 if (CallIfStatus.NotHandled == selector.CallIfFoundHandled && 0 == currentUOW.CallIfFoundHandled && 0 != (GPAL.GPALSettings.CallIfFoundList?.Count ?? 0))
746 foreach (Browser.CallIfDelegate func in GPAL.GPALSettings.CallIfFoundList)
748 if (
true == selector.RemovedMethods.Contains(func))
751 safeUOW = browser.CurrentUOW;
753 currentMethodName = func.Method.Name;
754 str = $
"Gloabl CallIfFound handler [{currentMethodName}] requested program termination on selector [{selector.Name}] [{selector.SelectorPaths[0].SelectorPath}]";
756 GPAL.PublishSimpleEvent(GPALEventType.INFO, $
"Invoking Global CallIfFound [{func.Method.Name}]", browser, GPALObjectType.Browser);
758 GPAL.GPALSettings.CallIfFoundHandled = func(browser, foundElements?.Cast<IGPALElement>().ToList(), matchedElements?.Cast<IGPALElement>().ToList(), selector, matchedAll);
760 browser.CurrentUOW = safeUOW;
762 if (CallIfStatus.NotHandled == GPAL.GPALSettings.CallIfFoundHandled)
768 if (CallIfStatus.Terminate == (GPAL.GPALSettings.CallIfFoundHandled))
770 GPAL.PublishSimpleEvent(GPALEventType.INFO, str, browser, GPALObjectType.Browser);
771 throw new GPALException(str);
777 if (0 < selector.SelectorSettings.CallIfNotFound.Count)
779 foreach (Browser.CallIfDelegate func in selector.SelectorSettings.CallIfNotFound)
781 if (
true == selector.RemovedMethods.Contains(func))
784 safeUOW = browser.CurrentUOW;
786 currentMethodName = func.Method.Name;
787 str = $
"Browser Selector CallIfNotFound handler [{currentMethodName}] requested program termination on selector [{selector.Name}] [{selector.SelectorPaths[0].SelectorPath}[";
789 GPAL.PublishSimpleEvent(GPALEventType.INFO, $
"Invoking Selector CallIfNotFound [{func.Method.Name}]", browser, GPALObjectType.Browser);
791 selector.CallIfNotFoundHandled = func(browser, foundElements?.Cast<IGPALElement>().ToList(), matchedElements?.Cast<IGPALElement>().ToList(), selector, matchedAll);
793 browser.CurrentUOW = safeUOW;
795 if (CallIfStatus.NotHandled == selector.CallIfNotFoundHandled)
801 if (CallIfStatus.Terminate == selector.CallIfNotFoundHandled)
803 GPAL.PublishSimpleEvent(GPALEventType.INFO, str, browser, GPALObjectType.Browser);
804 throw new GPALException(str);
808 if (0 == selector.CallIfNotFoundHandled && 0 < currentUOW.CallIfNotFound.Count)
810 foreach (Browser.CallIfDelegate func in currentUOW.CallIfNotFound)
812 if (
true == selector.RemovedMethods.Contains(func))
815 safeUOW = browser.CurrentUOW;
817 currentMethodName = func.Method.Name;
818 str = $
"Browser UOW Selector CallIfNotFound handler [{currentMethodName}] requested program termination on selector [{selector.Name}] [{selector.SelectorPaths[0].SelectorPath}]";
820 GPAL.PublishSimpleEvent(GPALEventType.INFO, $
"Invoking UOW CallIfNotFound [{func.Method.Name}]", browser, GPALObjectType.Browser);
822 currentUOW.CallIfNotFoundHandled = func(browser, foundElements?.Cast<IGPALElement>().ToList(), matchedElements?.Cast<IGPALElement>().ToList(), selector, matchedAll);
824 browser.CurrentUOW = safeUOW;
826 if (CallIfStatus.NotHandled == currentUOW.CallIfNotFoundHandled)
832 if (CallIfStatus.Terminate == currentUOW.CallIfNotFoundHandled)
834 GPAL.PublishSimpleEvent(GPALEventType.INFO, str, browser, GPALObjectType.Browser);
835 throw new GPALException(str);
839 if (0 == selector.CallIfNotFoundHandled && 0 == currentUOW.CallIfNotFoundHandled && 0 < GPAL.GPALSettings.CallIfNotFoundList.Count)
841 foreach (Browser.CallIfDelegate func in GPAL.GPALSettings.CallIfNotFoundList)
843 if (
true == selector.RemovedMethods.Contains(func))
846 safeUOW = browser.CurrentUOW;
848 currentMethodName = func.Method.Name;
849 str = $
"Gloabl CallIfNotFound handler [{currentMethodName}] requested program termination on selector [{selector.Name}] [{selector.SelectorPaths[0].SelectorPath}]";
851 GPAL.PublishSimpleEvent(GPALEventType.INFO, $
"Invoking Global CallIfNotFound [{func.Method.Name}]", browser, GPALObjectType.Browser);
853 GPAL.GPALSettings.CallIfNotFoundHandled = func(browser, foundElements?.Cast<IGPALElement>().ToList(), matchedElements?.Cast<IGPALElement>().ToList(), selector, matchedAll);
855 browser.CurrentUOW = safeUOW;
857 if (CallIfStatus.NotHandled == GPAL.GPALSettings.CallIfNotFoundHandled)
863 if (CallIfStatus.Terminate == GPAL.GPALSettings.CallIfNotFoundHandled)
865 GPAL.PublishSimpleEvent(GPALEventType.INFO, str, browser, GPALObjectType.Browser);
866 throw new GPALException(str);
870 if (
true == selector.StopOnNotFound ||
true == GPAL.StopOnNotFound)
872 str =
$@"{(true == selector.StopOnNotFound ? "selector.
" : "GPAL.
")}StopOnNotFound set, terminating on Not Found element for [{selector.Name}][{selector.SelectorPaths[0].SelectorPath}]";
873 GPAL.PublishSimpleEvent(GPALEventType.INFO, str, browser, GPALObjectType.Browser);
874 throw new GPALException(str);
888 private static ReadOnlyCollection<GPALElement> FindWebElementsByCss(Browser browser, SelectorPathEntry selectorPath, Selector selector, GPALElement webElement =
null,
bool isPersistent =
false)
890 bool searchForElement = selector.SearchForSelector;
891 ReadOnlyCollection<IWebElement> elems;
892 ReadOnlyCollection<GPALElement> GPALelems =
null;
894 if (browser.UseOttoMagic)
896 List<GPALElement> result =
null;
898 if (
false == webElement?.IsShadowRoot)
899 browser.MagicHelper.SwitchToElement(webElement.Css);
901 if (
true == isPersistent)
902 result = browser.MagicHelper
903 .QueryPersistentSelectors(selectorPath.SelectorPath);
905 result = browser.MagicHelper
906 .QuerySelectors(selectorPath.SelectorPath);
909 GPALelems =
new ReadOnlyCollection<GPALElement>(result);
911 else if (
true == browser.UsePuppeteer)
913 if (
false == webElement?.IsShadowRoot)
914 browser.PuppeteerClient.InElement(webElement.Css).Execute();
916 var result2 = Task.Run(() => browser.PuppeteerCommunicator.EvaluateSelector(selectorPath.SelectorPath));
918 GPALelems =
new ReadOnlyCollection<GPALElement>(result2.Result);
922 string css = selectorPath.SelectorPath;
924 elems = webElement?.WebElement !=
null
925 ? webElement.WebElement.FindElements(By.CssSelector(css))
929 : browser.BrowserDriver.FindElements(By.CssSelector(css));
936 if (0 == elems.Count &&
true == webElement?.IsClosedShadowRoot &&
false == isPersistent)
937 return new ReadOnlyCollection<GPALElement>(
new List<GPALElement>() { webElement });
939 GPALelems = ToGPALElements(browser, selector, elems);
955 private static ReadOnlyCollection<GPALElement> FindWebElementsByImage(Browser browser, SelectorPathEntry selectorPath, Selector selector, GPALElement webElement =
null,
bool isPersistent =
false)
957 bool searchForElement = selector.SearchForSelector;
959 ReadOnlyCollection<GPALElement> elems =
null;
961 Rectangle foundImageBounds =
new Rectangle();
962 List<GPALElement> retWebElement =
new List<GPALElement>(); ;
964 if (
null == selectorPath.Image)
968 selectorPath.Image = Image.FromFile(selectorPath.SelectorPath);
970 catch (GPALException)
976 GPAL.PublishSimpleEvent(GPALEventType.EXCEPTION, $
"Unable to load image for [{selector.Name}].", browser, GPALObjectType.Browser, ex);
981 foundImageBounds = ((ImageHelper)GPAL.ImageHelper).FindImage(selectorPath.SelectorPath, browser);
984 if (
true == searchForElement)
986 if (
true == browser.UseSelenium)
988 var bodyElement = browser.BrowserDriver.FindElement(By.TagName(
"body"));
994 if (
true == foundImageBounds.IsEmpty)
997 while (
true == foundImageBounds.IsEmpty &&
false == IsEndOfPage(browser, webElement))
999 _ = browser.PageDown;
1001 foundImageBounds = ((ImageHelper)GPAL.ImageHelper).FindImage(selectorPath.SelectorPath, browser);
1005 if (
false == foundImageBounds.IsEmpty)
1011 retWebElement.Add(
new GPALElement(foundImageBounds.Location, foundImageBounds.Size, selectorPath.SelectorPathType.ToString(), browser.BrowserDriver,
"GPALElement")
1014 Selector = selector,
1015 CoordinateSpace = true == browser.BrowserSettings.UseHeadless ? CoordinateSpace.Viewport : CoordinateSpace.Screen
1017 elems =
new ReadOnlyCollection<GPALElement>(retWebElement);
1033 private static ReadOnlyCollection<GPALElement> FindWebElementsByXPath(Browser browser, SelectorPathEntry selectorPath, Selector selector, GPALElement webElement =
null,
bool isPersistent =
false)
1035 bool searchForElement = selector.SearchForSelector;
1037 ReadOnlyCollection<IWebElement> elems;
1038 ReadOnlyCollection<GPALElement> GPALelems =
null;
1040 if (browser.UseOttoMagic)
1042 List<GPALElement> result =
null;
1044 if (
false == webElement?.IsShadowRoot)
1045 browser.MagicHelper.SwitchToElement(webElement.Css);
1047 if (
true == isPersistent)
1048 result = browser.MagicHelper
1049 .EvaluateAllPersistent(selectorPath.SelectorPath);
1051 result = browser.MagicHelper
1052 .EvaluateAll(selectorPath.SelectorPath);
1055 GPALelems =
new ReadOnlyCollection<GPALElement>(result);
1057 else if (
true == browser.UsePuppeteer)
1059 if (
false == webElement?.IsShadowRoot)
1060 browser.PuppeteerClient.InElement(webElement.Css).Execute();
1062 var result2 = Task.Run(() => browser.PuppeteerCommunicator.EvaluateSelector(selectorPath.SelectorPath));
1064 if (
null != result2)
1065 GPALelems =
new ReadOnlyCollection<GPALElement>(result2.Result);
1069 string xpath = selectorPath.SelectorPath;
1071 elems = webElement?.WebElement !=
null
1072 ? webElement.WebElement.FindElements(By.XPath(xpath))
1073 : browser.BrowserDriver.FindElements(By.XPath(xpath));
1077 if (0 == elems.Count &&
true == webElement?.IsClosedShadowRoot &&
false == isPersistent)
1078 return new ReadOnlyCollection<GPALElement>(
new List<GPALElement>() { webElement });
1080 GPALelems = ToGPALElements(browser, selector, elems);
1101 internal static ReadOnlyCollection<GPALElement> ToGPALElements(Browser browser, Selector selector, ReadOnlyCollection<IWebElement> elems)
1103 List<GPALElement> gPALElements =
new List<GPALElement>();
1105 if (0 < elems.Count)
1108 return arguments[0].map(function (element) {
1109 var rect = element.getBoundingClientRect();
1110 var style = window.getComputedStyle(element);
1113 tag: element.tagName ? element.tagName.toLowerCase() : null,
1114 text: element.innerText,
1115 enabled: false === element.disabled || undefined === element.disabled,
1116 selected: true === element.checked || true === element.selected,
1117 displayed: (0 < element.getClientRects().length) && 'hidden' !== style.visibility && 'none' !== style.display,
1118 x: Math.round(rect.left + window.pageXOffset),
1119 y: Math.round(rect.top + window.pageYOffset),
1120 w: Math.round(rect.width),
1121 h: Math.round(rect.height),
1122 href: element.getAttribute('href'),
1123 src: element.getAttribute('src'),
1124 value: undefined !== element.value ? element.value : element.getAttribute('value'),
1125 placeholder: element.getAttribute('placeholder'),
1126 type: element.getAttribute('type')
1131 string pageUrl = browser.BrowserDriver.Url;
1132 object answered = ((IJavaScriptExecutor)browser.BrowserDriver).ExecuteScript(script, elems.ToList());
1133 List<object> properties = (answered as IEnumerable<object>)?.ToList();
1135 for (
int idx = 0; idx < elems.Count; idx++)
1139 GPALElement gPALElement = properties?.Count == elems.Count && properties[idx] is Dictionary<string, object> bag
1140 ?
new GPALElement(elems[idx], selector.AttributeName, bag, pageUrl)
1141 : new GPALElement(elems[idx], selector.AttributeName);
1143 gPALElement.Browser = browser;
1144 gPALElement.Selector = selector;
1146 gPALElements.Add(gPALElement);
1150 return new ReadOnlyCollection<GPALElement>(gPALElements);
1163 private static ReadOnlyCollection<GPALElement> FindWebElementsByText(Browser browser, SelectorPathEntry selectorPath, Selector selector, GPALElement webElement =
null,
bool isPersistent =
false)
1165 bool searchForElement = selector.SearchForSelector;
1166 ReadOnlyCollection<IWebElement> elems;
1167 ReadOnlyCollection<GPALElement> GPALelems =
null;
1168 string xpathSelector = $
"//*[contains(text(), '{selectorPath.SelectorPath}')]";
1170 if (browser.UseOttoMagic)
1172 List<GPALElement> result =
null;
1174 if (
false == webElement?.IsShadowRoot)
1175 browser.MagicHelper.SwitchToElement(webElement.Css);
1177 if (
true == isPersistent)
1178 result = browser.MagicHelper
1179 .EvaluateAllPersistent(xpathSelector);
1181 result = browser.MagicHelper
1182 .EvaluateAll(xpathSelector);
1185 GPALelems =
new ReadOnlyCollection<GPALElement>(result);
1187 else if (
true == browser.UsePuppeteer)
1189 if (
false == webElement?.IsShadowRoot)
1190 browser.PuppeteerClient.InElement(webElement.Css).Execute();
1192 var result2 = Task.Run(() => browser.PuppeteerCommunicator.EvaluateSelector(xpathSelector));
1194 if (
null != result2)
1195 GPALelems =
new ReadOnlyCollection<GPALElement>(result2.Result);
1199 if (webElement !=
null)
1200 xpathSelector = $
".{xpathSelector}";
1202 elems = webElement?.WebElement !=
null
1203 ? webElement.WebElement.FindElements(By.XPath(xpathSelector))
1204 : browser.BrowserDriver.FindElements(By.XPath(xpathSelector));
1206 GPALelems = ToGPALElements(browser, selector, elems);
1222 private static ReadOnlyCollection<GPALElement> FindWebElementsByHRef(Browser browser, SelectorPathEntry selectorPath, Selector selector, GPALElement webElement =
null,
bool isPersistent =
false)
1224 bool searchForElement = selector.SearchForSelector;
1225 ReadOnlyCollection<IWebElement> elems;
1226 ReadOnlyCollection<GPALElement> GPALelems =
null;
1227 string cssSelector = $
"a[href*='{selectorPath.SelectorPath}']";
1229 if (browser.UseOttoMagic)
1231 List<GPALElement> result =
null;
1233 if (
false == webElement?.IsShadowRoot)
1234 browser.MagicHelper.SwitchToElement(webElement.Css);
1236 if (
true == isPersistent)
1237 result = browser.MagicHelper
1238 .QueryPersistentSelectors(cssSelector);
1240 result = browser.MagicHelper
1241 .QuerySelectors(cssSelector);
1244 GPALelems =
new ReadOnlyCollection<GPALElement>(result);
1246 else if (
true == browser.UsePuppeteer)
1248 if (
false == webElement?.IsShadowRoot)
1249 browser.PuppeteerClient.InElement(webElement.Css).Execute();
1251 var result2 = Task.Run(() => browser.PuppeteerCommunicator.EvaluateSelector(cssSelector));
1253 if (
null != result2)
1254 GPALelems =
new ReadOnlyCollection<GPALElement>(result2.Result);
1258 elems = webElement?.WebElement !=
null
1259 ? webElement.WebElement.FindElements(By.PartialLinkText(selectorPath.SelectorPath))
1260 : browser.BrowserDriver.FindElements(By.PartialLinkText(selectorPath.SelectorPath));
1262 GPALelems =
new ReadOnlyCollection<GPALElement>(
1263 elems.Select(e =>
new GPALElement(e, selector.AttributeName) { Browser = browser, Selector = selector }).ToList()
1279 private static ReadOnlyCollection<GPALElement> FindWebElementsByClassName(Browser browser, SelectorPathEntry selectorPath, Selector selector, GPALElement webElement =
null,
bool isPersistent =
false)
1281 bool searchForElement = selector.SearchForSelector;
1282 ReadOnlyCollection<IWebElement> elems;
1283 ReadOnlyCollection<GPALElement> GPALelems =
null;
1284 string cssSelector = $
".{selectorPath.SelectorPath}";
1286 if (browser.UseOttoMagic)
1288 List<GPALElement> result =
null;
1290 if (
false == webElement?.IsShadowRoot)
1291 browser.MagicHelper.SwitchToElement(webElement.Css);
1293 if (
true == isPersistent)
1294 result = browser.MagicHelper
1295 .QueryPersistentSelectors(cssSelector);
1297 result = browser.MagicHelper
1298 .QuerySelectors(cssSelector);
1301 GPALelems =
new ReadOnlyCollection<GPALElement>(result);
1303 else if (
true == browser.UsePuppeteer)
1305 if (
false == webElement?.IsShadowRoot)
1306 browser.PuppeteerClient.InElement(webElement.Css).Execute();
1308 var result2 = Task.Run(() => browser.PuppeteerCommunicator.EvaluateSelector(cssSelector));
1309 if (
null != result2)
1310 GPALelems =
new ReadOnlyCollection<GPALElement>(result2.Result);
1314 elems = webElement?.WebElement !=
null
1315 ? webElement.WebElement.FindElements(By.ClassName(selectorPath.SelectorPath))
1316 : browser.BrowserDriver.FindElements(By.ClassName(selectorPath.SelectorPath));
1318 GPALelems =
new ReadOnlyCollection<GPALElement>(
1319 elems.Select(e =>
new GPALElement(e, selector.AttributeName) { Browser = browser, Selector = selector }).ToList()
1335 private static ReadOnlyCollection<GPALElement> FindWebElementsByName(Browser browser, SelectorPathEntry selectorPath, Selector selector, GPALElement webElement =
null,
bool isPersistent =
false)
1337 bool searchForElement = selector.SearchForSelector;
1338 ReadOnlyCollection<IWebElement> elems;
1339 ReadOnlyCollection<GPALElement> GPALelems =
null;
1340 string cssSelector = $
"[name='{selectorPath.SelectorPath}']";
1342 if (browser.UseOttoMagic)
1344 List<GPALElement> result =
null;
1346 if (
false == webElement?.IsShadowRoot)
1347 browser.MagicHelper.SwitchToElement(webElement.Css);
1349 if (
true == isPersistent)
1350 result = browser.MagicHelper
1351 .QueryPersistentSelectors(cssSelector);
1353 result = browser.MagicHelper
1354 .QuerySelectors(cssSelector);
1357 GPALelems =
new ReadOnlyCollection<GPALElement>(result);
1359 else if (
true == browser.UsePuppeteer)
1361 if (
false == webElement?.IsShadowRoot)
1362 browser.PuppeteerClient.InElement(webElement.Css).Execute();
1364 var result2 = Task.Run(() => browser.PuppeteerCommunicator.EvaluateSelector(cssSelector));
1366 if (
null != result2)
1367 GPALelems =
new ReadOnlyCollection<GPALElement>(result2.Result);
1371 elems = webElement?.WebElement !=
null
1372 ? webElement.WebElement.FindElements(By.Name(selectorPath.SelectorPath))
1373 : browser.BrowserDriver.FindElements(By.Name(selectorPath.SelectorPath));
1375 GPALelems =
new ReadOnlyCollection<GPALElement>(
1376 elems.Select(e =>
new GPALElement(e, selector.AttributeName) { Browser = browser, Selector = selector }).ToList()
1393 private static ReadOnlyCollection<GPALElement> FindWebElementsByValue(Browser browser, SelectorPathEntry selectorPath, Selector selector, GPALElement webElement =
null,
bool isPersistent =
false)
1395 bool searchForElement = selector.SearchForSelector;
1396 ReadOnlyCollection<IWebElement> elems;
1397 ReadOnlyCollection<GPALElement> GPALelems =
null;
1398 string cssSelector = $
"[value='{selectorPath.SelectorPath}']";
1400 if (browser.UseOttoMagic)
1402 List<GPALElement> result =
null;
1404 if (
false == webElement?.IsShadowRoot)
1405 browser.MagicHelper.SwitchToElement(webElement.Css);
1407 if (
true == isPersistent)
1408 result = browser.MagicHelper
1409 .QueryPersistentSelectors(cssSelector);
1411 result = browser.MagicHelper
1412 .QuerySelectors(cssSelector);
1415 GPALelems =
new ReadOnlyCollection<GPALElement>(result);
1417 else if (
true == browser.UsePuppeteer)
1419 if (
false == webElement?.IsShadowRoot)
1420 browser.PuppeteerClient.InElement(webElement.Css).Execute();
1422 var result2 = Task.Run(() => browser.PuppeteerCommunicator.EvaluateSelector(cssSelector));
1423 if (
null != result2)
1424 GPALelems =
new ReadOnlyCollection<GPALElement>(result2.Result);
1428 elems = webElement?.WebElement !=
null
1429 ? webElement.WebElement.FindElements(By.CssSelector($
"[value='{selectorPath.SelectorPath}']"))
1430 : browser.BrowserDriver.FindElements(By.CssSelector($
"[value='{selectorPath.SelectorPath}']"));
1432 GPALelems =
new ReadOnlyCollection<GPALElement>(
1433 elems.Select(e =>
new GPALElement(e, selector.AttributeName) { Browser = browser, Selector = selector }).ToList()
1449 private static ReadOnlyCollection<GPALElement> FindWebElementsByPlaceholder(Browser browser, SelectorPathEntry selectorPath, Selector selector, GPALElement webElement =
null,
bool isPersistent =
false)
1451 bool searchForElement = selector.SearchForSelector;
1452 ReadOnlyCollection<IWebElement> elems;
1453 ReadOnlyCollection<GPALElement> GPALelems =
null;
1454 string cssSelector = $
"[placeholder='{selectorPath.SelectorPath}']";
1456 if (browser.UseOttoMagic)
1458 List<GPALElement> result =
null;
1460 if (
false == webElement?.IsShadowRoot)
1461 browser.MagicHelper.SwitchToElement(webElement.Css);
1463 if (
true == isPersistent)
1464 result = browser.MagicHelper
1465 .QueryPersistentSelectors(cssSelector);
1467 result = browser.MagicHelper
1468 .QuerySelectors(cssSelector);
1471 GPALelems =
new ReadOnlyCollection<GPALElement>(result);
1473 else if (
true == browser.UsePuppeteer)
1475 if (
false == webElement?.IsShadowRoot)
1476 browser.PuppeteerClient.InElement(webElement.Css).Execute();
1478 var result2 = Task.Run(() => browser.PuppeteerCommunicator.EvaluateSelector(cssSelector));
1479 if (
null != result2)
1480 GPALelems =
new ReadOnlyCollection<GPALElement>(result2.Result);
1484 elems = webElement?.WebElement !=
null
1485 ? webElement.WebElement.FindElements(By.CssSelector($
"[placeholder='{selectorPath.SelectorPath}']"))
1486 : browser.BrowserDriver.FindElements(By.CssSelector($
"[placeholder='{selectorPath.SelectorPath}']"));
1488 GPALelems =
new ReadOnlyCollection<GPALElement>(
1489 elems.Select(e =>
new GPALElement(e, selector.AttributeName) { Browser = browser, Selector = selector }).ToList()
1505 public static void PublishToEventHandler(Browser browser, Selector selector, List<GPALElement> webElements)
1508 if (
true == selector.DeleteMe)
1509 msg = $
"Ignoring selector [{selector.Name}] marked for deletion. Continuing.";
1510 else if (
null == webElements || 0 == webElements.Count)
1511 msg = $
"No elements found for selector [{selector.Name}]. Continuing.";
1513 GPAL.PublishSimpleEvent(GPALEventType.WARNING, msg, browser, GPALObjectType.Browser);
1525 public static List<GPALElement> WaitFor(Browser browser,
int timeoutInTicks, out
bool matchedAll)
1527 bool matchedAll2 =
true;
1528 List<GPALElement> foundElements =
null;
1529 List<GPALElement> matchedElements;
1531 foundElements =
new List<GPALElement>();
1533 foreach (Selector selector
in browser.CurrentUOW.WithSelectorList)
1535 if (SelectorType.Selector != selector.SelectorType)
1538 WaitFor(browser, selector, timeoutInTicks, out matchedAll2, out matchedElements);
1542 if (
true == selector.DeleteMe ||
null == matchedElements)
1544 PublishToEventHandler(browser, selector, matchedElements);
1550 foreach (GPALElement webElement
in matchedElements)
1552 foundElements.Add(webElement);
1554 if (++rowCount >= browser.CurrentUOW.WithAllThatMatch)
1559 matchedAll = matchedAll2;
1560 return foundElements;
1575 private static List<GPALElement> WaitFor(Browser browser, Selector selector,
int timeoutInTicks, out
bool matchedAll, out List<GPALElement> matchedElements, GPALElement webElement =
null)
1577 bool matchedAll2 =
true;
1578 List<GPALElement> matchedElements2;
1579 ReadOnlyCollection<GPALElement> elements;
1580 int orgTimeoutInTicks = timeoutInTicks;
1582 if (WaitTime.Forever == timeoutInTicks)
1583 timeoutInTicks = Int32.MaxValue;
1585 GPAL.PublishSimpleEvent(GPALEventType.INFO, $
"Waiting up to [{timeoutInTicks}] ms for elements matching selector [{selector.Name}][{selector.SelectorPath}]", browser, GPALObjectType.Browser);
1587 var sw = Stopwatch.StartNew();
1590 elements = FindWebElements(browser, browser.CurrentUOW, selector,
1591 out matchedAll2, out matchedElements2, webElement,
false);
1594 if (0 == matchedElements2?.Count && WaitTime.Immediate != timeoutInTicks && sw.ElapsedMilliseconds < timeoutInTicks)
1595 Thread.Sleep((
int)Math.Min(1000, timeoutInTicks - sw.ElapsedMilliseconds));
1597 }
while (sw.ElapsedMilliseconds < timeoutInTicks &&
1598 (matchedElements2 ==
null || matchedElements2.Count == 0));
1600 matchedAll = matchedAll2;
1601 matchedElements = matchedElements2;
1604 if (ElementState.NotSet != browser.CurrentUOW.WaitForElementState)
1605 foreach (GPALElement gPALElement
in matchedElements2)
1606 matchedAll &= WaitFor(browser, gPALElement, browser.CurrentUOW.WaitForElementState, orgTimeoutInTicks);
1608 return elements?.ToList<GPALElement>();
1619 public static bool WaitFor(Browser browser, GPALElement element, ElementState state,
int timeoutInTicks)
1621 const int defaultStateWaitInTicks = 3000;
1623 if (WaitTime.Forever == timeoutInTicks)
1625 timeoutInTicks = Int32.MaxValue;
1627 else if (timeoutInTicks < 0)
1629 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);
1630 timeoutInTicks = defaultStateWaitInTicks;
1635 dynamic retryElement = element;
1636 int tmpTimeoutInTicks = timeoutInTicks;
1642 int sleepTime = Math.Min(1000, tmpTimeoutInTicks);
1644 Thread.Sleep(sleepTime);
1646 if (IsElementInState(browser, element, state))
1649 tmpTimeoutInTicks -= sleepTime;
1651 catch (StaleElementReferenceException sereex)
1653 string message = $
"StaleElementReferenceException while waiting for state [{state}]: [{sereex.Message}]";
1655 retry = ResolveWebElementIssue(browser, browser.CurrentUOW, ref retryElement, sereex, ref retries, message);
1657 element = retryElement;
1659 catch (GPALException)
1663 catch (Exception ex)
1665 GPAL.PublishSimpleEvent(GPALEventType.EXCEPTION, $
"Error while waiting for state [{state}]", element, GPALObjectType.Other, ex);
1668 }
while (tmpTimeoutInTicks > 0 && retry);
1670 GPAL.PublishSimpleEvent(GPALEventType.WARNING, $
"Element not in state [{state}] within [{timeoutInTicks}] ms.", element, GPALObjectType.Other);
1681 private static bool IsElementInState(Browser browser, GPALElement element, ElementState state)
1687 case ElementState.Visible:
1688 return element.Displayed;
1689 case ElementState.Hidden:
1690 return !element.Displayed;
1691 case ElementState.Enabled:
1692 return element.Enabled;
1693 case ElementState.Disabled:
1694 return !element.Enabled;
1695 case ElementState.Checked:
1696 return element.Selected || element.GetAttribute(
"checked") ==
"true";
1697 case ElementState.Unchecked:
1698 return !element.Selected && element.GetAttribute(
"checked") !=
"true";
1699 case ElementState.Selected:
1700 return element.Selected;
1701 case ElementState.Unselected:
1702 return !element.Selected;
1703 case ElementState.Clickable:
1704 return IsElementClickable(element);
1705 case ElementState.Editable:
1706 return IsElementEditable(element);
1708 GPAL.PublishSimpleEvent(GPALEventType.WARNING, $
"Unsupported element state [{state}].", element, GPALObjectType.Other);
1712 catch (GPALException)
1716 catch (Exception ex)
1718 GPAL.PublishSimpleEvent(GPALEventType.EXCEPTION, $
"Error checking state [{state}]", element, GPALObjectType.Other, ex);
1729 public static bool FillInFrom(Browser browser,
string useText, WriteMode writeMode, GPALElement element =
null)
1731 bool retVal =
false;
1732 ReadOnlyCollection<GPALElement> elems =
null;
1733 List<GPALElement> matchedElements =
null;
1735 foreach (Selector selector
in browser.CurrentUOW.WithSelectorList)
1737 if (SelectorType.Selector != selector.SelectorType)
1740 if (
null == element)
1741 elems = ElementHelper.FindWebElements(browser, browser.CurrentUOW, selector, out
bool matchedAll, out matchedElements);
1744 if (
true == selector.DeleteMe ||
null == elems)
1746 PublishToEventHandler(browser, selector, elems?.ToList());
1750 if (
null != element)
1752 var tmpElems =
new List<GPALElement>();
1753 tmpElems.Add(element);
1754 elems =
new ReadOnlyCollection<GPALElement>(tmpElems);
1756 else if (
null != matchedElements)
1757 elems =
new ReadOnlyCollection<GPALElement>(matchedElements);
1759 if (
true == elems.Any())
1760 handleFillIn(elems);
1762 if (
true == retVal &&
null != browser.CurrentUOW.CallAfterFillIn &&
true == elems.Any())
1764 UnitOfWork safeUOW = browser.CurrentUOW;
1765 CallIfStatus handled = 0;
1769 IGPALGrid<string> tokens = GPAL.GridForType<
string>();
1770 tokens.AddRow(
new List<string> { useText });
1772 GPAL.PublishSimpleEvent(GPALEventType.INFO, $
"Invoking CallAfterFillIn [{browser.CurrentUOW.CallAfterFillIn.Method.Name}]", browser, GPALObjectType.Browser);
1773 handled = browser.CurrentUOW.CallAfterFillIn(browser, tokens, 1);
1777 browser.CurrentUOW = safeUOW;
1790 if (CallIfStatus.Terminate == handled)
1792 string str = $
"CallAfterFillIn handler [{browser.CurrentUOW.AppCallAfterFillIn.GetInvocationList()[0].Method.Name}] requested program termination.";
1793 GPAL.PublishSimpleEvent(GPALEventType.INFO, str, browser, GPALObjectType.Browser);
1794 throw new GPALException($
"" + str);
1798 void handleFillIn(ReadOnlyCollection<GPALElement> elems)
1802 foreach (GPALElement elem
in elems)
1804 string type = elem.GetAttribute(
"type");
1805 string abbreviatedText = useText[0] +
"..." + useText[useText.Length - 1];
1807 if (
null == type ||
false == type.Equals(
"password"))
1809 if (GPALEventType.DEBUG == (GPALEventType.DEBUG & GPAL.GPALSettings.DebugEvents) || GPALEventType.DEBUG == (GPALEventType.DEBUG & GPAL.GPALSettings.ConsoleEvents))
1810 GPAL.PublishSimpleEvent(GPALEventType.DEBUG, $
"[{writeMode}] text [{useText}] in element [{elem.TagName}][{selector.Name}]", browser, GPALObjectType.Browser);
1812 GPAL.PublishSimpleEvent(GPALEventType.INFO, $
"[{writeMode}] text [{abbreviatedText}] in element [{elem.TagName}][{selector.Name}]", browser, GPALObjectType.Browser);
1815 GPAL.PublishSimpleEvent(GPALEventType.INFO, $
"[{writeMode}] XXXpasswordXXX in element [{elem.TagName}][{selector.Name}]", browser, GPALObjectType.Browser);
1821 if (
false == browser.BrowserSettings.UseHeadless
1822 && (
"GPALElement" == elem.TagName
1823 ||
true == browser.BrowserSettings.UseHardware ||
true == GPAL.GPALSettings.UseHardware))
1824 HardwareFillInFrom(browser, elem, selector.OffsetX, selector.OffsetY, useText, writeMode);
1825 else if (
"GPALElement" == elem.TagName)
1827 FillInFrom(browser, ToViewportPoint(browser, elem, selector), useText, writeMode);
1828 else if (browser.UseOttoMagic)
1830 if (
false == browser.BrowserSettings.UseHeadless && InteractionType.Hardware == selector.InteractionType)
1831 ElementHelper.HardwareFillInFrom(browser, elem, selector.OffsetX, selector.OffsetY, useText, writeMode);
1835 case WriteMode.Append:
1836 browser.MagicHelper.FillInAppend(elem.Css, useText);
1838 case WriteMode.Insert:
1839 browser.MagicHelper.FillInInsert(elem.Css, useText);
1841 case WriteMode.Overwrite:
1842 browser.MagicHelper.FillInOverwrite(elem.Css, useText);
1848 if (++rowCount >= browser.CurrentUOW.WithAllThatMatch)
1851 else if (
true == browser.UsePuppeteer)
1853 if (
false == browser.BrowserSettings.UseHeadless && InteractionType.Hardware == selector.InteractionType)
1854 ElementHelper.HardwareFillInFrom(browser, elem, selector.OffsetX, selector.OffsetY, useText, writeMode);
1858 case WriteMode.Append:
1859 browser.PuppeteerClient.FillInAppend(elem).WithText(useText).Execute();
1861 case WriteMode.Insert:
1862 browser.PuppeteerClient.FillInInsert(elem).WithText(useText).Execute();
1864 case WriteMode.Overwrite:
1865 browser.PuppeteerClient.FillInOverwrite(elem).WithText(useText).Execute();
1871 if (++rowCount >= browser.CurrentUOW.WithAllThatMatch)
1874 else if (
true == browser.UseSelenium)
1876 if (
false == browser.BrowserSettings.UseHeadless && InteractionType.Hardware == selector.InteractionType)
1877 ElementHelper.HardwareFillInFrom(browser, elem, selector.OffsetX, selector.OffsetY, useText, writeMode);
1879 SeleniumFillInFrom(browser, elem, useText, writeMode);
1895 public static void SeleniumSendKeys(Browser browser, GPALElement element,
byte keyCode)
1903 keyToSend = Keys.Alt;
1906 keyToSend = Keys.Delete;
1909 keyToSend = Keys.F10;
1912 keyToSend = Keys.PageUp;
1915 keyToSend = Keys.PageDown;
1918 keyToSend = Keys.End;
1921 keyToSend = Keys.Home;
1924 keyToSend = Keys.ArrowDown;
1927 keyToSend = Keys.Tab;
1930 keyToSend = Keys.Return;
1933 keyToSend = Keys.Shift;
1936 keyToSend = Keys.LeftShift;
1939 keyToSend = Keys.LeftControl;
1942 keyToSend = Keys.Control;
1945 keyToSend = Keys.Space;
1948 keyToSend = Keys.Backspace;
1952 keyToSend = ((char)keyCode).ToString();
1957 element.SendKeys(keyToSend);
1973 private static readonly HashSet<string> valueOnlyInputTypes =
new HashSet<string>
1975 "color",
"date",
"datetime-local",
"month",
"week",
"time",
1977 public static void SeleniumFillInFrom(Browser browser, GPALElement elem,
string textToUse, WriteMode writeMode)
1980 if (
true == valueOnlyInputTypes.Contains(elem.Type))
1982 JavaScriptFillInFrom(browser, elem, textToUse, writeMode);
1986 textToUse = ElementHelper.GetKeystrokeValueForInput(elem, textToUse);
1990 if (WriteMode.Overwrite == writeMode)
1992 else if (WriteMode.Append == writeMode)
1994 textToUse = elem.Text + textToUse;
1998 elem.SendKeys(textToUse);
2000 catch (GPALException)
2004 catch (Exception ex)
2006 if (
false == GPAL.NoFallbackRecoveryActions)
2008 GPAL.PublishSimpleEvent(GPALEventType.EXCEPTION,
"Unable to fill in text. Falling back on javascript.", browser, GPALObjectType.Browser, ex);
2009 JavaScriptFillInFrom(browser, elem, textToUse, writeMode);
2025 public static void HardwareFillInFrom(Browser browser, GPALElement webElement,
int offsetX,
int offsetY,
string text, WriteMode writeMode)
2027 string inputType = webElement.Type?.ToLowerInvariant() ??
"";
2030 bool isSpecialInput =
new[] {
"date",
"time",
"datetime-local",
"month",
"week",
"color" }
2031 .Contains(inputType);
2033 HardwareFocus(browser, webElement, offsetX, offsetY);
2038 if (
true == _lastPlacementQuestionable)
2040 GPAL.PublishSimpleEvent(GPALEventType.WARNING,
2041 $
"Skipping fill-in [{browser.CurrentUOW?.CurrentSelector?.Name}], questionable element placement.",
2042 browser, GPALObjectType.Browser);
2051 if (
"week" != inputType && BrowserType.FireFox != browser.BrowserSettings.BrowserType)
2054 HardwareHelper.SendChar(
'a', ModifierKeys.Control,
true);
2056 HardwareHelper.SendKey(GPAL.VK_DELETE);
2060 else if (WriteMode.Overwrite == writeMode)
2062 HardwareHelper.SendChar(
'a', ModifierKeys.Control,
true);
2066 else if (WriteMode.Append == writeMode)
2068 HardwareHelper.SendKey(GPAL.VK_END, ModifierKeys.Control);
2071 else if (WriteMode.Insert == writeMode)
2073 HardwareHelper.SendKey(GPAL.VK_HOME, ModifierKeys.Control);
2077 string textToType = GetKeystrokeValueForInput(webElement, text);
2082 HardwareFillInColor(textToType.TrimStart(
'#'), browser.BrowserSettings.BrowserType);
2084 case "datetime-local":
2085 HardwareFillInDateTimeLocal(textToType, browser.BrowserSettings.BrowserType);
2088 HardwareFillInWeek(textToType);
2091 HardwareFillInMonth(textToType);
2094 HardwareHelper.SendString(textToType);
2103 public static void HardwareFillInMonth(
string isoValue)
2105 var parts = isoValue.Split(
'/');
2106 string year = parts[0];
2107 string month = parts[1];
2108 HardwareHelper.SendString(month);
2110 HardwareHelper.SendKey(GPAL.VK_TAB);
2111 HardwareHelper.SendString(year);
2118 public static void HardwareFillInWeek(
string isoValue)
2120 if (isoValue.Contains(
"-W"))
2122 var parts = isoValue.Split(
'-');
2123 string year = parts[0];
2124 string week = parts[1].TrimStart(
'W');
2125 HardwareHelper.SendString($
"{week}{year}");
2133 public static void HardwareFillInDateTimeLocal(
string isoValue, BrowserType browserType)
2136 if (!DateTime.TryParseExact(isoValue,
2139 System.Globalization.DateTimeStyles.None,
2145 string datePart = dt.ToString(
"MM/dd/yyyy");
2146 string timePart = dt.ToString(
"hh:mm");
2147 string amPm = dt.ToString(
"tt");
2150 HardwareHelper.SendString(datePart);
2154 if (BrowserType.FireFox != browserType)
2157 HardwareHelper.SendKey(GPAL.VK_TAB);
2162 HardwareHelper.SendString(timePart);
2166 HardwareHelper.SendString(amPm);
2175 public static void HardwareFillInColor(
string hexValue, BrowserType browserType)
2177 if (hexValue.Length != 6)
2180 hexValue =
"000000";
2184 int r = Convert.ToInt32(hexValue.Substring(0, 2), 16);
2185 int g = Convert.ToInt32(hexValue.Substring(2, 2), 16);
2186 int b = Convert.ToInt32(hexValue.Substring(4, 2), 16);
2190 int tabsToFirstField = BrowserType.FireFox == browserType ? 7 : 3;
2192 for (
int tab = 0; tab < tabsToFirstField; tab++)
2193 HardwareHelper.SendKey(GPAL.VK_TAB);
2196 HardwareHelper.SendString(r.ToString());
2197 HardwareHelper.SendKey(GPAL.VK_TAB);
2200 HardwareHelper.SendString(g.ToString());
2201 HardwareHelper.SendKey(GPAL.VK_TAB);
2204 HardwareHelper.SendString(b.ToString());
2208 HardwareHelper.SendKey(GPAL.VK_RETURN);
2216 public static string GetKeystrokeValueForInput(GPALElement webElement,
string isoValue)
2218 if (
string.IsNullOrEmpty(isoValue) || webElement?.TagName?.ToLowerInvariant() !=
"input")
2221 string type = webElement.Type?.ToLowerInvariant() ??
"";
2229 if (DateTime.TryParseExact(isoValue,
"yyyy-MM-dd",
null, System.Globalization.DateTimeStyles.None, out DateTime dt))
2230 return dt.ToString(
"MM/dd/yyyy");
2233 case "datetime-local":
2235 if (DateTime.TryParseExact(isoValue.Replace(
"T",
" "),
"yyyy-MM-dd HH:mm",
null, System.Globalization.DateTimeStyles.None, out dt))
2236 return dt.ToString(
"MM/dd/yyyy HH:mm");
2237 return isoValue.Replace(
"T",
" ");
2241 return isoValue.Replace(
"-",
"/");
2251 if (DateTime.TryParseExact(isoValue,
new[] {
"HH:mm",
"H:mm",
"HH:mm:ss" }, System.Globalization.CultureInfo.InvariantCulture, System.Globalization.DateTimeStyles.None, out dt))
2252 return dt.ToString(
"hh:mm tt", System.Globalization.CultureInfo.InvariantCulture);
2271 public static void JavaScriptFillInFrom(Browser browser, GPALElement webElement,
string text, WriteMode writeMode)
2273 IWebDriver browserDriver = browser.BrowserDriver;
2275 BrowserHelper.ExecuteJavaScript(browser,
"arguments[0].focus();", webElement.WebElement);
2280 if (WriteMode.Overwrite == writeMode)
2281 BrowserHelper.ExecuteJavaScript(browser,
"arguments[0].value = arguments[1]", webElement.WebElement, text);
2282 else if (WriteMode.Append == writeMode)
2283 BrowserHelper.ExecuteJavaScript(browser,
"arguments[0].value = arguments[0].value + arguments[1]", webElement.WebElement, text);
2284 else if (WriteMode.Insert == writeMode)
2285 BrowserHelper.ExecuteJavaScript(browser,
"arguments[0].value = arguments[1] + arguments[0].value", webElement.WebElement, text);
2290 BrowserHelper.ExecuteJavaScript(browser,
2291 "arguments[0].dispatchEvent(new Event('input', { bubbles: true })); arguments[0].dispatchEvent(new Event('change', { bubbles: true }));",
2292 webElement.WebElement);
2300 public static async
void PuppeteerFillInFrom(Browser browser, GPALElement webElement,
string text, WriteMode writeMode)
2302 BrowserSettings browserSettings = browser.BrowserSettings;
2304 PuppeteerCommunicator puppeteerCommunicator = browserSettings.PuppeteerCommunicator;
2310 case WriteMode.Append:
2311 browser.PuppeteerClient.FillInAppend(webElement).WithText(text).Execute();
2313 case WriteMode.Insert:
2314 browser.PuppeteerClient.FillInInsert(webElement).WithText(text).Execute();
2316 case WriteMode.Overwrite:
2317 browser.PuppeteerClient.FillInOverwrite(webElement).WithText(text).Execute();
2320 VkCodeToDomKeyConverter.TryConvertAsciiToVkCode(
'\t', out
byte vkCode);
2321 VkCodeToDomKeyConverter.TryConvertVkCodeToDomKey(vkCode, out
string key, out
string code);
2322 await puppeteerCommunicator.SendKey(key, code, vkCode);
2332 public static void JavaScriptSendKey(Browser browser, GPALElement webElement,
byte keycode)
2334 IWebDriver browserDriver = browser.BrowserDriver;
2335 if (
true == VkCodeToDomKeyConverter.TryConvertVkCodeToDomKey(keycode, out
string key, out
string code))
2337 string sendKeyScript =
@"
2338 function isElementEditable(target) {
2339 if (!target || !(target instanceof Element)) return false;
2340 if (target.disabled || target.readOnly) return false;
2342 const tag = target.tagName.toUpperCase();
2343 if (tag === 'TEXTAREA') return true;
2344 if (tag === 'SELECT') return true;
2345 if (tag === 'INPUT') {
2346 const editableTypes = new Set(['text','email','password','search','number','url','tel',
2347 'date','datetime-local','month','time','week','color']);
2348 return editableTypes.has(target.type.toLowerCase());
2350 if (target.isContentEditable === true ||
2351 target.getAttribute('contenteditable') === 'true' ||
2352 target.ownerDocument?.designMode === 'on') {
2355 const style = window.getComputedStyle(target);
2356 if (style.userModify === 'read-write' || style.webkitUserModify === 'read-write' ||
2357 style.cursor === 'text') {
2363 // === Parameters passed from C# ===
2364 const key = arguments[0]; // e.g. 'a', 'Enter', 'Tab'
2365 const code = arguments[1]; // e.g. 'KeyA', 'Enter'
2366 const keyCode = arguments[2]; // numeric keyCode if needed (mostly legacy)
2368 let target = document.activeElement;
2369 if (!target || target === document.body) {
2370 target = document; // fallback
2383 // 1. Key events (for listeners)
2384 target.dispatchEvent(new KeyboardEvent('keydown', eventInit));
2386 if (key.length === 1) { // printable char
2387 target.dispatchEvent(new KeyboardEvent('keypress', eventInit));
2390 // 2. Actually insert text (this is what most sites need)
2391 const isEditable = isElementEditable(target);
2393 if (target.tagName === 'INPUT' || target.tagName === 'TEXTAREA') {
2394 const start = target.selectionStart ?? target.value.length;
2395 const end = target.selectionEnd ?? target.value.length;
2396 target.value = target.value.slice(0, start) + key + target.value.slice(end);
2397 target.selectionStart = target.selectionEnd = start + key.length;
2399 else if (target.isContentEditable) {
2400 // Simpler fallback for contenteditable
2401 document.execCommand('insertText', false, key); // still works in many places
2404 // Fire input event (React, Vue, Angular, etc. listen to this)
2405 target.dispatchEvent(new InputEvent('input', {
2409 inputType: 'insertText',
2413 // Also fire change for some frameworks
2414 target.dispatchEvent(new Event('change', { bubbles: true }));
2418 target.dispatchEvent(new KeyboardEvent('keyup', eventInit));
2420 // Special case: Enter on button/submit
2421 if (key === 'Enter' && (target.tagName === 'BUTTON' ||
2422 (target.tagName === 'INPUT' && (target.type === 'submit' || target.type === 'button')))) {
2427 console.error('SendKey script error:', e);
2432 BrowserHelper.ExecuteJavaScript(browser, sendKeyScript, key, code, keycode.ToString());
2443 public static void Focus(Browser browser)
2445 List<GPALElement> matchedElements;
2447 foreach (Selector selector
in browser.CurrentUOW.WithSelectorList)
2449 if (SelectorType.Selector != selector.SelectorType)
2452 ElementHelper.FindWebElements(browser, browser.CurrentUOW, selector, out
bool matchedAll, out matchedElements);
2455 if (
true == selector.DeleteMe ||
null == matchedElements)
2457 PublishToEventHandler(browser, selector, matchedElements?.ToList());
2463 foreach (GPALElement elem
in matchedElements)
2467 if (
false == browser.BrowserSettings.UseHeadless && (
true == browser.BrowserSettings.UseHardware ||
true == GPAL.GPALSettings.UseHardware))
2469 HardwareFocus(browser, elem, selector.OffsetX, selector.OffsetY);
2471 else if (
true == IsElementEditable(elem))
2474 Click(browser, selector, elem, ClickType.LeftClick, ModifierKeys.NONE);
2476 else if (
true == browser.UseOttoMagic)
2478 browser.MagicHelper.Focus(elem.Css);
2480 else if (
true == browser.UsePuppeteer)
2482 browser.PuppeteerClient.Focus(elem.Css).Execute();
2484 else if (
true == browser.UseSelenium)
2486 if (Enums.InteractionType.JavaScript == selector.InteractionType ||
true == browser.BrowserSettings.UseJavaScript)
2488 JavaScriptFocus(browser, elem);
2490 else if (elem.TagName.ToLower().Equals(
"input"))
2492 elem.SendKeys(OpenQA.Selenium.Keys.Shift);
2499 new Actions(browser.BrowserDriver).MoveToElement((IWebElement)elem.WebElement).Perform();
2501 catch (GPALException)
2505 catch (Exception ex)
2507 if (
false == GPAL.NoFallbackRecoveryActions)
2509 GPAL.PublishSimpleEvent(GPALEventType.EXCEPTION, $
"Selenium threw an excaption on selector [{selector.Name}], falling back to javascript.", browser, GPALObjectType.Browser, ex);
2510 ElementHelper.ScrollIntoView(browser, elem);
2513 GPAL.PublishSimpleEvent(GPALEventType.EXCEPTION, $
"Selenium threw an excaption on selector [{selector.Name}], continuing.", browser, GPALObjectType.Browser, ex);
2518 if (++rowCount >= browser.CurrentUOW.WithAllThatMatch)
2530 private static void JavaScriptFocus(Browser browser, GPALElement webElement)
2532 IWebDriver browserDriver = browser.BrowserDriver;
2534 string forceFocusScript =
$@"
2535 function forceFocus(el) {{
2536 // Remember original state
2537 const wasDisabled = el.disabled;
2538 const originalTabIndex = el.getAttribute('tabindex');
2541 // 1. Enable if disabled
2542 if (wasDisabled) el.disabled = false;
2544 // 2. Make sure it can receive focus
2545 if (el.tabIndex === -1 || !el.hasAttribute('tabindex')) {{
2546 el.setAttribute('tabindex', '0');
2550 el.focus({{ preventScroll: true }});
2552 // 4. Small delay + aggressive fallback (helps with buttons, React, Shadow DOM, etc.)
2554 if (document.activeElement !== el) {{
2557 // Dispatch synthetic focus events
2558 el.dispatchEvent(new FocusEvent('focusin', {{ bubbles: true }}));
2559 el.dispatchEvent(new FocusEvent('focus', {{ bubbles: false }}));
2564 return document.activeElement === el;
2569 // Restore original state
2570 if (wasDisabled) el.disabled = true;
2571 if (originalTabIndex !== null) {{
2572 el.setAttribute('tabindex', originalTabIndex);
2573 }} else if (el.getAttribute('tabindex') === '0') {{
2574 el.removeAttribute('tabindex');
2578 forceFocus(arguments[0]);
2580 BrowserHelper.ExecuteJavaScript(browser, forceFocusScript, webElement.WebElement);
2586 public static void Hide(Browser browser)
2588 List<GPALElement> matchedElements;
2590 foreach (Selector selector
in browser.CurrentUOW.WithSelectorList)
2592 if (SelectorType.Selector != selector.SelectorType)
2595 ReadOnlyCollection<GPALElement> elems = ElementHelper.FindWebElements(browser, browser.CurrentUOW, selector, out
bool matchedAll, out matchedElements);
2598 if (
true == selector.DeleteMe ||
null == elems || 0 == elems.Count)
2600 PublishToEventHandler(browser, selector, elems?.ToList());
2604 if (
null != matchedElements)
2605 elems =
new ReadOnlyCollection<GPALElement>(matchedElements);
2607 foreach (GPALElement elem
in elems)
2608 Hide(browser, elem);
2617 public static void Hide(Browser browser, GPALElement element)
2619 if (
true == browser.UseOttoMagic)
2620 browser.MagicHelper.HideElement(element.Css);
2621 else if (
true == browser.UsePuppeteer)
2622 browser.PuppeteerClient.HideElement(element.ElementBackendNodeId.ToString());
2624 BrowserHelper.ExecuteJavaScript(browser,
"arguments[0].style.display = 'none';", element.WebElement);
2633 public static void SetAttribute(Browser browser,
string attribute,
string value)
2635 List<GPALElement> matchedElements;
2637 foreach (Selector selector
in browser.CurrentUOW.WithSelectorList)
2639 if (SelectorType.Selector != selector.SelectorType)
2642 ReadOnlyCollection<GPALElement> elems = ElementHelper.FindWebElements(browser, browser.CurrentUOW, selector, out
bool matchedAll, out matchedElements);
2645 if (
true == selector.DeleteMe ||
null == elems || 0 == elems.Count)
2647 PublishToEventHandler(browser, selector, elems?.ToList());
2651 if (
null != matchedElements)
2652 elems =
new ReadOnlyCollection<GPALElement>(matchedElements);
2654 foreach (GPALElement elem
in elems)
2655 SetAttribute(browser, elem, attribute, value);
2667 public static void SetAttribute(Browser browser, GPALElement element,
string attribute,
string value)
2669 if (
true == browser.UseOttoMagic)
2670 browser.MagicHelper.SetAttribute(element.Css, attribute, value);
2671 else if (
true == browser.UsePuppeteer)
2672 browser.PuppeteerClient.SetAttribute(element.ElementBackendNodeId.ToString()).WithAttribute(attribute).WithValue(value).Execute();
2674 BrowserHelper.ExecuteJavaScript(browser,
"arguments[0].setAttribute(arguments[1], arguments[2]);", element.WebElement, attribute, value);
2682 public static void SetValueFromElement(Browser browser, Selector srcSelector)
2684 List<GPALElement> matchedElements;
2686 ReadOnlyCollection<GPALElement> srcElems = ElementHelper.FindWebElements(browser, browser.CurrentUOW, srcSelector, out
bool srcMatchedAll, out List<GPALElement> srcMatchedElements);
2688 if (
null != srcMatchedElements)
2689 srcElems =
new ReadOnlyCollection<GPALElement>(srcMatchedElements);
2691 if (
null == srcElems || 0 == srcElems.Count)
2694 GPALElement srcElem = srcElems[0];
2696 foreach (Selector selector
in browser.CurrentUOW.WithSelectorList)
2698 if (SelectorType.Selector != selector.SelectorType)
2701 ReadOnlyCollection<GPALElement> elems = ElementHelper.FindWebElements(browser, browser.CurrentUOW, selector, out
bool matchedAll, out matchedElements);
2704 if (
true == selector.DeleteMe ||
null == elems || 0 == elems.Count)
2706 PublishToEventHandler(browser, selector, elems?.ToList());
2710 if (
null != matchedElements)
2711 elems =
new ReadOnlyCollection<GPALElement>(matchedElements);
2713 foreach (GPALElement elem
in elems)
2714 SetValueFromElement(browser, elem, srcElem);
2725 public static void SetValueFromElement(Browser browser, GPALElement element, GPALElement srcElement)
2727 if (
true == browser.UseOttoMagic)
2728 browser.MagicHelper.SetValueFromElement(srcElement.Css, element.Css);
2729 else if (
true == browser.UsePuppeteer)
2730 browser.PuppeteerClient.WithElementId(srcElement.Css).SetValueFrom(element.Css).Execute();
2732 BrowserHelper.ExecuteJavaScript(browser,
2733 "arguments[0].value = arguments[1].value; arguments[0].dispatchEvent(new Event('input', {bubbles: true})); arguments[0].dispatchEvent(new Event('change', {bubbles: true}));",
2734 element.WebElement, srcElement.WebElement);
2754 public static void MoveTo(Browser browser)
2756 List<GPALElement> matchedElements;
2757 UnitOfWork currentUOW = browser.CurrentUOW;
2759 foreach (Selector selector
in currentUOW.WithSelectorList)
2761 if (SelectorType.Selector != selector.SelectorType)
2764 ReadOnlyCollection<GPALElement> elems = ElementHelper.FindWebElements(browser, currentUOW, selector, out
bool matchedAll, out matchedElements);
2767 if (
true == selector.DeleteMe ||
null == elems || 0 == elems.Count)
2769 PublishToEventHandler(browser, selector, elems?.ToList());
2773 if (
null != matchedElements)
2774 elems =
new ReadOnlyCollection<GPALElement>(matchedElements);
2778 foreach (GPALElement elem
in elems)
2783 if (0 == elem.Size.Width && 0 == elem.Size.Height)
2785 GPAL.PublishSimpleEvent(GPALEventType.ERROR, $
"Element has Zero Width and Height, skipping.", browser, GPALObjectType.Browser);
2793 if (
false == browser.BrowserSettings.UseHeadless
2794 && (
"GPALElement" == elem.TagName
2795 ||
true == browser.BrowserSettings.UseHardware
2796 ||
true == GPAL.GPALSettings.UseHardware
2797 || InteractionType.Hardware == selector.InteractionType))
2799 HardwareMoveTo(browser, elem, selector);
2801 else if (
"GPALElement" == elem.TagName)
2805 MoveTo(browser, elem, selector);
2807 else if (
true == browser.UseOttoMagic ||
true == browser.UsePuppeteer)
2809 MoveTo(elem, selector.OffsetX, selector.OffsetY, browser);
2811 else if (Enums.InteractionType.JavaScript == selector.InteractionType ||
true == browser.BrowserSettings.UseJavaScript ||
true == browser.BrowserSettings.UseHeadless)
2813 if (
null != currentUOW.ShadowRoot)
2814 JavaScriptHover(browser, currentUOW.ShadowRoot);
2816 JavaScriptHover(browser, elem);
2820 MoveTo(elem, selector.OffsetX, selector.OffsetY, browser);
2823 if (++rowCount >= browser.CurrentUOW.WithAllThatMatch)
2838 private static void MoveTo(Browser browser, GPALElement element, Selector selector)
2840 Point point = ToViewportPoint(browser, element, selector);
2842 if (
true == browser.UsePuppeteer)
2843 browser.PuppeteerClient.MoveTo(point).Execute();
2844 else if (
true == browser.UseSelenium)
2845 new Actions(browser.BrowserDriver).MoveToLocation(point.X, point.Y).Perform();
2847 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);
2851 [ThreadStatic]
private static Random _jitter;
2852 private static Random Jitter => _jitter ?? (_jitter =
new Random(Guid.NewGuid().GetHashCode()));
2861 private static Point ImageCenterOffset(Size size)
2864 int jitterX = Math.Max(0, Math.Min(2, (size.Width / 2) - 1));
2865 int jitterY = Math.Max(0, Math.Min(2, (size.Height / 2) - 1));
2867 return new Point((size.Width / 2) + Jitter.Next(-jitterX, jitterX + 1),
2868 (size.Height / 2) + Jitter.Next(-jitterY, jitterY + 1));
2879 private static Point ToViewportPoint(Browser browser, GPALElement element, Selector selector)
2882 Point aim = (0 == selector.OffsetX && 0 == selector.OffsetY)
2883 ? ImageCenterOffset(element.Size)
2884 :
new Point(selector.OffsetX, selector.OffsetY);
2886 int pointX = element.Location.X + aim.X;
2887 int pointY = element.Location.Y + aim.Y;
2889 if (CoordinateSpace.Screen == element.CoordinateSpace)
2891 Rectangle content = GetWindowRectangle(browser);
2892 pointX -= content.X;
2893 pointY -= content.Y;
2896 return new Point(pointX, pointY);
2906 private static void Click(Browser browser, Point point, ClickType clickType, ModifierKeys modifierKeys)
2908 if (
true == browser.UsePuppeteer)
2912 case ClickType.LeftClick:
2913 browser.PuppeteerClient.LeftClick(point).WithModifiers(modifierKeys).Execute();
2915 case ClickType.LeftDoubleClick:
2916 browser.PuppeteerClient.LeftDoubleClick(point).WithModifiers(modifierKeys).Execute();
2918 case ClickType.MiddleClick:
2919 browser.PuppeteerClient.MiddleClick(point).WithModifiers(modifierKeys).Execute();
2921 case ClickType.RightClick:
2922 browser.PuppeteerClient.RightClick(point).WithModifiers(modifierKeys).Execute();
2926 else if (
true == browser.UseSelenium)
2928 Actions action =
new Actions(browser.BrowserDriver).MoveToLocation(point.X, point.Y);
2930 if (modifierKeys.HasFlag(ModifierKeys.Alt))
2931 action.KeyDown(OpenQA.Selenium.Keys.LeftAlt).KeyDown(OpenQA.Selenium.Keys.Alt);
2932 if (modifierKeys.HasFlag(ModifierKeys.Control))
2933 action.KeyDown(OpenQA.Selenium.Keys.LeftControl).KeyDown(OpenQA.Selenium.Keys.Control);
2934 if (modifierKeys.HasFlag(ModifierKeys.Shift))
2935 action.KeyDown(OpenQA.Selenium.Keys.LeftShift).KeyDown(OpenQA.Selenium.Keys.Shift);
2936 if (modifierKeys.HasFlag(ModifierKeys.Windows))
2937 action.KeyDown(OpenQA.Selenium.Keys.Meta);
2941 case ClickType.LeftClick:
2944 case ClickType.LeftDoubleClick:
2945 action.DoubleClick();
2947 case ClickType.RightClick:
2948 action.ContextClick();
2952 GPAL.PublishSimpleEvent(GPALEventType.WARNING, $
"[{clickType}] on an image match by coordinate is not supported by Selenium, performing a left click.", browser, GPALObjectType.Browser);
2957 if (modifierKeys.HasFlag(ModifierKeys.Alt))
2958 action.KeyUp(OpenQA.Selenium.Keys.LeftAlt).KeyUp(OpenQA.Selenium.Keys.Alt);
2959 if (modifierKeys.HasFlag(ModifierKeys.Control))
2960 action.KeyUp(OpenQA.Selenium.Keys.LeftControl).KeyUp(OpenQA.Selenium.Keys.Control);
2961 if (modifierKeys.HasFlag(ModifierKeys.Shift))
2962 action.KeyUp(OpenQA.Selenium.Keys.LeftShift).KeyUp(OpenQA.Selenium.Keys.Shift);
2963 if (modifierKeys.HasFlag(ModifierKeys.Windows))
2964 action.KeyUp(OpenQA.Selenium.Keys.Meta);
2966 ModifierKeys unsupported = modifierKeys & (ModifierKeys.Application | ModifierKeys.ScrollLock);
2967 if (ModifierKeys.NONE != unsupported)
2968 GPAL.PublishSimpleEvent(GPALEventType.WARNING, $
"[{clickType}]: [{Browser.GetModifierKeys(unsupported)}] is not supported via Selenium/WebDriver Actions and was ignored.", browser, GPALObjectType.Browser);
2970 action.Build().Perform();
2973 GPAL.PublishSimpleEvent(GPALEventType.WARNING, $
"Cannot click the image match by coordinate - OttoMagic has no coordinate click and there is no hardware mouse available.", browser, GPALObjectType.Browser);
2984 private static void FillInFrom(Browser browser, Point point,
string text, WriteMode writeMode)
2987 Click(browser, point, ClickType.LeftClick, ModifierKeys.NONE);
2989 if (
true == browser.UsePuppeteer)
2991 if (WriteMode.Overwrite == writeMode)
2992 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);
2994 browser.PuppeteerClient.SendString(text).Execute();
2996 else if (
true == browser.UseSelenium)
2998 Actions actions =
new Actions(browser.BrowserDriver);
3000 if (WriteMode.Overwrite == writeMode)
3001 actions.KeyDown(Keys.Control).SendKeys(
"a").KeyUp(Keys.Control);
3003 actions.SendKeys(text).Perform();
3006 GPAL.PublishSimpleEvent(GPALEventType.WARNING,
"Cannot fill in the image match by coordinate - OttoMagic has no coordinate click to focus with and there is no hardware keyboard available.", browser, GPALObjectType.Browser);
3016 private static void MoveTo(GPALElement element,
int offsetX,
int offsetY, Browser browser)
3020 if (
true == browser.UseOttoMagic)
3022 browser.MagicHelper.MoveTo(element.Css);
3024 else if (
true == browser.UsePuppeteer)
3026 browser.PuppeteerClient.MoveTo(element.ElementHandle).Execute();
3029 new Actions(browser.BrowserDriver).MoveToElement(element.WebElement, offsetX, offsetY).Perform();
3031 catch (GPALException)
3035 catch (Exception ex)
3037 if (
false == GPAL.NoFallbackRecoveryActions)
3039 GPAL.PublishSimpleEvent(GPALEventType.EXCEPTION, $
"Selenium threw an excaption during move to Selector [{browser.CurrentUOW.CurrentSelector?.Name}], falling back to javascript.", browser, GPALObjectType.Browser, ex);
3040 ElementHelper.ScrollIntoView(browser, element);
3043 GPAL.PublishSimpleEvent(GPALEventType.EXCEPTION, $
"Selenium threw an excaption during move to Selector [{browser.CurrentUOW.CurrentSelector?.Name}], continuing.", browser, GPALObjectType.Browser, ex);
3056 private static void HardwareFocus(Browser browser, GPALElement webElement,
int OffsetX,
int OffsetY)
3061 ScrollIntoView(browser, webElement, Enums.InteractionType.Hardware);
3065 bool isPickerInput = valueOnlyInputTypes.Contains(webElement.Type?.ToLowerInvariant() ??
"");
3067 if (
true == ElementHelper.IsElementEditable(webElement) ||
true == isPickerInput)
3070 HardwareClick(browser, webElement, ClickType.LeftClick, ModifierKeys.NONE, browser.CurrentUOW.CurrentSelector);
3075 HardwareMoveTo(browser, webElement, browser.CurrentUOW.CurrentSelector);
3086 private static Rectangle HardwareMoveTo(Browser browser, dynamic element, Selector selector)
3088 Rectangle content =
new Rectangle(), iframeRect =
new Rectangle(0, 0, 0, 0);
3089 bool isImageMatch =
"GPALElement" == (string)element.TagName;
3096 if (
false == isImageMatch)
3097 RefreshLocation(browser, (GPALElement)element);
3099 Rectangle rect2 =
new Rectangle(element.Location, element.Size);
3100 int offsetX = selector.OffsetX;
3101 int offsetY = selector.OffsetY;
3108 Point scroll = Point.Empty;
3109 if (
false == isImageMatch)
3114 if (
true == browser.UseOttoMagic)
3116 pagex = browser.MagicHelper.WindowPageOffsetX();
3117 pagey = browser.MagicHelper.WindowPageOffsetY();
3118 scroll =
new Point(pagex, pagey);
3120 else if (
true == browser.UsePuppeteer)
3122 pagex = browser.PuppeteerClient.WindowPageOffsetX().Execute<
int>();
3123 pagey = browser.PuppeteerClient.WindowPageOffsetY().Execute<
int>();
3127 scroll = GetPageOffset(browser);
3134 if (0 < browser.CurrentUOW.ContextPath?.Count)
3136 foreach (UnitOfWork.WebElementWithType contextType in browser.CurrentUOW.ContextPath)
3138 if (ElementType.IFrame == contextType.elementType)
3140 ClientRectangle tmpRect = contextType.gPalElement.BoundingRect;
3141 iframeRect.X += (int)contextType.gPalElement.Location.X;
3142 iframeRect.Y += (int)contextType.gPalElement.Location.Y;
3148 if (0 == offsetX && 0 == offsetY)
3150 if (
"GPALElement" == (
string)element.TagName)
3153 Point center = ImageCenterOffset(rect2.Size);
3163 int spreadX = Math.Max(1, (
int)rect2.Width / 10);
3164 int spreadY = Math.Max(1, (
int)rect2.Height / 10);
3166 offsetX = (int)rect2.Width / 2 + Jitter.Next(-spreadX, spreadX + 1);
3167 offsetY = (int)rect2.Height / 2 + Jitter.Next(-spreadY, spreadY + 1);
3171 content = GetWindowRectangle(browser);
3176 Point imageOrigin = Point.Empty;
3178 if (
"GPALElement" == (
string)element.TagName && CoordinateSpace.Viewport == ((GPALElement)element).CoordinateSpace)
3179 imageOrigin =
new Point(content.X, content.Y);
3182 int screenX = (int)((
"GPALElement" == element.TagName)
3183 ? rect2.Left + offsetX + imageOrigin.X
3184 : rect2.Left + offsetX + iframeRect.X + content.X - scroll.X);
3186 int screenY = (int)((
"GPALElement" == element.TagName)
3187 ? rect2.Top + offsetY + imageOrigin.Y
3188 : rect2.Top + offsetY + iframeRect.Y + content.Y - scroll.Y);
3194 if (
"GPALElement" != (
string)element.TagName
3195 &&
true == IsBiggerThanViewport(
new Size((
int)rect2.Width, (
int)rect2.Height), content)
3196 &&
false == content.Contains(screenX, screenY))
3201 Point origin =
new Point(screenX - offsetX, screenY - offsetY);
3202 Rectangle onScreen = Rectangle.Intersect(
new Rectangle(origin.X, origin.Y, (
int)rect2.Width, (
int)rect2.Height), content);
3204 if (
false == onScreen.IsEmpty)
3211 bool verified =
false;
3212 int tryY = onScreen.Y + onScreen.Height / 2;
3213 List<int> candidates =
new List<int>();
3215 foreach (
int inset
in hitTestInsets)
3216 if (inset < onScreen.Width)
3217 candidates.Add(onScreen.Right - inset);
3219 foreach (
float fraction
in hitTestFractions)
3220 candidates.Add(onScreen.X + (
int)(onScreen.Width * fraction));
3222 foreach (
int tryX
in candidates)
3223 if (
true == PointHitsElement(browser, (GPALElement)element,
new Point(tryX, tryY)))
3231 if (
false == verified)
3236 screenX = onScreen.X + onScreen.Width / 2;
3237 screenY = onScreen.Y + onScreen.Height / 2;
3243 _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";
3248 if (
true == GPAL.SimulateMouse ||
true == selector.SimulateMouse)
3249 HardwareHelper.MoveMouse(screenX, screenY, 10, 10);
3251 HardwareHelper.MoveMouse(screenX, screenY);
3253 return new Rectangle(screenX, screenY, (
int)rect2.Width, (
int)rect2.Height);
3262 internal static Point GetPageOffset(Browser browser)
3264 Point pageOffset = Point.Empty;
3266 if (
true == browser.UseOttoMagic)
3267 pageOffset =
new Point(browser.MagicHelper.WindowPageOffsetX(), browser.MagicHelper.WindowPageOffsetY());
3268 else if (
true == browser.UsePuppeteer)
3269 pageOffset =
new Point(browser.PuppeteerClient.WindowPageOffsetX().Execute<
int>(), browser.PuppeteerClient.WindowPageOffsetY().Execute<
int>());
3270 else if (BrowserHelper.ExecuteJavaScriptObj(
"return [window.top.pageXOffset, window.top.pageYOffset]", browser) is IList<object> offsets && 2 <= offsets.Count)
3271 pageOffset =
new Point(Convert.ToInt32(offsets[0]), Convert.ToInt32(offsets[1]));
3284 private static bool UsesScreenCoordinates(Browser browser, Enums.InteractionType? interactionType)
3286 Enums.InteractionType? useType = interactionType ?? browser.CurrentUOW?.CurrentSelector?.InteractionType;
3288 return true == browser.BrowserSettings.UseHardware
3289 ||
true == GPAL.GPALSettings.UseHardware
3290 || Enums.InteractionType.Hardware == useType;
3300 internal static void ScrollIntoView(Browser browser, dynamic element, Enums.InteractionType? interactionType =
null)
3302 if (
"GPALElement".Equals(((GPALElement)element).TagName))
3307 if (
true == browser.UseOttoMagic &&
false == UsesScreenCoordinates(browser, interactionType))
3310 if (
true == browser.UseOttoMagic)
3311 browser.MagicHelper.ScrollIntoView(((GPALElement)element).Css);
3312 else if (
true == browser.UsePuppeteer)
3313 browser.PuppeteerClient.ScrollIntoView((GPALElement)element).Execute();
3314 else if (
null != element)
3316 string debounceScript =
@"
3317 // Best JS replacement for scrollIntoViewIfNeeded - do not scroll to if it's already on-screen
3318 function scrollIntoViewIfNeeded(el) {
3319 return new Promise(resolve => {
3320 const io = new IntersectionObserver(([entry]) => {
3322 if (entry.intersectionRatio < 1) {
3323 el.scrollIntoView({ behavior: 'instant', block: 'center', inline: 'center' });
3326 }, { threshold: 1 });
3331 scrollIntoViewIfNeeded(arguments[0]);
3334 BrowserHelper.ExecuteJavaScript(browser, debounceScript, typeof(GPALElement) == element.GetType() ? ((GPALElement)element).WebElement : element);
3341 ScrollUntilVisible(browser, (GPALElement)element);
3350 public static void LeftClick(Browser browser, ModifierKeys modifierKeys = ModifierKeys.NONE, ClickType clickType = ClickType.LeftClick)
3352 List<GPALElement> matchedElements;
3353 List<GPALElement> clickableElements;
3355 foreach (Selector selector
in browser.CurrentUOW.WithSelectorList)
3357 if (SelectorType.Selector != selector.SelectorType)
3360 ElementHelper.FindWebElements(browser, browser.CurrentUOW, selector, out
bool matchedAll, out matchedElements);
3363 if (
true == selector.DeleteMe ||
null == matchedElements)
3365 PublishToEventHandler(browser, selector, matchedElements);
3372 if (1 < matchedElements.Count)
3374 clickableElements =
new List<GPALElement>();
3375 foreach (GPALElement elem
in matchedElements)
3376 if (
true == elem.IsClickable(
false))
3377 clickableElements.Add(elem);
3379 if (0 == clickableElements.Count)
3380 clickableElements = matchedElements;
3383 clickableElements = matchedElements;
3387 foreach (GPALElement elem
in clickableElements)
3391 if (
true == elem.TagName.ToLower().Equals(
"option"))
3392 SelectClick(browser, selector, elem, SelectClickType.LeftClick, modifierKeys);
3394 Click(browser, selector, elem, clickType, modifierKeys);
3396 if (++rowCount >= browser.CurrentUOW.WithAllThatMatch)
3408 public static void MiddleClick(Browser browser)
3410 LeftClick(browser, ModifierKeys.NONE, ClickType.MiddleClick);
3420 public static void ScrollElement(Browser browser,
int amount, ScrollTypes scrollTypes)
3422 List<GPALElement> matchedElements;
3423 bool matchedAll =
false;
3425 foreach (Selector selector
in browser.CurrentUOW.WithSelectorList)
3427 if (SelectorType.Selector != selector.SelectorType)
3430 ElementHelper.FindWebElements(browser, browser.CurrentUOW, selector, out matchedAll, out matchedElements);
3432 if (
null != matchedElements)
3436 foreach (GPALElement elem
in matchedElements)
3440 int hAmount = ScrollTypes.Horizontal == scrollTypes ? amount : 0;
3441 int vAmount = ScrollTypes.Vertical == scrollTypes ? amount : 0;
3443 if (
true == browser.UseOttoMagic)
3444 browser.MagicHelper.ScrollElement(elem.Css, hAmount, vAmount);
3445 else if (
true == browser.UsePuppeteer)
3446 browser.PuppeteerClient.ScrollElement(elem.Css).WithHPixels(hAmount).WithVPixels(vAmount).Execute();
3447 else if (
true == browser.UseSelenium)
3448 BrowserHelper.ExecuteJavaScript(browser, $
"arguments[0].scrollBy(left: {hAmount}, top: {vAmount}, behavior: 'instant')", elem.WebElement);
3450 if (++rowCount >= browser.CurrentUOW.WithAllThatMatch)
3464 public static bool GenericClick(Browser browser, ClickType clickType)
3466 List<GPALElement> matchedElements;
3467 bool matchedAll =
false;
3468 bool clickPerformed =
false;
3470 foreach (Selector selector
in browser.CurrentUOW.WithSelectorList)
3472 if (SelectorType.Selector != selector.SelectorType)
3475 ElementHelper.FindWebElements(browser, browser.CurrentUOW, selector, out matchedAll, out matchedElements);
3477 if (
null != matchedElements)
3480 foreach (GPALElement elem
in matchedElements)
3484 if (
true == elem.TagName.ToLower().Equals(
"option"))
3485 SelectClick(browser, selector, elem, SelectClickType.LeftClick, ModifierKeys.NONE);
3487 Click(browser, selector, elem, clickType, ModifierKeys.NONE);
3489 clickPerformed =
true;
3491 if (++rowCount >= browser.CurrentUOW.WithAllThatMatch)
3497 return clickPerformed;
3506 public static bool GenericClick(Browser browser, SelectClickType selectClickType)
3508 List<GPALElement> matchedElements;
3509 bool matchedAll =
false;
3510 bool clickPerformed =
false;
3512 foreach (Selector selector
in browser.CurrentUOW.WithSelectorList)
3514 if (SelectorType.Selector != selector.SelectorType)
3517 ElementHelper.FindWebElements(browser, browser.CurrentUOW, selector, out matchedAll, out matchedElements);
3519 if (
null != matchedElements)
3522 foreach (GPALElement elem
in matchedElements)
3526 if (
true == elem.TagName.ToLower().Equals(
"option"))
3527 SelectClick(browser, selector, elem, selectClickType, ModifierKeys.NONE);
3529 Click(browser, selector, elem, ClickType.LeftClick, ModifierKeys.NONE);
3531 clickPerformed =
true;
3533 if (++rowCount >= browser.CurrentUOW.WithAllThatMatch)
3539 return clickPerformed;
3545 internal static class SelectClickHelper
3548 internal static readonly Dictionary<string, int> _sequentialSelectIndices =
new Dictionary<string, int>();
3550 internal static string _lastParentNodeId =
null;
3559 internal static string GetElementAttributeHashCsharp(GPALElement element, Browser browser)
3564 var attrList =
new List<string>();
3566 foreach (var attrName
in element.Attributes.Keys)
3569 string value = element.GetAttribute(attrName);
3570 if (!
string.IsNullOrEmpty(value))
3572 attrList.Add($
"{attrName}={value}");
3578 string attrString =
string.Join(
"|", attrList);
3582 foreach (
char c
in attrString)
3584 hash = ((hash << 5) - hash + c) | 0;
3587 return hash.ToString();
3591 return element.GetHashCode().ToString();
3602 internal static string GetElementAttributeHashJavaScript(GPALElement element, Browser browser)
3606 if (
false == browser.UseOttoMagic)
3608 string jsScript =
@"
3609 let attrs = arguments[0].attributes;
3611 for (let attr of attrs) {
3612 attrList.push(attr.name + '=' + attr.value);
3614 attrList.sort(); // Sort attributes for consistent hashing
3615 let str = attrList.join('|');
3617 for (let i = 0; i < str.length; i++) {
3618 hash = ((hash << 5) - hash + str.charCodeAt(i)) | 0;
3620 return hash.toString();
3622 return (
string)BrowserHelper.ExecuteJavaScript(browser, jsScript, element.WebElement) ?? element.GetHashCode().ToString();
3625 return browser.OttoMagicClient.GetElementAttributeHash(element.Css).Execute().Replace(
"\"",
"");
3630 return element.GetHashCode().ToString();
3645 public static void SelectClick(Browser browser, Selector selector, GPALElement webElement, SelectClickType clickType, ModifierKeys modifierKeys)
3648 dynamic result =
null;
3649 dynamic parentElem =
null;
3650 SelectElement elem =
null;
3651 string selectedValue =
null;
3652 dynamic options =
null;
3653 string elementId =
null;
3657 if (
true == browser.UseOttoMagic)
3658 result = browser.MagicHelper.GetParentNode(webElement.Css);
3659 else if (
true == browser.UsePuppeteer)
3660 result = browser.PuppeteerClient.GetParentNode(webElement.ElementHandle).Execute<GPALElement>();
3662 result = BrowserHelper.ExecuteJavaScriptObj(
"return arguments[0].parentNode;", browser, webElement.WebElement);
3666 if (
true == browser.UseSelenium)
3668 parentElem = result;
3669 elementId = parentElem.GetAttribute(
"id");
3673 parentElem = (GPALElement)result;
3674 parentElem.Browser = browser;
3675 elementId = parentElem.GetAttribute(
"id");
3680 ScrollIntoView(browser, parentElem);
3683 if (
true == browser.UseSelenium)
3685 elem =
new SelectElement(parentElem);
3688 if (SelectClickType.LeftClick != clickType)
3690 options = elem.Options;
3691 if (options.Count == 0)
3693 GPAL.PublishSimpleEvent(GPALEventType.ERROR,
"No options found in select element.", webElement, GPALObjectType.Other);
3699 if (
string.IsNullOrEmpty(elementId))
3704 elementId = SelectClickHelper.GetElementAttributeHashJavaScript(parentElem, browser);
3709 if (SelectClickType.LeftClick != clickType)
3711 Selector optionsSelector = GPAL.CssSelector($
"{parentElem.Css} > option").WithSelectorName(
"options").ToGPALObject();
3713 options = FindWebElementsByCss(browser, optionsSelector.SelectorPaths[0], optionsSelector);
3715 if (0 == options.Count)
3717 Click(browser, selector, webElement, ClickType.LeftClick, modifierKeys);
3722 if (
string.IsNullOrEmpty(elementId))
3724 if (
true == browser.UseOttoMagic)
3725 elementId = browser.MagicHelper.GetElementAttributeHash(parentElem.Css);
3726 else if (
true == browser.UsePuppeteer)
3727 elementId = browser.PuppeteerClient.GetElementAttributeHash(parentElem.Css).Execute();
3733 if (SelectClickHelper._lastParentNodeId != elementId &&
3734 (clickType == SelectClickType.SelectNext || clickType == SelectClickType.SelectNextWithWrap))
3736 SelectClickHelper._sequentialSelectIndices[elementId] = -1;
3739 SelectClickHelper._lastParentNodeId = elementId;
3741 if (
false == browser.BrowserSettings.UseHeadless && (
true == browser.BrowserSettings.UseHardware ||
true == GPAL.GPALSettings.UseHardware))
3745 case SelectClickType.SelectRandom:
3746 Random rand =
new Random();
3747 int randomIndex = rand.Next(0, options.Count);
3749 SelectClickHelper._sequentialSelectIndices[elementId] = randomIndex;
3750 HardwareClick(browser, parentElem, ClickType.LeftClick, modifierKeys, browser.CurrentUOW.CurrentSelector);
3751 HardwareHelper.SendKey(GPAL.VK_HOME);
3752 for (
int i = 0; i < randomIndex; i++)
3755 HardwareHelper.SendKey(GPAL.VK_DOWN);
3757 HardwareHelper.SendKey(GPAL.VK_RETURN);
3760 case SelectClickType.SelectNext:
3761 case SelectClickType.SelectNextWithWrap:
3762 case SelectClickType.SelectPrevious:
3763 case SelectClickType.SelectPreviousWithWrap:
3766 int incOrDec = (clickType == SelectClickType.SelectPrevious ||
3767 clickType == SelectClickType.SelectPreviousWithWrap)
3770 bool shouldWrap = (clickType == SelectClickType.SelectNextWithWrap ||
3771 clickType == SelectClickType.SelectPreviousWithWrap);
3774 if (!SelectClickHelper._sequentialSelectIndices.TryGetValue(elementId, out
int currentIndex))
3777 SelectClickHelper._sequentialSelectIndices[elementId] = currentIndex;
3785 nextIndex = (currentIndex + incOrDec % options.Count + options.Count) % options.Count;
3790 nextIndex = currentIndex + incOrDec;
3793 else if (nextIndex >= options.Count)
3794 nextIndex = options.Count - 1;
3798 HardwareClick(browser, parentElem, ClickType.LeftClick, modifierKeys,
3799 browser.CurrentUOW.CurrentSelector);
3802 HardwareHelper.SendKey(GPAL.VK_HOME);
3808 for (
int i = 0; i < nextIndex; i++)
3811 HardwareHelper.SendKey(GPAL.VK_DOWN);
3818 HardwareHelper.SendKey(GPAL.VK_END);
3821 int stepsUp = options.Count - 1 - nextIndex;
3822 for (
int i = 0; i < stepsUp; i++)
3825 HardwareHelper.SendKey(GPAL.VK_UP);
3830 HardwareHelper.SendKey(GPAL.VK_RETURN);
3833 SelectClickHelper._sequentialSelectIndices[elementId] = nextIndex;
3838 HardwareClick(browser, parentElem, ClickType.LeftClick, modifierKeys, browser.CurrentUOW.CurrentSelector);
3841 int index = Int32.Parse(webElement.GetAttribute(
"index"));
3843 SelectClickHelper._sequentialSelectIndices[elementId] = index;
3844 HardwareHelper.SendKey(GPAL.VK_HOME);
3845 for (
int cnt = 0; cnt < index; cnt++)
3848 HardwareHelper.SendKey(GPAL.VK_DOWN);
3850 HardwareHelper.SendKey(GPAL.VK_RETURN);
3854 else if (
true == browser.UseOttoMagic)
3858 case SelectClickType.SelectRandom:
3859 Random rand =
new Random();
3860 int randomIndex = rand.Next(0, options.Count);
3861 selectedValue = options[randomIndex].GetAttribute(
"value");
3863 SelectClickHelper._sequentialSelectIndices[elementId] = randomIndex;
3864 browser.MagicHelper.SelectClick(parentElem.Css, selectedValue);
3867 case SelectClickType.SelectNext:
3868 case SelectClickType.SelectNextWithWrap:
3869 case SelectClickType.SelectPrevious:
3870 case SelectClickType.SelectPreviousWithWrap:
3873 int incOrDec = (clickType == SelectClickType.SelectPrevious ||
3874 clickType == SelectClickType.SelectPreviousWithWrap)
3878 bool shouldWrap = (clickType == SelectClickType.SelectNextWithWrap ||
3879 clickType == SelectClickType.SelectPreviousWithWrap);
3882 if (!SelectClickHelper._sequentialSelectIndices.TryGetValue(elementId, out
int currentIndex))
3885 SelectClickHelper._sequentialSelectIndices[elementId] = currentIndex;
3893 nextIndex = (currentIndex + incOrDec % options.Count + options.Count) % options.Count;
3898 nextIndex = currentIndex + incOrDec;
3901 else if (nextIndex >= options.Count)
3902 nextIndex = options.Count - 1;
3906 SelectClickHelper._sequentialSelectIndices[elementId] = nextIndex;
3907 selectedValue = options[nextIndex].GetAttribute(
"value");
3908 browser.MagicHelper.SelectClick(parentElem.Css, selectedValue);
3913 int index = Int32.Parse(webElement.GetAttribute(
"index"));
3915 SelectClickHelper._sequentialSelectIndices[elementId] = index;
3916 browser.MagicHelper.SelectClick(parentElem.Css, webElement.Value);
3920 else if (
true == browser.UsePuppeteer)
3924 case SelectClickType.SelectRandom:
3925 Random rand =
new Random();
3926 int randomIndex = rand.Next(0, options.Count);
3927 selectedValue = options[randomIndex].GetAttribute(
"value");
3929 SelectClickHelper._sequentialSelectIndices[elementId] = randomIndex;
3931 ((PuppeteerClient)browser.PuppeteerClient).SelectClick((dynamic)parentElem.ElementHandle).WithSelectValue(selectedValue).Execute();
3934 case SelectClickType.SelectNext:
3935 case SelectClickType.SelectNextWithWrap:
3936 case SelectClickType.SelectPrevious:
3937 case SelectClickType.SelectPreviousWithWrap:
3940 int incOrDec = (clickType == SelectClickType.SelectPrevious ||
3941 clickType == SelectClickType.SelectPreviousWithWrap)
3945 bool shouldWrap = (clickType == SelectClickType.SelectNextWithWrap ||
3946 clickType == SelectClickType.SelectPreviousWithWrap);
3949 if (!SelectClickHelper._sequentialSelectIndices.TryGetValue(elementId, out
int currentIndex))
3952 SelectClickHelper._sequentialSelectIndices[elementId] = currentIndex;
3960 nextIndex = (currentIndex + incOrDec % options.Count + options.Count) % options.Count;
3965 nextIndex = currentIndex + incOrDec;
3968 else if (nextIndex >= options.Count)
3969 nextIndex = options.Count - 1;
3973 SelectClickHelper._sequentialSelectIndices[elementId] = nextIndex;
3974 selectedValue = options[nextIndex].GetAttribute(
"value");
3976 ((PuppeteerClient)browser.PuppeteerClient).SelectClick((dynamic)parentElem.ElementHandle).WithSelectValue(selectedValue).Execute();
3981 int index = Int32.Parse(webElement.GetAttribute(
"index"));
3982 SelectClickHelper._sequentialSelectIndices[elementId] = index;
3985 ((PuppeteerClient)browser.PuppeteerClient).SelectClick((dynamic)parentElem.ElementHandle).WithSelectIndex(index).Execute();
3989 else if (
true == browser.UseSelenium)
3992 if (Enums.InteractionType.JavaScript == selector.InteractionType || browser.BrowserSettings.UseJavaScript)
3996 case SelectClickType.SelectRandom:
3997 Random rand =
new Random();
3998 int randomIndex = rand.Next(0, options.Count);
4000 SelectClickHelper._sequentialSelectIndices[elementId] = randomIndex;
4001 selectedValue = options[randomIndex].GetAttribute(
"value");
4002 BrowserHelper.ExecuteJavaScript(browser,
"arguments[0].value = arguments[1];", parentElem, selectedValue);
4005 case SelectClickType.SelectNext:
4006 case SelectClickType.SelectNextWithWrap:
4007 case SelectClickType.SelectPrevious:
4008 case SelectClickType.SelectPreviousWithWrap:
4011 int incOrDec = (clickType == SelectClickType.SelectPrevious ||
4012 clickType == SelectClickType.SelectPreviousWithWrap)
4016 bool shouldWrap = (clickType == SelectClickType.SelectNextWithWrap ||
4017 clickType == SelectClickType.SelectPreviousWithWrap);
4020 if (!SelectClickHelper._sequentialSelectIndices.TryGetValue(elementId, out
int currentIndex))
4023 SelectClickHelper._sequentialSelectIndices[elementId] = currentIndex;
4031 nextIndex = (currentIndex + incOrDec % options.Count + options.Count) % options.Count;
4036 nextIndex = currentIndex + incOrDec;
4039 else if (nextIndex >= options.Count)
4040 nextIndex = options.Count - 1;
4044 SelectClickHelper._sequentialSelectIndices[elementId] = nextIndex;
4045 selectedValue = options[nextIndex].GetAttribute(
"value");
4046 BrowserHelper.ExecuteJavaScript(browser,
"arguments[0].value = arguments[1];", parentElem, selectedValue);
4050 int index = Int32.Parse(webElement.WebElement.GetAttribute(
"index"));
4052 SelectClickHelper._sequentialSelectIndices[elementId] = index;
4053 BrowserHelper.ExecuteJavaScript(browser,
"arguments[0].value = arguments[1].value", parentElem, webElement.WebElement);
4060 case SelectClickType.SelectRandom:
4061 Random rand =
new Random();
4062 int randomIndex = rand.Next(0, options.Count);
4064 SelectClickHelper._sequentialSelectIndices[elementId] = randomIndex;
4065 elem.SelectByIndex(randomIndex);
4068 case SelectClickType.SelectNext:
4069 case SelectClickType.SelectNextWithWrap:
4070 case SelectClickType.SelectPrevious:
4071 case SelectClickType.SelectPreviousWithWrap:
4074 int incOrDec = (clickType == SelectClickType.SelectPrevious ||
4075 clickType == SelectClickType.SelectPreviousWithWrap)
4079 bool shouldWrap = (clickType == SelectClickType.SelectNextWithWrap ||
4080 clickType == SelectClickType.SelectPreviousWithWrap);
4083 if (!SelectClickHelper._sequentialSelectIndices.TryGetValue(elementId, out
int currentIndex))
4086 SelectClickHelper._sequentialSelectIndices[elementId] = currentIndex;
4094 nextIndex = (currentIndex + incOrDec % options.Count + options.Count) % options.Count;
4099 nextIndex = currentIndex + incOrDec;
4102 else if (nextIndex >= options.Count)
4103 nextIndex = options.Count - 1;
4107 elem.SelectByIndex(nextIndex);
4108 SelectClickHelper._sequentialSelectIndices[elementId] = nextIndex;
4113 int index = Int32.Parse(webElement.GetAttribute(
"index"));
4115 SelectClickHelper._sequentialSelectIndices[elementId] = index;
4116 elem.SelectByValue(webElement.GetAttribute(
"value"));
4121 catch (GPALException)
4125 catch (Exception ex)
4127 if (!GPAL.NoFallbackRecoveryActions)
4129 GPAL.PublishSimpleEvent(GPALEventType.EXCEPTION, $
"Unable to Select using [{selector.InteractionType}]. Falling back on JavaScript.", webElement, GPALObjectType.Other, ex);
4130 BrowserHelper.ExecuteJavaScript(browser,
"arguments[0].value = arguments[1].value",
true == browser.UseSelenium ? parentElem : ((GPALElement)parentElem).WebElement, webElement.WebElement);
4134 GPAL.PublishSimpleEvent(GPALEventType.EXCEPTION, $
"Unable to Select using [{selector.InteractionType}].", webElement, GPALObjectType.Other, ex);
4137 selectedValue =
null != options ? options[SelectClickHelper._sequentialSelectIndices[elementId]].GetAttribute(
"value") : webElement.GetAttribute(
"value");
4138 string selectedText =
null != options ? options[SelectClickHelper._sequentialSelectIndices[elementId]].Text : webElement.Text;
4139 GPAL.PublishSimpleEvent(GPALEventType.INFO, $
"[{selector.InteractionType}] selected [{selectedText}] value [{selectedValue}] index [{SelectClickHelper._sequentialSelectIndices[elementId]}]");
4143 GPAL.PublishSimpleEvent(GPALEventType.WARNING, $
"Unable to click Parent Select for Option using [{selector.InteractionType}]. Clicking element [{selector.Name}].", webElement, GPALObjectType.Other);
4144 webElement.Click(modifierKeys);
4158 public static void Click(Browser browser, Selector selector, GPALElement webElement, ClickType clickType, ModifierKeys modifierKeys)
4160 bool dontPublish =
false;
4162 ScrollIntoView(browser, webElement, selector.InteractionType);
4169 if (
false == browser.BrowserSettings.UseHeadless
4170 && (Enums.InteractionType.Hardware == selector.InteractionType
4171 ||
"GPALElement" == webElement.TagName
4172 ||
true == browser.BrowserSettings.UseHardware ||
true == GPAL.GPALSettings.UseHardware)
4179 GPALElement clickMe = webElement;
4181 if (
false == browser.UseSelenium &&
false ==
string.IsNullOrEmpty(webElement.Css))
4183 Selector cssSel = GPAL.Selector.WithCSS(webElement.Css).ToGPALObject();
4184 ReadOnlyCollection<GPALElement> elems = FindWebElementsByCss(browser, cssSel.SelectorPaths[0], cssSel);
4188 if (0 < elems?.Count)
4192 HardwareClick(browser, clickMe, clickType, modifierKeys, selector,
true);
4195 else if (
"GPALElement" == webElement.TagName)
4198 Click(browser, ToViewportPoint(browser, webElement, selector), clickType, modifierKeys);
4200 else if (
true == browser.UseOttoMagic)
4202 if (modifierKeys != ModifierKeys.NONE)
4203 browser.MagicHelper.PressModifierKey(modifierKeys);
4207 case ClickType.LeftClick:
4208 browser.MagicHelper.LeftClick(webElement.Css);
4210 case ClickType.LeftDoubleClick:
4211 browser.MagicHelper.LeftDoubleClick(webElement.Css);
4213 case ClickType.MiddleClick:
4214 browser.MagicHelper.MiddleClick(webElement.Css);
4216 case ClickType.RightClick:
4217 browser.MagicHelper.RightClick(webElement.Css);
4221 if (modifierKeys != ModifierKeys.NONE)
4222 browser.MagicHelper.ReleaseModifierKey(modifierKeys);
4224 else if (Enums.InteractionType.JavaScript == selector.InteractionType ||
true == browser.BrowserSettings.UseJavaScript)
4226 if (
false == JavaScriptClick(browser, webElement, clickType, modifierKeys) &&
false == GPAL.NoFallbackRecoveryActions)
4228 if (
true == browser.UseSelenium)
4230 GPAL.PublishSimpleEvent(GPALEventType.ERROR, $
"JavaScript click failed on selector [{selector.Name}], falling back to selenium click.", browser, GPALObjectType.Browser);
4231 SeleniumClick(browser, webElement, clickType, modifierKeys);
4235 GPAL.PublishSimpleEvent(GPALEventType.ERROR, $
"JavaScript click failed on selector [{selector.Name}], falling back to Puppeteer click.", browser, GPALObjectType.Browser);
4236 PuppeteerClick(browser, webElement, clickType, modifierKeys);
4241 else if (
true == browser.UsePuppeteer)
4243 PuppeteerClick(browser, webElement, clickType, modifierKeys);
4247 SeleniumClick(browser, webElement, clickType, modifierKeys);
4249 if (
false == dontPublish)
4250 GPAL.PublishSimpleEvent(GPALEventType.INFO, $
"Element [{webElement.TagName}] for selector [{selector.Name}] [{selector.InteractionType.ToString()}][{clickType.ToString()}] clicked with modifiers [{GetEnumNames(modifierKeys)}].", browser, GPALObjectType.Browser);
4253 catch (GPALException)
4257 catch (Exception ex)
4259 GPAL.PublishSimpleEvent(GPALEventType.EXCEPTION, $
"Unable to [{clickType.ToString()}] click element [{webElement.TagName}] for selector [{selector.Name}]", browser, GPALObjectType.Browser, ex);
4275 public static void PuppeteerClick(Browser browser, GPALElement webElement, ClickType clickType, ModifierKeys modifierKeys = ModifierKeys.NONE)
4279 case ClickType.LeftClick:
4280 browser.PuppeteerClient.LeftClick(webElement).WithModifiers(modifierKeys).Execute();
4282 case ClickType.LeftDoubleClick:
4283 browser.PuppeteerClient.LeftDoubleClick(webElement).WithModifiers(modifierKeys).Execute();
4285 case ClickType.MiddleClick:
4286 browser.PuppeteerClient.MiddleClick(webElement).WithModifiers(modifierKeys).Execute();
4288 case ClickType.RightClick:
4289 browser.PuppeteerClient.RightClick(webElement).WithModifiers(modifierKeys).Execute();
4301 public static void SeleniumClick(Browser browser, GPALElement webElement, ClickType clickType, ModifierKeys modifierKeys = ModifierKeys.NONE)
4305 Actions action =
new Actions(browser.BrowserDriver);
4306 string errorMessage = $
"Failed to {clickType.ToString()} element with text [{webElement.Text}]";
4314 case ClickType.LeftClick:
4315 if (ModifierKeys.NONE == modifierKeys)
4317 webElement.iWebElement.Click();
4321 if (modifierKeys.HasFlag(ModifierKeys.Alt))
4322 action.KeyDown(OpenQA.Selenium.Keys.LeftAlt).KeyDown(OpenQA.Selenium.Keys.Alt);
4323 if (modifierKeys.HasFlag(ModifierKeys.Control))
4324 action.KeyDown(OpenQA.Selenium.Keys.LeftControl).KeyDown(OpenQA.Selenium.Keys.Control);
4325 if (modifierKeys.HasFlag(ModifierKeys.Shift))
4326 action.KeyDown(OpenQA.Selenium.Keys.LeftShift).KeyDown(OpenQA.Selenium.Keys.Shift);
4327 if (modifierKeys.HasFlag(ModifierKeys.Windows))
4328 action.KeyDown(OpenQA.Selenium.Keys.Meta);
4330 action.Click(webElement.iWebElement);
4332 if (modifierKeys.HasFlag(ModifierKeys.Alt))
4333 action.KeyUp(OpenQA.Selenium.Keys.LeftAlt).KeyUp(OpenQA.Selenium.Keys.Alt);
4334 if (modifierKeys.HasFlag(ModifierKeys.Control))
4335 action.KeyUp(OpenQA.Selenium.Keys.LeftControl).KeyUp(OpenQA.Selenium.Keys.Control);
4336 if (modifierKeys.HasFlag(ModifierKeys.Shift))
4337 action.KeyUp(OpenQA.Selenium.Keys.LeftShift).KeyUp(OpenQA.Selenium.Keys.Shift);
4338 if (modifierKeys.HasFlag(ModifierKeys.Windows))
4339 action.KeyUp(OpenQA.Selenium.Keys.Meta);
4341 ModifierKeys unsupported = modifierKeys & (ModifierKeys.Application | ModifierKeys.ScrollLock);
4342 if (ModifierKeys.NONE != unsupported)
4343 GPAL.PublishSimpleEvent(GPALEventType.WARNING, $
"LeftClick: [{Browser.GetModifierKeys(unsupported)}] is not supported via Selenium/WebDriver Actions and was ignored.", browser, GPALObjectType.Browser);
4345 action.Build().Perform();
4348 case ClickType.LeftDoubleClick:
4349 action.DoubleClick(webElement.iWebElement).Perform();
4351 case ClickType.MiddleClick:
4356 PointerInputDevice mouse =
new PointerInputDevice(PointerKind.Mouse,
"default mouse");
4357 ActionSequence middleClick =
new ActionSequence(mouse);
4359 middleClick.AddAction(mouse.CreatePointerMove(webElement.iWebElement, 0, 0, TimeSpan.Zero));
4360 middleClick.AddAction(mouse.CreatePointerDown(MouseButton.Middle));
4361 middleClick.AddAction(mouse.CreatePointerUp(MouseButton.Middle));
4363 ((IActionExecutor)browser.BrowserDriver).PerformActions(
new List<ActionSequence> { middleClick });
4365 if (ModifierKeys.NONE != modifierKeys)
4366 GPAL.PublishSimpleEvent(GPALEventType.WARNING, $
"Selenium cannot press [{Browser.GetModifierKeys(modifierKeys)}] with a middle click.", browser, GPALObjectType.Browser);
4370 case ClickType.RightClick:
4371 action.ContextClick(webElement.iWebElement).Perform();
4376 catch (StaleElementReferenceException sereex)
4378 GPAL.PublishSimpleEvent(GPALEventType.EXCEPTION, errorMessage, webElement, GPALObjectType.Other, sereex);
4379 retry = ResolveWebElementIssue(browser, browser.CurrentUOW, ref webElement.iWebElement, sereex, ref retries, errorMessage);
4382 catch (ElementClickInterceptedException ecieex)
4384 GPAL.PublishSimpleEvent(GPALEventType.EXCEPTION, errorMessage, webElement, GPALObjectType.Other, ecieex);
4385 if (
false == GPAL.NoFallbackRecoveryActions)
4386 retry = JavaScriptClick(browser, webElement, clickType, modifierKeys);
4391 catch (ElementNotVisibleException enveex)
4393 GPAL.PublishSimpleEvent(GPALEventType.EXCEPTION, errorMessage, webElement, GPALObjectType.Other, enveex);
4394 retry = ResolveWebElementIssue(browser, browser.CurrentUOW, ref webElement.iWebElement, enveex, ref retries, errorMessage);
4397 catch (GPALException)
4401 catch (Exception ex)
4405 GPAL.PublishSimpleEvent(GPALEventType.EXCEPTION, errorMessage, webElement, GPALObjectType.Other, ex);
4407 }
while (retry && 0 < retries);
4409 if (retries == 0 &&
false == GPAL.NoFallbackRecoveryActions)
4411 GPAL.PublishSimpleEvent(GPALEventType.INFO,
"Falling back on javascript click", webElement, GPALObjectType.Other);
4412 JavaScriptClick(browser, webElement, clickType, modifierKeys);
4423 public static bool JavaScriptClick(Browser browser, GPALElement webElement, ClickType clickType, ModifierKeys modifierKeys = ModifierKeys.NONE)
4425 string clickString = clickType
switch
4427 ClickType.LeftClick =>
"click",
4428 ClickType.LeftDoubleClick =>
"dblclick",
4429 ClickType.RightClick =>
"contextmenu",
4430 ClickType.MiddleClick =>
"auxclick",
4433 int button = (
true ==
"contextmenu".Equals(clickString) ? 2 : 0);
4434 bool shiftPressed = 0 != (modifierKeys & ModifierKeys.Shift);
4435 bool controlPressed = 0 != (modifierKeys & ModifierKeys.Control);
4436 bool altPressed = 0 != (modifierKeys & ModifierKeys.Alt);
4439 var random =
new Random();
4441 var clickAreaWidth = (float)webElement.BoundingRect.Width * (1f - 2 * margin);
4442 var clickAreaHeight = (float)webElement.BoundingRect.Height * (1f - 2 * margin);
4443 var x = (float)webElement.BoundingRect.X + (
float)webElement.BoundingRect.Width * margin + (float)random.NextDouble() * clickAreaWidth;
4444 var y = (float)webElement.BoundingRect.Y + (
float)webElement.BoundingRect.Height * margin + (float)random.NextDouble() * clickAreaHeight;
4446 if (
true == browser.UseSelenium)
4448 IWebDriver browserDriver = browser.BrowserDriver;
4450 string clickScript =
$@"
4451 var theEvent = document.createEvent(""MouseEvent"");
4452 theEvent.initMouseEvent(""{clickString}"", true, true, window, 0, 0, 0, {x}, {y}, {controlPressed.ToString().ToLower()}, {altPressed.ToString().ToLower()}, {shiftPressed.ToString().ToLower()}, false, 0, null);
4453 arguments[0].dispatchEvent(theEvent);
4460 JavaScriptFocus(browser, webElement);
4462 if (
false == browser.BrowserSettings.UseHeadless)
4463 HardwareHelper.SendKey(GPAL.VK_F10, ModifierKeys.Shift);
4465 new Actions(browserDriver).ContextClick((IWebElement)webElement.WebElement).Perform();
4468 BrowserHelper.ExecuteJavaScript(browser, clickScript, webElement.WebElement);
4470 catch (GPALException)
4474 catch (Exception ex)
4476 GPAL.PublishSimpleEvent(GPALEventType.EXCEPTION, $
"Failed to [{clickString}] element [{webElement.Text}]", webElement, GPALObjectType.Other, ex);
4480 else if (
true == browser.UsePuppeteer)
4482 string sessionId = browser.PuppeteerCommunicator.GetEffectiveSessionId();
4484 string selector = webElement.Css;
4486 string clickScript =
null;
4492 const sel = `{selector.Replace("'", "\\'")}`;
4494 const el = document.querySelector(sel) || document.evaluate(sel, document, null, XPathResult.FIRST_ORDERED_NODE_TYPE, null).singleNodeValue;
4495 if (!el) return false;
4497 const rect = el.getBoundingClientRect();
4498 const cx = rect.left + rect.width / 2 + window.scrollX;
4499 const cy = rect.top + rect.height / 2 + window.scrollY;
4501 const opts = {{ bubbles: true, cancelable: true, clientX: cx, clientY: cy, view: window }};
4503 // === LEFT CLICK (or double) ===
4504 if ({(clickType == ClickType.LeftClick || clickType == ClickType.LeftDoubleClick).ToString().ToLower()}) {{
4505 const count = {(clickType == ClickType.LeftDoubleClick ? 2 : 1)};
4506 for (let i = 0; i < count; i++) {{
4507 el.dispatchEvent(new MouseEvent('mousedown', {{...opts, button: 0, buttons: 1}}));
4508 el.dispatchEvent(new MouseEvent('mouseup', {{...opts, button: 0, buttons: 0}}));
4509 el.dispatchEvent(new MouseEvent('click', {{...opts, button: 0, detail: i+1}}));
4510 if (i === 0) Atomics.wait(new Int32Array(new SharedArrayBuffer(4)), 0, 0, 80); // ~80ms between clicks
4515 // === MIDDLE CLICK (eBay pop-under) ===
4516 if ({(clickType == ClickType.MiddleClick).ToString().ToLower()}) {{
4517 el.dispatchEvent(new MouseEvent('mousedown', {{...opts, button: 1, buttons: 4}}));
4518 el.dispatchEvent(new MouseEvent('mouseup', {{...opts, button: 1, buttons: 0}}));
4519 el.dispatchEvent(new MouseEvent('click', {{...opts, button: 1}}));
4523 // === RIGHT CLICK (context menu) ===
4524 if ({(clickType == ClickType.RightClick).ToString().ToLower()}) {{
4525 el.dispatchEvent(new MouseEvent('mousedown', {{...opts, button: 2, buttons: 2}}));
4526 el.dispatchEvent(new MouseEvent('mouseup', {{...opts, button: 2, buttons: 0}}));
4527 el.dispatchEvent(new MouseEvent('contextmenu', {{...opts, button: 2}}));
4538 browser.PuppeteerCommunicator.SendCommand<
object>(DevToolsMethods.RuntimeEvaluate,
new
4540 expression = clickScript,
4541 returnByValue =
true
4542 }, sessionId).GetAwaiter().GetResult();
4589 internal static void RefreshLocation(Browser browser, GPALElement element)
4593 Rectangle live =
new Rectangle(0, 0, 0, 0);
4595 if (
true == browser.UsePuppeteer && 0 != element.ElementBackendNodeId)
4600 live = browser.PuppeteerClient.GetBoundingClientRect(element.ElementBackendNodeId.ToString()).Execute<Rectangle>();
4601 else if (
true == browser.UseOttoMagic)
4603 live = browser.MagicHelper.GetBoundingClientRect(element.Css);
4610 bool inFrame = browser.CurrentUOW?.ContextPath?.Any(context => ElementType.IFrame == context.elementType) ??
false;
4612 if (0 < live.Width && 0 < live.Height &&
false == inFrame)
4613 live.Offset(browser.MagicHelper.WindowPageOffsetX(), browser.MagicHelper.WindowPageOffsetY());
4615 else if (
null != element.WebElement)
4616 live =
new Rectangle(element.WebElement.Location, element.WebElement.Size);
4618 if (0 < live.Width && 0 < live.Height)
4620 element.Location = live.Location;
4621 element.Size = live.Size;
4622 element.BoundingRect =
new ClientRectangle(element.Location, element.Size);
4627 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);
4629 catch (GPALException)
4633 catch (Exception ex)
4635 GPAL.PublishSimpleEvent(GPALEventType.EXCEPTION, $
"Could not re-read where [{element.TagName}] is, using the reading from when it was found", browser, GPALObjectType.Browser, ex);
4651 internal static bool PointHitsElement(Browser browser, GPALElement element, Point screenPoint)
4653 bool retVal =
false;
4658 Rectangle content = GetWindowRectangle(browser);
4659 int x = screenPoint.X - content.X;
4660 int y = screenPoint.Y - content.Y;
4662 if (
true == browser.UsePuppeteer)
4663 retVal = browser.BrowserSettings.PuppeteerCommunicator.ElementFromPoint(element, x, y).GetAwaiter().GetResult();
4664 else if (
true == browser.UseOttoMagic)
4665 retVal = browser.MagicHelper.ElementFromPoint(element.Css, x, y);
4668 var result = BrowserHelper.ExecuteJavaScriptObj(
4669 "var elem = arguments[0]; " +
4670 "var hit = document.elementFromPoint(arguments[1], arguments[2]); " +
4671 "return hit === elem || elem.contains(hit);", browser, element.WebElement, x, y);
4673 retVal =
null != result &&
true == Convert.ToBoolean(result);
4678 catch (GPALException)
4682 catch (Exception ex)
4684 GPAL.PublishSimpleEvent(GPALEventType.EXCEPTION, $
"Could not ask what is at [{screenPoint.X}, {screenPoint.Y}] for [{element.TagName}]", browser, GPALObjectType.Browser, ex);
4695 private static readonly
int[] hitTestInsets = { 24, 40, 60, 12 };
4699 private static readonly
float[] hitTestFractions = { 0.75f, 0.50f, 0.25f, 0.10f };
4701 public static bool IsVisibleInViewport(Browser browser, GPALElement element)
4703 bool retval =
false;
4705 if (
true == browser.UsePuppeteer)
4706 retval = browser.PuppeteerClient.IsVisibleInViewport(element).Execute<
bool>();
4707 else if (
true == browser.UseOttoMagic)
4708 retval = browser.MagicHelper.IsVisibleInViewport(element.Css);
4711 IWebDriver browserDriver = browser.BrowserDriver;
4713 var result = BrowserHelper.ExecuteJavaScriptObj(
4714 "var elem = arguments[0]; " +
4715 "const rect = elem.getBoundingClientRect(); " +
4717 " rect.top >= 0 && " +
4718 " rect.left >= 0 && " +
4719 " rect.bottom <= (window.innerHeight || document.documentElement.clientHeight) && " +
4720 " rect.right <= (window.innerWidth || document.documentElement.clientWidth) " +
4721 ");", browser, element.WebElement);
4724 retval = (bool)result;
4735 public static bool IsEndOfPage(Browser browser, GPALElement element)
4739 if (
true == browser.UsePuppeteer)
4740 retval = browser.PuppeteerClient.IsEndOfPage().Execute<
bool>();
4741 else if (
true == browser.UseOttoMagic)
4742 retval = browser.MagicHelper.IsEndOfPage();
4745 IWebDriver browserDriver = browser.BrowserDriver;
4748 var result = BrowserHelper.ExecuteJavaScriptObj(
"if ((window.innerHeight + window.scrollY) >= document.documentElement.scrollHeight) { return true; } else { return false; }", browser,
null);
4751 retval = (bool)result;
4765 private static bool SettledIsVisibleInViewport(Browser browser, GPALElement element)
4767 bool visible = IsVisibleInViewport(browser, element);
4768 int checksLeft = VisibilitySettleChecks;
4770 while (
false == visible && 0 < checksLeft--)
4772 Thread.Sleep(VisibilitySettleMs);
4773 visible = IsVisibleInViewport(browser, element);
4794 private static bool IsBiggerThanViewport(Size elementSize, Rectangle viewport)
4796 return elementSize.Width > viewport.Width || elementSize.Height > viewport.Height;
4798 internal static bool ScrollUntilVisible(Browser browser, GPALElement element)
4800 bool visible = SettledIsVisibleInViewport(browser, element);
4804 if (
false == visible &&
true == IsBiggerThanViewport(element.Size, GetWindowRectangle(browser)))
4807 if (
false == visible)
4809 int pagesLeft = MaxScrollPages;
4811 _ = browser.PageTop;
4812 visible = SettledIsVisibleInViewport(browser, element);
4816 while (
false == visible && 0 < pagesLeft-- &&
false == IsEndOfPage(browser, element))
4818 _ = browser.PageDown;
4819 visible = SettledIsVisibleInViewport(browser, element);
4822 if (
false == visible)
4824 _ = browser.PageTop;
4825 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);
4837 private static IntPtr GetWindowHandle(BrowserType browserType)
4839 IntPtr hWnd = IntPtr.Zero;
4840 Process[] processes =
null;
4842 if (BrowserType.Chrome == browserType)
4843 processes = Process.GetProcessesByName(
"chrome");
4844 else if (BrowserType.Edge == browserType)
4845 processes = Process.GetProcessesByName(
"msedge");
4846 else if (BrowserType.FireFox == browserType)
4847 processes = Process.GetProcessesByName(
"firefox");
4849 if (processes.Length > 0)
4851 foreach (Process proc
in processes)
4853 if (proc.MainWindowHandle == IntPtr.Zero)
4857 hWnd = proc.MainWindowHandle;
4880 [ThreadStatic]
private static bool _lastPlacementQuestionable;
4884 [ThreadStatic]
private static string _lastClampedPoint;
4890 [ThreadStatic]
private static Rectangle _lastOuterWindow;
4892 public static void HardwareClick(Browser browser, dynamic element, ClickType clickType, ModifierKeys modifierKeys, Selector selector,
bool isClickable =
false)
4894 Rectangle rect, iframeRect =
new Rectangle(0, 0, 0, 0);
4895 bool clicked =
false;
4896 _lastPlacementQuestionable =
false;
4897 _lastClampedPoint =
null;
4901 BrowserHelper.TopBrowser(browser,
false);
4903 if (ModifierKeys.NONE != modifierKeys)
4904 HardwareHelper.PressModifierKey(modifierKeys);
4908 rect = HardwareMoveTo(browser, element, selector);
4914 Rectangle browserRect = GetWindowRectangle(browser);
4915 bool insideBrowser = 0 < rect.Width && 0 < rect.Height
4916 && rect.X > browserRect.Left && rect.X < browserRect.Right
4917 && rect.Y > browserRect.Top && rect.Y < browserRect.Bottom;
4919 if (
"GPALElement" == element.TagName ||
true == insideBrowser)
4922 HardwareHelper.HardwareClick(rect.X, rect.Y, clickType);
4929 _lastPlacementQuestionable =
true;
4931 if (
false == GPAL.NoFallbackRecoveryActions)
4933 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);
4935 if (ModifierKeys.NONE != modifierKeys)
4936 HardwareHelper.ReleaseModifierKeys(modifierKeys);
4938 if (
true == browser.UseOttoMagic)
4939 browser.MagicHelper.LeftClick(element.Css);
4940 else if (
true == browser.UsePuppeteer)
4941 browser.PuppeteerClient.LeftClick(element).Execute();
4943 JavaScriptClick(browser, element, clickType, modifierKeys);
4948 GPAL.PublishSimpleEvent(GPALEventType.WARNING,
"Unable to get screen coordinates for webelement, continuing.", browser, GPALObjectType.Browser);
4958 GPAL.PublishSimpleEvent(
null == _lastClampedPoint ? GPALEventType.INFO : GPALEventType.WARNING,
4959 $
"Element [{element.TagName}] for selector [{selector.Name}] [{selector.InteractionType.ToString()}][{clickType.ToString()}]{(true == clicked ? "" : " [NOT]
")} clicked{(null == _lastClampedPoint ? "" : " " + _lastClampedPoint)}.", browser, GPALObjectType.Browser);
4961 if (ModifierKeys.NONE != modifierKeys)
4962 HardwareHelper.ReleaseModifierKeys(modifierKeys);
4974 public static void DragAndDrop(IBrowser browser, ModifierKeys modifierKeys = ModifierKeys.NONE)
4976 var driver = browser.BrowserDriver;
4977 Rectangle iframeRect =
new Rectangle(0, 0, 0, 0);
4979 List<GPALElement> matchedElements;
4981 foreach (Selector selector
in ((Browser)browser).CurrentUOW.WithSelectorList)
4983 if (SelectorType.Selector != selector.SelectorType)
4986 ReadOnlyCollection<GPALElement> elems = ElementHelper.FindWebElements((Browser)browser, ((Browser)browser).CurrentUOW, selector, out
bool matchedAll, out matchedElements);
4989 if (
true == selector.DeleteMe ||
null == elems || 0 == elems.Count)
4991 PublishToEventHandler((Browser)browser, selector, elems?.ToList());
4995 if (
null != matchedElements)
4996 elems =
new ReadOnlyCollection<GPALElement>(matchedElements);
5000 foreach (GPALElement elem
in elems)
5002 if (
true == ((Browser)browser).BrowserSettings.UseHardware || InteractionType.Hardware == selector.InteractionType)
5009 BrowserHelper.TopBrowser((Browser)browser,
false);
5013 Rectangle rect = HardwareMoveTo((Browser)browser, elem, selector);
5015 if (
"GPALElement" == elem.TagName || (0 <= rect.Width && 0 <= rect.Height && 0 < rect.X && 0 < rect.Y))
5017 int endX = rect.X + selector.DeltaX;
5018 int endY = rect.Y + selector.DeltaY;
5020 HardwareHelper.MouseDown(rect.X, rect.Y, ClickType.LeftClick);
5021 HardwareHelper.MoveMouse(endX, endY, 4, 4, 0.5);
5022 HardwareHelper.MouseUp(endX, endY, ClickType.LeftClick);
5025 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);
5032 else if (
true == browser.UseOttoMagic)
5034 browser.MagicHelper.DragAndDrop(elem.Css, selector.DeltaX, selector.DeltaY, selector.OffsetX, selector.OffsetY);
5036 else if (
true == browser.UsePuppeteer)
5038 browser.PuppeteerClient.DragAndDrop(elem.ElementHandle).WithDeltaX(selector.DeltaX).WithDeltaY(selector.DeltaY).WithOffsetX(selector.OffsetX).WithOffsetY(selector.OffsetY).Execute();
5040 else if (
true == browser.UseSelenium)
5047 if (BrowserType.FireFox == ((Browser)browser).BrowserSettings.BrowserType
5048 &&
"true" == elem.WebElement?.GetAttribute(
"draggable"))
5049 JavaScriptDragAndDrop((Browser)browser, elem, selector.DeltaX, selector.DeltaY, selector.OffsetX, selector.OffsetY);
5051 new Actions(driver).ClickAndHold(elem.WebElement).MoveByOffset(selector.DeltaX, selector.DeltaY).Release().Perform();
5054 if (++rowCount >= ((Browser)browser).CurrentUOW.WithAllThatMatch)
5073 public static bool JavaScriptDragAndDrop(Browser browser, GPALElement element,
int deltaX,
int deltaY,
int offsetX,
int offsetY)
5076 if (
null == element?.WebElement)
5079 ScrollIntoView(browser, element);
5082 string javaScript =
@"
5083 var src = arguments[0];
5084 var offsetX = arguments[1], offsetY = arguments[2];
5085 var deltaX = arguments[3], deltaY = arguments[4];
5087 var rect = src.getBoundingClientRect();
5088 var startX = rect.left + rect.width / 2 + offsetX;
5089 var startY = rect.top + rect.height / 2 + offsetY;
5090 var endX = startX + deltaX;
5091 var endY = startY + deltaY;
5093 var target = document.elementFromPoint(endX, endY);
5096 return 'nothing at drop point ' + endX + ',' + endY;
5098 var dt = new DataTransfer();
5100 function fire(type, el, x, y) {
5101 el.dispatchEvent(new DragEvent(type, {
5102 bubbles: true, cancelable: true, composed: true,
5103 clientX: x, clientY: y, dataTransfer: dt
5107 fire('dragstart', src, startX, startY);
5108 fire('dragenter', target, endX, endY);
5109 fire('dragover', target, endX, endY);
5110 fire('drop', target, endX, endY);
5111 fire('dragend', src, endX, endY);
5118 if (
"ok" != result?.Trim())
5120 GPAL.
PublishSimpleEvent(GPALEventType.WARNING, $
"HTML5 drag and drop did not complete for selector [{element.Selector?.Name}] [{result}].", browser, GPALObjectType.Browser);
5141 ScrollIntoView(browser, element);
5144 String javaScript =
"var evObj = document.createEvent('MouseEvents');"
5145 +
"evObj.initMouseEvent(\"mouseover\",true, false, window, 0, 0, 0, 0, 0, false, false, false, false, 0, null);"
5146 +
"arguments[0].dispatchEvent(evObj);";
5157 private static Rectangle GetWindowRect(
string windowTitle)
5159 IntPtr hWnd = IntPtr.Zero;
5160 foreach (Process pList
in Process.GetProcesses())
5162 if (pList.MainWindowTitle.Contains(windowTitle))
5164 hWnd = pList.MainWindowHandle;
5169 if (hWnd != IntPtr.Zero)
5172 GetWindowRect(hWnd, out rect);
5173 return new Rectangle(rect.Left, rect.Top, rect.Right - rect.Left, rect.Bottom - rect.Top);
5177 return Rectangle.Empty;
5187 [DllImport(
"user32.dll")]
5188 [
return: MarshalAs(UnmanagedType.Bool)]
5189 private static extern bool GetWindowRect(IntPtr hWnd, out RECT lpRect);
5191 [StructLayout(LayoutKind.Sequential)]
5213 private static void RestoreFrameContext(Browser browser)
5215 if (
null == browser.CurrentUOW?.ContextPath)
5218 foreach (UnitOfWork.WebElementWithType contextType in browser.CurrentUOW.ContextPath)
5219 if (ElementType.IFrame == contextType.elementType &&
null != contextType.gPalElement?.WebElement)
5220 browser.BrowserDriver.SwitchTo().Frame((IWebElement)contextType.gPalElement.WebElement);
5230 public static Rectangle GetWindowRectangle(Browser browser, out Rectangle outerWindow)
5232 Rectangle retVal = GetWindowRectangle(browser);
5235 outerWindow = _lastOuterWindow;
5240 public static Rectangle GetWindowRectangle(Browser browser)
5243 bool jsSuccess =
false;
5244 int screenX = 0, screenY = 0, outerHeight = 0, innerHeight = 0, outerWidth = 0, innerWidth = 0;
5248 bool withReturn = !browser.UsePuppeteer;
5249 string prefix = withReturn ?
"return " :
"";
5250 dynamic value =
null;
5252 if (
true == browser.UseOttoMagic)
5254 Rectangle rect = browser.MagicHelper.GetWindowRectangle();
5256 outerHeight = rect.Height;
5257 innerHeight = rect.Y;
5258 outerWidth = rect.Width;
5259 innerWidth = rect.X;
5260 screenX = browser.MagicHelper.WindowScreenLeft();
5261 screenY = browser.MagicHelper.WindowScreenTop();
5264 else if (
true == browser.UsePuppeteer)
5266 outerHeight = browser.PuppeteerClient.WindowOuterHeight().Execute<
int>();
5267 innerHeight = browser.PuppeteerClient.WindowInnerHeight().Execute<
int>();
5268 outerWidth = browser.PuppeteerClient.WindowOuterWidth().Execute<
int>();
5269 innerWidth = browser.PuppeteerClient.WindowInnerWidth().Execute<
int>();
5270 screenX = browser.PuppeteerClient.WindowScreenLeft().Execute<
int>();
5271 screenY = browser.PuppeteerClient.WindowScreenTop().Execute<
int>();
5276 string jsScript = $
"{prefix} ["
5277 +
"window.screenLeft, "
5278 +
"window.screenTop, "
5279 +
"window.outerHeight, window.innerHeight, window.outerWidth, window.innerWidth]";
5285 bool inFrame = browser.CurrentUOW?.ContextPath?.Any(context => ElementType.IFrame == context.elementType) ??
false;
5289 if (
true == inFrame)
5290 browser.BrowserDriver.SwitchTo().DefaultContent();
5292 value = BrowserHelper.ExecuteJavaScriptObj(jsScript, browser,
null);
5296 if (
true == inFrame)
5297 RestoreFrameContext(browser);
5299 IList<object> values;
5301 if (value is Newtonsoft.Json.Linq.JArray jArray)
5303 values = jArray.Select(token => (
object)token).ToList();
5305 else if (value is IList<object> list)
5312 values =
new List<object> { 0, 0, 0, 0, 0, 0 };
5315 screenX = values.Count > 0 ? Convert.ToInt32(values[0]) : 0;
5316 screenY = values.Count > 1 ? Convert.ToInt32(values[1]) : 0;
5317 outerHeight = values.Count > 2 ? Convert.ToInt32(values[2]) : 0;
5318 innerHeight = values.Count > 3 ? Convert.ToInt32(values[3]) : 0;
5319 outerWidth = values.Count > 4 ? Convert.ToInt32(values[4]) : 0;
5320 innerWidth = values.Count > 5 ? Convert.ToInt32(values[5]) : 0;
5324 if (jsSuccess && innerWidth > 0 && innerHeight > 0)
5326 _lastOuterWindow =
new Rectangle(screenX, screenY, outerWidth, outerHeight);
5328 int navHeight = outerHeight - innerHeight;
5329 int navWidth = outerWidth - innerWidth;
5335 int border = navWidth / 2;
5336 int contentLeft = screenX + border;
5337 int contentTop = screenY + navHeight - border;
5338 return new Rectangle(contentLeft, contentTop, innerWidth, innerHeight);
5341 catch (GPALException)
5345 catch (Exception ex)
5347 GPAL.PublishSimpleEvent(GPALEventType.EXCEPTION, $
"JS execution failed for browser [{browser.BrowserSettings.BrowserType}]", browser, GPALObjectType.Browser, ex);
5353 GPAL.PublishSimpleEvent(GPALEventType.ERROR, $
"Failed to get rectangle for [{browser.BrowserSettings.BrowserType}].", browser, GPALObjectType.Browser);
5355 return new Rectangle(0, 0, 0, 0);
5364 public static Rectangle GetWindowOuterRectangle(Browser browser)
5366 int screenX = 0, screenY = 0, outerWidth = 0, outerHeight = 0;
5370 if (
true == browser.UseOttoMagic)
5373 Rectangle rect = browser.MagicHelper.GetWindowRectangle();
5374 outerWidth = rect.Width;
5375 outerHeight = rect.Height;
5376 screenX = browser.MagicHelper.WindowScreenLeft();
5377 screenY = browser.MagicHelper.WindowScreenTop();
5379 else if (
true == browser.UsePuppeteer)
5381 outerWidth = browser.PuppeteerClient.WindowOuterWidth().Execute<
int>();
5382 outerHeight = browser.PuppeteerClient.WindowOuterHeight().Execute<
int>();
5383 screenX = browser.PuppeteerClient.WindowScreenLeft().Execute<
int>();
5384 screenY = browser.PuppeteerClient.WindowScreenTop().Execute<
int>();
5388 var value = BrowserHelper.ExecuteJavaScriptObj(
"return [window.screenLeft, window.screenTop, window.outerWidth, window.outerHeight]", browser,
null);
5389 IList<object> values;
5391 if (value is Newtonsoft.Json.Linq.JArray jArray)
5392 values = jArray.Select(token => (
object)token).ToList();
5393 else if (value is IList<object> list)
5396 values =
new List<object> { 0, 0, 0, 0 };
5398 screenX = values.Count > 0 ? Convert.ToInt32(values[0]) : 0;
5399 screenY = values.Count > 1 ? Convert.ToInt32(values[1]) : 0;
5400 outerWidth = values.Count > 2 ? Convert.ToInt32(values[2]) : 0;
5401 outerHeight = values.Count > 3 ? Convert.ToInt32(values[3]) : 0;
5404 return new Rectangle(screenX, screenY, outerWidth, outerHeight);
5406 catch (GPALException)
5410 catch (Exception ex)
5412 GPAL.PublishSimpleEvent(GPALEventType.EXCEPTION, $
"Failed to get outer window rectangle for [{browser.BrowserSettings.BrowserType}]", browser, GPALObjectType.Browser, ex);
5413 return new Rectangle(0, 0, 0, 0);
5422 public static Dictionary<string, object> GetCssAttributes(Browser browser, IWebElement webElement)
5425 const el = arguments[0];
5426 const styles = window.getComputedStyle(el);
5429 // 2. Values that are ""noise"" - we never want them in the result
5430 const EMPTY_VALUES = new Set([
5431 '', 'none', 'normal', 'inherit', 'initial', 'unset',
5432 'auto', '0px', '0', 'transparent'
5435 // 3. The only properties we ever care about
5436 const RELEVANT = new Set([
5437 'display', 'visibility', 'opacity', 'pointer-events', 'overflow', 'clip-path',
5438 'position', 'top', 'left', 'right', 'bottom', 'z-index',
5439 'width', 'height', 'min-width', 'min-height', 'max-width', 'max-height',
5440 'margin', 'margin-top', 'margin-right', 'margin-bottom', 'margin-left',
5441 'padding', 'padding-top', 'padding-right', 'padding-bottom', 'padding-left',
5442 'border', 'border-width', 'border-style', 'border-color', 'box-sizing',
5443 'flex', 'flex-direction', 'flex-wrap', 'flex-grow', 'flex-shrink', 'flex-basis',
5444 'justify-content', 'align-items', 'align-self', 'order',
5445 'grid', 'grid-template', 'grid-area', 'grid-column', 'grid-row',
5446 'transform', 'transform-origin', 'transition', 'animation',
5447 'cursor', 'user-select', 'content', 'filter'
5450 // 4. Iterate only the properties that exist on the style object
5451 for (let i = 0; i < styles.length; i++) {
5452 const prop = styles[i]; // e.g. ""display""
5453 if (!RELEVANT.has(prop)) { continue; } // skip everything else
5455 const value = styles.getPropertyValue(prop).trim(); // ""block"", ""0.5"", etc.
5457 // Skip empty / default values
5458 if (!value || EMPTY_VALUES.has(value)) { continue; }
5460 props[prop] = value;
5465 var dict = BrowserHelper.ExecuteJavaScriptObj(script, browser, webElement) as Dictionary<string, object>;
5467 return dict?.ToDictionary(k => k.Key, v => v.Value ??
"");
5473 public static Dictionary<string, object> GetDomAttributes(
5475 IWebElement webElement)
5477 const string script =
@"
5478 const cleanText = (str) => (str || '').replace(/\s+/g, ' ').trim();
5479 const el = arguments[0];
5480 const attrs = el.attributes;
5482 for (let i = 0; i < attrs.length; i++) {
5484 result[a.name] = a.value; // value is always a string or empty
5486 // Also add a few handy built-in properties that are not attributes
5487 result['id'] = el.id;
5488 result['className'] = el.className;
5489 result['tagName'] = el.tagName;
5490 result['innerText'] = cleanText(el.innerText);
5491 result['textContent'] = cleanText(el.textContent);
5495 var raw = BrowserHelper.ExecuteJavaScriptObj(script, browser, webElement)
5496 as Dictionary<string, object>;
5499 return raw?.ToDictionary(
5501 kvp => kvp.Value ??
""
5502 ) ??
new Dictionary<string, object>();
5508 public static Dictionary<string, object> GetDomProperties(
5510 IWebElement webElement)
5512 const string script =
@"
5513 const el = arguments[0];
5516 if (""textContent"" in el) props.textContent = el.textContent;
5517 if (""innerHTML"" in el) props.innerHTML = el.innerHTML;
5518 if (""innerText"" in el) props.innerText = el.innerText;
5519 if (""outerHTML"" in el) props.outerHTML = el.outerHTML;
5520 if (""className"" in el) props.className = el.className;
5521 if (""disabled"" in el) props.disabled = el.disabled;
5526 var raw = BrowserHelper.ExecuteJavaScriptObj(script, browser, webElement)
5527 as Dictionary<string, object>;
5530 return raw?.ToDictionary(
5532 kvp => kvp.Value ??
""
5533 ) ??
new Dictionary<string, object>();
5547 internal static bool ResolveWebElementIssue(Browser browser, UnitOfWork currentUOW, ref dynamic webOrGpalElement, Exception exception, ref
int retries,
string errorMessage)
5550 if (
true == GPAL.NoFallbackRecoveryActions)
5553 bool shouldRetry =
false;
5554 IWebElement iWebElement;
5555 GPALElement gPalElement =
null;
5557 if (webOrGpalElement is GPALElement)
5559 gPalElement = webOrGpalElement;
5560 iWebElement = gPalElement.WebElement;
5564 iWebElement = webOrGpalElement;
5565 gPalElement =
new GPALElement() { WebElement = iWebElement };
5569 string recoveryMessage = $
"Attempting recovery for [{exception.GetType().Name}]. Retries left: [{retries}]";
5570 GPAL.PublishSimpleEvent(GPALEventType.INFO, recoveryMessage, iWebElement, GPALObjectType.Other);
5574 if (exception is StaleElementReferenceException)
5577 if (
null != currentUOW.CurrentSelector.WebSelectorMatchedResults ||
null != currentUOW.CurrentSelector.WebSelectorFoundResults)
5579 currentUOW.CurrentSelector.WebSelectorMatchedResults =
null;
5580 currentUOW.CurrentSelector.WebSelectorFoundResults =
null;
5584 _ = browser.Refresh;
5586 if (
null != webOrGpalElement)
5588 var elems = FindWebElements(browser, currentUOW, currentUOW.CurrentSelector, out
bool matchedAll, out List<GPALElement> matched,
null,
false);
5590 if (0 < elems.Count())
5591 webOrGpalElement = elems[0].WebElement;
5595 else if (exception is ElementClickInterceptedException)
5598 ElementHelper.MoveTo(gPalElement, currentUOW.CurrentSelector.OffsetX, currentUOW.CurrentSelector.OffsetY, browser);
5601 if (
false == (shouldRetry = IsElementClickable(iWebElement)))
5607 var overlays = browser.BrowserDriver.FindElements(By.XPath(
"//div[contains(@class, 'modal') or contains(@class, 'overlay') or contains(@class, 'popup')]"));
5610 foreach (var overlay
in overlays)
5612 if (overlay.Displayed)
5614 var closeButton = browser.BrowserDriver.FindElements(By.XPath(
".//button[contains(@class, 'close') or contains(text(), 'Close') or @aria-label='Close']"));
5615 if (closeButton.Any())
5617 closeButton.First().Click();
5618 shouldRetry = IsElementClickable(iWebElement);
5622 BrowserHelper.ExecuteJavaScriptObj(
"arguments[0].style.display='none';", browser, overlay);
5628 catch (NoSuchElementException)
5634 else if (exception is ElementNotVisibleException)
5637 ElementHelper.MoveTo(gPalElement, currentUOW.CurrentSelector.OffsetX, currentUOW.CurrentSelector.OffsetY, browser);
5641 if (
false == IsElementClickable(iWebElement))
5642 browser.ExecuteJavaScriptStr(
"window.scrollBy(0, -100, behavior: 'instant');");
5644 shouldRetry = IsElementClickable(iWebElement);
5647 catch (GPALException)
5651 catch (Exception ex)
5654 GPAL.PublishSimpleEvent(GPALEventType.EXCEPTION, $
"Recovery failed", iWebElement, GPALObjectType.Other, ex);
5655 shouldRetry =
false;
5658 return shouldRetry && retries > 0;
5665 internal static bool IsElementClickable(IWebElement webElement)
5667 return webElement.Displayed && webElement.Enabled;
5669 internal static bool IsElementClickable(GPALElement element)
5671 return element.Displayed && element.Enabled;
5679 public static bool IsElementEditable(GPALElement elem)
5684 string tag = elem?.WebElement?.TagName.ToUpperInvariant() ?? elem?.TagName.ToUpperInvariant();
5685 string type =
string.Empty;
5688 type = elem.GetAttribute(
"type")?.ToLower();
5692 string cursor = elem?.GetCssValue(
"cursor")?.ToLower();
5693 if (cursor ==
"text")
5697 bool retval = (tag ==
"INPUT" &&
5698 (type ==
"text" || type ==
"email" || type ==
"password" || type ==
"search"))
5699 || tag ==
"TEXTAREA"
5700 || (tag ==
"DIV" && elem.GetAttribute(
"contenteditable")?.ToLower() ==
"true");
5705 string classAttr = elem?.GetAttribute(
"class") ??
"";
5706 if (
null != classAttr)
5707 retval = classAttr.Split(
' ').Contains(
"Editable");
5733 public static void SendString(Browser browser,
string textToSend)
5735 int delayMs = GPAL.TypingDelay;
5737 if (
true == browser.BrowserSettings.UseHardware ||
true == GPAL.GPALSettings.UseHardware)
5738 HardwareHelper.SendString(textToSend, delayMs);
5739 else if (
true == browser.BrowserSettings.UseOttoMagic)
5740 browser.MagicHelper.SendString(textToSend, delayMs);
5741 else if (
true == browser.BrowserSettings.UsePuppeteer)
5742 browser.PuppeteerClient.SendString(textToSend, delayMs);
5745 foreach (SelectorSettings.SelectorPathEntry selectorPathEntry in browser.CurrentUOW.CurrentSelector?.SelectorPaths)
5746 if (
null != selectorPathEntry.WebSelectorFoundResults)
5747 foreach (GPALElement webElement
in selectorPathEntry.WebSelectorFoundResults)
5748 if (InteractionType.JavaScript == browser.CurrentUOW.CurrentSelector?.InteractionType ||
true == GPAL.GPALSettings.UseJavaScript)
5751 BrowserHelper.ExecuteJavaScriptObj(
@"
5752 function sendStringBlindly(text) {
5753 for (let char of text) {
5754 document.dispatchEvent(new KeyboardEvent('keydown', {{
5758 document.dispatchEvent(new KeyboardEvent('keyup', {{
5764 $
"sendStringBlindly({textToSend});", browser);
5768 foreach (
char c
in textToSend)
5771 Thread.Sleep(HardwareHelper.GetTypingDelay(delayMs));
5772 BrowserHelper.ExecuteJavaScriptObj(
@"
5773 var char = arguments[0];
5774 document.dispatchEvent(new KeyboardEvent('keydown', { key: char, bubbles: true }));
5775 document.dispatchEvent(new KeyboardEvent('keyup', { key: char, bubbles: true }));",
5776 browser, c.ToString());
5783 IWebElement body = browser.BrowserSettings.BrowserDriver.FindElement(By.TagName(
"body"));
5785 Actions actions =
new Actions(browser.BrowserSettings.BrowserDriver);
5787 foreach (
char c
in textToSend)
5789 if (
false == first && 0 < delayMs)
5790 actions.Pause(TimeSpan.FromMilliseconds(HardwareHelper.GetTypingDelay(delayMs)));
5792 .KeyDown(body, c.ToString())
5793 .KeyUp(body, c.ToString());
5796 actions.Build().Perform();
5808 public static void SendKey(Browser browser,
byte VKCode)
5810 if (
true == browser.BrowserSettings.UseHardware ||
true == GPAL.GPALSettings.UseHardware)
5812 if (GPAL.VK_RETURN == VKCode)
5814 var matched = browser.CurrentUOW.CurrentSelector?.WebSelectorMatchedResults?.FirstOrDefault();
5815 if (
false == ElementHelper.IsElementEditable(matched))
5822 HardwareHelper.SendKey(VKCode);
5824 else if (
true == browser.BrowserSettings.UseOttoMagic)
5825 browser.MagicHelper.SendKey(VKCode);
5826 else if (
true == browser.BrowserSettings.UsePuppeteer)
5827 browser.PuppeteerClient.SendKey(VKCode).Execute();
5831 foreach (SelectorSettings.SelectorPathEntry selectorPathEntry in browser.CurrentUOW.CurrentSelector?.SelectorPaths)
5832 if (
null != selectorPathEntry.WebSelectorFoundResults)
5833 foreach (GPALElement webElement
in selectorPathEntry.WebSelectorFoundResults)
5834 if (InteractionType.JavaScript == browser.CurrentUOW.CurrentSelector?.InteractionType)
5835 ElementHelper.JavaScriptSendKey(browser, webElement, VKCode);
5837 ElementHelper.SeleniumSendKeys(browser, webElement, VKCode);
5841 public static string GetEnumNames<T>(T value) where T :
struct, Enum
5843 if (!typeof(T).IsDefined(typeof(FlagsAttribute),
false))
5844 return value.ToString();
5846 var flags = Enum.GetValues(typeof(T))
5848 .Where(flag => !Equals(flag,
default(T)) && value.HasFlag(flag))
5849 .Select(flag => flag.ToString());
5851 string retVal =
string.Join(
", ", flags);
5853 return false ==
string.IsNullOrEmpty(retVal) ? retVal :
"NONE";
5862 public static string GenerateCssSelector(IBrowser browser, IWebElement webElement)
5864 return BrowserHelper.ExecuteJavaScript(browser,
@"
5865 function getCssPath(el) {
5866 if (!(el instanceof Element))
5871 while (el && el.nodeType === Node.ELEMENT_NODE) {
5874 path.unshift('#' + CSS.escape(el.id));
5878 var selector = el.nodeName.toLowerCase();
5883 while ((sibling = sibling.previousElementSibling) != null) {
5884 if (sibling.nodeName === el.nodeName)
5888 selector += ':nth-of-type(' + nth + ')';
5890 path.unshift(selector);
5892 el = el.parentElement;
5895 return path.join(' > ');
5897 return getCssPath(arguments[0]);
static string ExecuteJavaScript(IBrowser browser, string executeMe, params object[] args)
Runs a script and hands back what it produced. Selenium's executor is a function body,...
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....
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...
Settings for the current selector. Use this only for debugging.