106 public delegate CallIfStatus
CallIfDelegate(
IBrowser browser, List<IGPALElement> foundElements, List<IGPALElement> matchedElements,
Selector selector,
bool matchedAll);
190 #region <Working Vars>
195 internal UnitOfWork persistentUOW {
get;
set; } =
null;
201 internal DateTime? BrowserLaunched {
get;
set; } =
null;
206 internal int tabsWeOpened {
get;
set; } = 0;
213 internal string GhostTabUrl {
get;
set; } =
null;
218 internal string GhostTabHandle {
get;
set; } =
null;
228 private readonly Dictionary<string, string> tabWindows =
new Dictionary<string, string>();
230 internal bool _areRobotsAllowed =
true;
235 public bool AreRobotsAllowed {
get => _areRobotsAllowed;
internal set => _areRobotsAllowed = value; }
237 #endregion <Working Vars>
263 DriverLocation =
GPAL.DriverLocation,
264 DownloadTimeoutInSec = GPAL.GPALSettings.DownloadTimeoutInSec
268 persistentUOW =
new UnitOfWork() { Browser =
this };
269 WorkflowManager =
new WorkflowManager(
this);
272 #region <Browser Settings>
299 BrowserSettings.UseDirectDownload = trueFalse;
309 BrowserSettings.DownloadTimeoutInSec = seconds;
338 BrowserSettings.BlockPopUps = blockPopUp;
364 BrowserSettings.HiddenDesktop = hiddenDesktop;
368 if (
true == hiddenDesktop)
369 BrowserSettings.HiddenDesktopName = Desktops.NextName();
390 BrowserSettings.HiddenDesktop =
true;
394 BrowserSettings.HiddenDesktopName =
true ==
string.IsNullOrWhiteSpace(desktopName)
395 ? Desktops.NextName()
407 private T LaunchWhereAsked<T>(Func<T> launch)
490 BrowserSettings.BrowserType = browserType;
510 BrowserSettings.DeleteFileBeforeDownload = overwriteFile;
529 BrowserSettings.LoadImages = loadImages;
551 BrowserSettings.ScrollIntoView = trueFalse;
572 BrowserSettings.PromptForDownload = promptForDownload;
582 BrowserSettings.OpenPDFExternally = openPDFExternally;
622 if (
null != inputGrid && 0 < inputGrid.Count())
625 CurrentUOW.RetGrid = inputGrid;
626 CurrentUOW.ColCount = inputGrid.Columns;
629 GPAL.
PublishSimpleEvent(GPALEventType.INFO,
"Input IGPALGrid<T> is empty.",
this, GPALObjectType.Browser);
669 BrowserSettings.UseExistingBrowser =
true;
670 BrowserSettings.ExistingBrowserPort = port;
719 if (
true ==
string.IsNullOrWhiteSpace(restApiUrl))
721 GPAL.
PublishSimpleEvent(GPALEventType.WARNING, $
"No address given to WithRestApiUrl, so a browser will be launched as usual",
this, GPALObjectType.Browser);
727 GPAL.
PublishSimpleEvent(GPALEventType.WARNING, $
"WithRestApiUrl drives a browser over GPALRestAPI, which is the OttoMagic engine. The engine is currently [{BrowserSettings.AutomationEngine}]",
this, GPALObjectType.Browser);
729 BrowserSettings.RestApiBaseUrl = restApiUrl.EndsWith(
"/") ? restApiUrl : restApiUrl +
"/";
730 BrowserSettings.AttachedRestApi =
true;
732 GPAL.
PublishSimpleEvent(GPALEventType.INFO, $
"Driving the browser already running on [{BrowserSettings.RestApiBaseUrl}].",
this, GPALObjectType.Browser);
739 if (port < 1024 || port > 65535)
741 GPAL.
PublishSimpleEvent(GPALEventType.WARNING, $
"Invalid debug port [{port}]. Must be between 1024 and 65535. Using current port [{BrowserSettings.DebugPort}].",
this, GPALObjectType.Browser);
746 while (
true == BrowserHelper.IsPortListening(port))
749 BrowserSettings.DebugPort = port;
751 GPAL.
PublishSimpleEvent(GPALEventType.DEBUG, $
"Puppeteer using port [{port}]",
this, GPALObjectType.Browser);
758 BrowserSettings.ExistingBrowserPort = port;
761 BrowserSettings.DebugPipe =
false;
779 GPAL.
PublishSimpleEvent(GPALEventType.WARNING, $
"Firefox cannot be driven with [{automationEngine}], so this browser runs on [{steppedDownTo}]",
this, GPALObjectType.Browser);
781 automationEngine = steppedDownTo;
797 BrowserSettings.DebugPipe =
true;
798 BrowserSettings.DebugPort =
null;
821 BrowserSettings.WaitOnDocumentReady =
true;
822 BrowserSettings.WaitOnNetworkIdle =
false;
823 BrowserSettings.WaitForDocumentReadyTimeoutMs = timeoutInMs;
845 BrowserSettings.NavigationGraceMs = graceInMs;
871 BrowserSettings.WaitOnNetworkIdle = trueFalse;
872 BrowserSettings.WaitOnDocumentReady = trueFalse ? false :
BrowserSettings.WaitOnDocumentReady;
897 BrowserSettings.NetworkIdleTimeoutMs = networkIdleTimeoutMs;
921 BrowserSettings.NetworkIdleMaxConnections = maxConnections;
947 BrowserSettings.NetworkIdlePruneMs = networkIdlePruneMs;
983 BrowserSettings.ProfileDataDirectory = tmpProfileDirectory;
1007 BrowserSettings.ProfileUserName = profileUserName;
1030 BrowserSettings.ProfileName = profileName;
1042 BrowserSettings.StealthType = steathType;
1053 BrowserSettings.Referrer = referrer;
1064 BrowserSettings.OverrideUserAgent = userAgent;
1080 BrowserSettings.UserAgentFromBrowser = userAgentFromBrowser;
1110 selector.ElementsFoundAndMatchedCount = 0;
1111 selector.DeleteMe =
false;
1112 selector.RemovedMethods.Clear();
1113 if (
true == CurrentUOW.ActionCalled)
1116 var iframeRoot = CurrentUOW.ContextPath;
1117 var shadowRoot = CurrentUOW.ShadowRoot;
1121 ContextPath = iframeRoot,
1122 ShadowRoot = shadowRoot
1125 CurrentUOW.Browser =
this;
1126 selector.Browser =
this;
1129 ResolveInteractionType(selector);
1132 selector.WebSelectorFoundResults =
null;
1133 selector.WebSelectorMatchedResults =
null;
1139 BrowserSettings.CurrentSelector = selector;
1151 if (
true == CurrentUOW.ActionCalled)
1154 var iframeRoot = CurrentUOW.ContextPath;
1155 var shadowRoot = CurrentUOW.ShadowRoot;
1160 ContextPath = iframeRoot,
1161 ShadowRoot = shadowRoot
1166 CurrentUOW.Browser =
this;
1167 persistentUOW.Browser =
this;
1171 tmpSel.selectorSettings.SelectorType = SelectorType.Data;
1172 tmpSel.selectorSettings.Name =
"Literal" + CurrentUOW.WithSelectorList.Select(s => SelectorType.Data == s.SelectorSettings.SelectorType).Count() + 1;
1173 CurrentUOW.WithSelectorList.Add(tmpSel);
1184 tmpSel.selectorSettings.SelectorType = SelectorType.DataFunc;
1185 tmpSel.selectorSettings.Name = dataFunction.Method.Name;
1186 tmpSel.selectorSettings.DataFunction = dataFunction;
1187 CurrentUOW.WithSelectorList.Add(tmpSel);
1210 if (
true ==
string.IsNullOrEmpty(origin))
1211 GPAL.
PublishSimpleEvent(GPALEventType.WARNING, $
"No page loaded, so there is no session to carry. Navigate first, then continue as a RESTClient",
this, GPALObjectType.Browser);
1217 cookieUrl.
Add(
new StorageAction { StorageType = WebsiteStorageType.cookie, Action = WebsiteStorageAction.get });
1220 new StorageAction { StorageType = WebsiteStorageType.cookie, Action = WebsiteStorageAction.get },
1221 out
string cookieJson);
1223 string cookieHeader = CookieHeaderFrom(cookieJson, origin, out
int cookiesRead);
1225 if (
false ==
string.IsNullOrEmpty(cookieHeader))
1231 string sentAcceptLanguage =
BrowserHelper.LiveAcceptLanguage(
this);
1235 if (
false ==
string.IsNullOrEmpty(sentAcceptLanguage))
1236 retVal.
WithHeader(
"Accept-Language", sentAcceptLanguage);
1243 if (
null != browserCredential)
1248 if (
false ==
string.IsNullOrEmpty(authValue))
1253 GPAL.
PublishSimpleEvent(GPALEventType.INFO, $
"Continuing as a RESTClient from [{BrowserSettings.BrowserType}], carrying its [{(null == sentUserAgent ? "configured
" : "live
")}] user agent, [{(string.IsNullOrEmpty(sentAcceptLanguage) ? "no accept language
" : sentAcceptLanguage)}], [{cookieHeader?.Split(';').Length ?? 0}] of [{cookiesRead}] cookies for [{origin}] and [{(null == browserCredential ? "no credential
" : ((Credentials)browserCredential).WebAuthType.ToString())}]",
this, GPALObjectType.Browser);
1268 private static string CookieHeaderFrom(
string cookieJson,
string origin, out
int cookiesRead)
1270 string retVal =
null;
1276 if (
false ==
string.IsNullOrEmpty(cookieJson))
1278 string host =
true == Uri.TryCreate(origin, UriKind.Absolute, out Uri originUri) ? originUri.Host :
null;
1279 Newtonsoft.Json.Linq.JArray cookies = Newtonsoft.Json.Linq.JArray.Parse(cookieJson);
1280 List<string> pairs =
new List<string>();
1282 cookiesRead = cookies.Count;
1284 foreach (Newtonsoft.Json.Linq.JToken cookie in cookies)
1287 string name = (string)(cookie[
"name"] ?? cookie[
"Name"] ?? cookie[
"Key"]);
1288 string value = (string)(cookie[
"value"] ?? cookie[
"Value"]);
1289 string domain = (string)(cookie[
"domain"] ?? cookie[
"Domain"]);
1291 if (
false ==
string.IsNullOrEmpty(name) &&
true == UrlHelper.CookieAppliesToHost(domain, host))
1292 pairs.Add($
"{name}={value}");
1295 if (0 < pairs.Count)
1296 retVal =
string.Join(
"; ", pairs);
1299 catch (GPALException)
1303 catch (Exception ex)
1305 GPAL.PublishSimpleEvent(GPALEventType.WARNING, $
"Could not read the cookies into a header",
null, GPALObjectType.Browser, ex);
1320 BrowserSettings.StorageActionCalled =
true;
1321 BrowserSettings.StorageActionToRun.Action = WebsiteStorageAction.get;
1322 BrowserSettings.StorageActionToRun.StorageType = storageType;
1334 BrowserSettings.StorageActionCalled =
true;
1335 BrowserSettings.StorageActionToRun.Action = WebsiteStorageAction.set;
1336 BrowserSettings.StorageActionToRun.StorageType = storageType;
1348 BrowserSettings.StorageActionCalled =
true;
1349 BrowserSettings.StorageActionToRun.Action = WebsiteStorageAction.delete;
1350 BrowserSettings.StorageActionToRun.StorageType = storageType;
1366 BrowserSettings.StorageActionCalled =
false;
1369 BrowserSettings.StorageActionToRun.Domain = domain;
1385 BrowserSettings.StorageActionCalled =
false;
1388 BrowserSettings.StorageActionToRun.Path = path;
1404 BrowserSettings.StorageActionCalled =
false;
1407 BrowserSettings.StorageActionToRun.Key = key;
1423 BrowserSettings.StorageActionCalled =
false;
1426 BrowserSettings.StorageActionToRun.StoreName = storeName;
1442 BrowserSettings.StorageActionCalled =
false;
1445 BrowserSettings.StorageActionToRun.Data = data;
1461 BrowserSettings.StorageActionCalled =
false;
1464 BrowserSettings.StorageActionToRun.UserDefined = userDefined;
1491 selector.ElementsFoundAndMatchedCount = 0;
1492 selector.WebSelectorFoundResults =
null;
1493 selector.WebSelectorMatchedResults =
null;
1496 if (CurrentUOW.ActionCalled)
1498 var previousShadowRoot = CurrentUOW.ShadowRoot;
1499 var previousContextPath = CurrentUOW.ContextPath;
1504 ShadowRoot = previousShadowRoot,
1505 ContextPath = previousContextPath
1508 persistentUOW =
new UnitOfWork { Browser =
this };
1511 BrowserSettings.CurrentSelector = selector;
1512 CurrentUOW.InSelectorList.Add(selector);
1538 stepsToProcess = CurrentUOW.ContextPath;
1545 if (ElementType.IFrame == contextType.elementType)
1550 BrowserDriver.SwitchTo().Frame((IWebElement)contextType.gPalElement.WebElement);
1556 else if (ElementType.ShadowRoot == contextType.elementType)
1559 MagicHelper.SwitchToShadowRoot(contextType.gPalElement.Css);
1561 PuppeteerClient.SwitchToShadowRoot(contextType.gPalElement.Css).Execute();
1565 inElement = contextType.gPalElement;
1567 else if (ElementType.Element == contextType.elementType)
1569 inElement = contextType.gPalElement;
1574 ElementHelper.FindWebElements(
1575 this, CurrentUOW, selector, out _, out var tempElems, inElement);
1577 if (tempElems?.Count > 0)
1580 selector, tempElems[0], ElementType.Element);
1583 CurrentUOW.ContextPath.Add(elementEntry);
1588 GPAL.
PublishSimpleEvent(GPALEventType.WARNING, $
"InElement selector returned no results [{selector.Name}]",
this);
1621 selector.ElementsFoundAndMatchedCount = 0;
1622 selector.WebSelectorFoundResults =
null;
1623 selector.WebSelectorMatchedResults =
null;
1626 if (CurrentUOW.ActionCalled)
1628 var previousShadowRoot = CurrentUOW.ShadowRoot;
1629 var previousContextPath = CurrentUOW.ContextPath;
1634 ShadowRoot = previousShadowRoot,
1635 ContextPath = previousContextPath
1638 persistentUOW =
new UnitOfWork { Browser =
this };
1641 BrowserSettings.CurrentSelector = selector;
1642 CurrentUOW.InSelectorList.Add(selector);
1669 stepsToProcess = CurrentUOW.ContextPath;
1675 if (ElementType.IFrame == contextType.elementType)
1678 MagicHelper.SwitchToElement(contextType.gPalElement.Css);
1682 BrowserDriver.SwitchTo().Frame((IWebElement)contextType.gPalElement.WebElement);
1688 else if (ElementType.ShadowRoot == contextType.elementType)
1691 MagicHelper.SwitchToShadowRoot(contextType.gPalElement.Css);
1693 PuppeteerClient.SwitchToShadowRoot(contextType.gPalElement.Css).Execute();
1697 inElement = contextType.gPalElement;
1702 ElementHelper.FindWebElements(
1703 this, CurrentUOW, selector, out _, out var tempElems, CurrentUOW.ShadowRoot);
1705 if (tempElems?.Count > 0)
1708 selector, tempElems[0], ElementType.IFrame);
1711 CurrentUOW.ContextPath.Add(iframeElement);
1749 shadowDomSelector.ElementsFoundAndMatchedCount = 0;
1750 shadowDomSelector.WebSelectorFoundResults =
null;
1751 shadowDomSelector.WebSelectorMatchedResults =
null;
1753 if (CurrentUOW.ActionCalled)
1755 var previousShadowRoot = CurrentUOW.ShadowRoot;
1756 var previousContextPath = CurrentUOW.ContextPath;
1761 ShadowRoot = previousShadowRoot,
1762 ContextPath = previousContextPath
1765 persistentUOW =
new UnitOfWork { Browser =
this };
1768 BrowserSettings.CurrentSelector = shadowDomSelector;
1769 CurrentUOW.InSelectorList.Add(shadowDomSelector);
1783 if (ElementType.IFrame == contextType.elementType)
1790 BrowserDriver.SwitchTo().Frame((IWebElement)contextType.gPalElement.WebElement);
1796 else if (ElementType.ShadowRoot == contextType.elementType)
1799 MagicHelper.SwitchToShadowRoot(contextType.gPalElement.Css);
1801 PuppeteerClient.SwitchToShadowRoot(contextType.gPalElement.Css).Execute();
1805 inElement = contextType.gPalElement;
1809 ElementHelper.FindWebElements(
1810 this, CurrentUOW, shadowDomSelector, out _, out var tempElems, inElement);
1812 if (tempElems?.Count > 0)
1815 shadowDomSelector, tempElems[0], ElementType.ShadowRoot);
1817 CurrentUOW.ShadowRoot = tempElems[0];
1818 CurrentUOW.ShadowRoot.IsShadowRoot =
true;
1821 CurrentUOW.ContextPath.Add(shadowElement);
1836 ISearchContext shadowRoot =
null;
1845 shadowRoot = ((IWebElement)tempElems[0].WebElement).GetShadowRoot();
1852 if (
null != shadowRoot)
1853 CurrentUOW.ShadowRoot.WebElement = shadowRoot;
1856 CurrentUOW.ShadowRoot.IsClosedShadowRoot =
true;
1861 GPAL.
PublishSimpleEvent(GPALEventType.WARNING, $
"Closed shadow root for selector [{shadowDomSelector.Name}]. Will return host element for searches inside.",
this, GPALObjectType.Browser);
1875 CurrentUOW.ShadowRoot =
null;
1876 CurrentUOW.ContextPath?.Clear();
1901 CurrentUOW.NextPageButton = nextPageButtonselector;
1902 BrowserSettings.CurrentSelector = nextPageButtonselector;
1913 CurrentUOW.PageCount = pageCount;
1925 CurrentUOW.FetchTokens = inputGrid;
1932 List<IGPALGrid<string>> tokenList =
FileHelper.TokenizeFile(CurrentUOW, inputFile);
1934 CurrentUOW.FetchTokens = 0 == tokenList?.Count ? null : tokenList[0];
1941 DatabaseHelper.TokenizeDatabase(CurrentUOW, inputDatabase);
1943 CurrentUOW.FetchTokens = CurrentUOW.InputDatabase?.Tokens;
1950 IGPALGrid<string> grid =
GPAL.GridForType<
string>();
1952 grid.AddRow(
new List<string>(tokens?.Split(
',') ??
new string[0]));
1953 CurrentUOW.FetchTokens = grid;
1965 CurrentUOW.CallAfterFillIn = callAfterFillIn;
1981 CurrentUOW.CallIfFound.Add(callIfFoundDelegate);
1997 CurrentUOW.CallIfNotFound.Add(callIfNotFound);
2008 BrowserSettings.WaitForWindowTimeoutInSeconds = waitTimeInSeconds;
2022 persistentUOW.CallIfFound.Add(persistentCallIfFound);
2037 persistentUOW.CallIfNotFound.Add(persistentCallIfNotFound);
2065 BrowserSettings.Credentials = credentials;
2084 internal CallIfStatus RaiseOnFail(GPALFailure failure,
string detail)
2086 CallIfStatus handled = CallIfStatus.NotHandled;
2087 List<CallOnFailDelegate> handlers = 0 < BrowserSettings.CallOnFail.Count ? BrowserSettings.CallOnFail :
GPAL.GPALSettings.CallOnFailHandlers;
2091 GPAL.
PublishSimpleEvent(GPALEventType.INFO, $
"Invoking CallOnFail [{handler.Method.Name}] for [{failure}]",
this, GPALObjectType.Browser);
2093 handled = handler(
this, failure, detail);
2095 if (CallIfStatus.Terminate == handled)
2097 string str = $
"CallOnFail handler [{handler.Method.Name}] requested program termination.";
2102 if (CallIfStatus.Handled == handled)
2121 if (
true == CurrentUOW.ActionCalled)
2124 var iframeRoot = CurrentUOW.ContextPath;
2125 var shadowRoot = CurrentUOW.ShadowRoot;
2129 ContextPath = iframeRoot,
2130 ShadowRoot = shadowRoot,
2134 persistentUOW =
new UnitOfWork() { Browser =
this };
2137 selector.Browser =
this;
2138 selector.DeleteMe =
false;
2139 selector.RemovedMethods.Clear();
2141 ResolveInteractionType(selector);
2144 selector.WebSelectorFoundResults =
null;
2145 selector.WebSelectorMatchedResults =
null;
2147 persistentUOW.WithSelectorList.Add(selector);
2170 if (
true == CurrentUOW.GetGridCalled)
2172 CurrentUOW.GetGridCalled =
false;
2173 CurrentUOW.HeaderList.Clear();
2175 CurrentUOW.HeaderList.Add(header);
2191 if (0 == CurrentUOW.WithSelectorList.Count ||
true == CurrentUOW.ActionCalled)
2192 BrowserSettings.WithAllThatMatch = rowCount;
2194 CurrentUOW.WithAllThatMatch = rowCount;
2206 int screenWidth =
Puppeteer.GetScreenWidthInt();
2207 int screenHeight =
Puppeteer.GetScreenHeightInt();
2209 if (100 > windowSize.Width || 100 > windowSize.Height)
2211 int widthToUse = Math.Min(windowSize.Width, 100);
2212 int heightToUse = Math.Min(windowSize.Height, 100);
2214 GPAL.
PublishSimpleEvent(GPALEventType.WARNING, $
"Requested browser sizes [{windowSize.Width}x{windowSize.Height}] cannot be less than 100. Using [{widthToUse}x{heightToUse}].",
this, GPALObjectType.Browser);
2216 windowSize.Width = widthToUse;
2217 windowSize.Height = heightToUse;
2219 else if (screenWidth < windowSize.Width || screenHeight < windowSize.Height)
2221 int widthToUse = Math.Min(windowSize.Width, screenWidth);
2222 int heightToUse = Math.Min(windowSize.Height, screenHeight);
2224 GPAL.
PublishSimpleEvent(GPALEventType.WARNING, $
"Requested browser size [{windowSize.Width}x{windowSize.Height}] exceeds screen size [{screenWidth}x{screenHeight}]. Using [{widthToUse}x{heightToUse}].",
this, GPALObjectType.Browser);
2226 windowSize.Width = widthToUse;
2227 windowSize.Height = heightToUse;
2231 BrowserSettings.WindowSize = windowSize;
2272 CurrentUOW.InfiniteScroll =
true;
2276 #region ACTION GETTERS
2285 InAction(
false,
false);
2306 InAction(
false,
false);
2327 InAction(
false,
false);
2351 InAction(
false,
false);
2370 InAction(
false,
false);
2377 ScrollToPosition(
"end");
2389 InAction(
false,
false);
2396 ScrollToPosition(
"top");
2408 InAction(
false,
false);
2427 BrowserSettings.FullScreen =
false;
2428 BrowserSettings.Maximize =
false;
2429 BrowserSettings.Minimize =
false;
2462 BrowserSettings.Minimize =
false;
2463 BrowserSettings.Maximize =
false;
2464 BrowserSettings.FullScreen =
true;
2471 BrowserSettings.SavedWindowSize =
new Size(
MagicHelper.WindowOuterWidth(),
MagicHelper.WindowOuterHeight());
2476 BrowserSettings.SavedWindowSize =
new Size(
PuppeteerClient.WindowOuterWidth().Execute<
int>(),
PuppeteerClient.WindowOuterHeight().Execute<
int>());
2481 BrowserSettings.SavedWindowSize =
BrowserSettings.BrowserDriver.Manage().Window.Size;
2495 BrowserSettings.Maximize =
true;
2496 BrowserSettings.Minimize =
false;
2497 BrowserSettings.FullScreen =
false;
2505 BrowserSettings.SavedWindowSize =
new Size(
MagicHelper.WindowOuterWidth(),
MagicHelper.WindowOuterHeight());
2511 BrowserSettings.SavedWindowSize =
new Size(
PuppeteerClient.WindowOuterWidth().Execute<
int>(),
PuppeteerClient.WindowOuterHeight().Execute<
int>());
2517 BrowserSettings.SavedWindowSize =
BrowserSettings.BrowserDriver.Manage().Window.Size;
2531 BrowserSettings.Minimize =
true;
2532 BrowserSettings.Maximize =
false;
2533 BrowserSettings.FullScreen =
false;
2540 BrowserSettings.SavedWindowSize =
new Size(
MagicHelper.WindowOuterWidth(),
MagicHelper.WindowOuterHeight());
2545 BrowserSettings.SavedWindowSize =
new Size(
PuppeteerClient.WindowOuterWidth().Execute<
int>(),
PuppeteerClient.WindowOuterHeight().Execute<
int>());
2551 BrowserSettings.SavedWindowSize =
BrowserSettings.BrowserDriver.Manage().Window.Size;
2558 #endregion ACTION GETTERS
2559 #endregion <Browser Settings>
2560 #region <Browser Actions>
2568 BrowserSettings.ObeyRobotsTxt = trueFalse;
2578 BrowserSettings.RespectRobotMetaTags = trueFalse;
2595 BrowserSettings.UseHeadless =
true;
2598 BrowserSettings.OpenPDFExternally =
false;
2599 BrowserSettings.PromptForDownload =
false;
2601 BrowserSettings.OpenInTab =
false;
2608 catch (Exception ex)
2610 GPAL.
PublishSimpleEvent(GPALEventType.EXCEPTION, $
"Failed to GET from [{URL}]",
this, GPALObjectType.Browser, ex);
2625 bool firstRunSave = firstRun;
2635 GPAL.
PublishSimpleEvent(GPALEventType.WARNING, $
"Firefox cannot be driven with [{BrowserSettings.AutomationEngine}], so this browser runs on [{steppedDownTo}]",
this, GPALObjectType.Browser);
2644 GPAL.
PublishSimpleEvent(GPALEventType.ERROR, $
"Visiting [{URL?.Url}] is disallowed by robots.txt and your request to honor it. Not going to URL. Workflow will fail.",
this, GPALObjectType.Browser);
2645 URL.
ForUrl(
"https://google.com");
2653 string priorHandle =
null;
2654 int tabsBefore = -1;
2656 BrowserSettings.CurrentURL = URL?.
Url;
2657 BrowserSettings.CurrentGPALUrl = URL;
2660 CurrentUOW =
new UnitOfWork() { Browser =
this };
2663 if (
true == InAction(
false,
false,
null))
2668 ApplyNetworkMonitor();
2670 CheckAndApplyStealth();
2692 UpdateActiveTab(tabTuple);
2708 if (
null == BrowserLaunched)
2710 BrowserLaunched = DateTime.Now;
2717 tabsBefore = RealTabCount();
2718 priorHandle =
true ==
UseSelenium ? BrowserDriver.CurrentWindowHandle :
null;
2723 if (
true ==
string.IsNullOrEmpty(tabTuple?.Url))
2736 RecordTabWindow(
BrowserDriver.CurrentWindowHandle, priorHandle);
2751 RaiseOnFail(GPALFailure.Navigation, $
"[{URL?.Url}] did not load.");
2753 RaiseOnFail(GPALFailure.Navigation, $
"[{URL?.Url}] did not load. [{PuppeteerCommunicator.LastNavigationError}]");
2758 if (-1 != tabsBefore && RealTabCount() == tabsBefore)
2759 BackOutOfSelfClosedTab(URL, priorUrl, priorGPALUrl, priorTab, priorHandle);
2765 if (
true ==
BrowserSettings.RespectRobotMetaTags &&
false == UrlHelper.CheckRobotMetaTags(URL,
this))
2767 GPAL.
PublishSimpleEvent(GPALEventType.WARNING, $
"Visiting [{BrowserSettings.CurrentURL}] is disallowed by robot meta tags and your request to honor them..");
2768 URL.
ForUrl(
"https://google.com");
2774 UpdateActiveTab(tabTuple);
2789 BrowserSettings.CurrentURL = URL.
Url;
2793 if (
true == navigationFailed)
2794 GPAL.
PublishSimpleEvent(GPALEventType.WARNING, $
"[{BrowserSettings.CurrentURL}] did not load. Continuing against whatever the browser is showing.");
2810 catch (Exception ex)
2812 GPAL.
PublishSimpleEvent(GPALEventType.EXCEPTION, $
"Failed to navigate to [{URL?.Url}]",
this, GPALObjectType.Browser, ex);
2821 BrowserSettings.OpenInTab =
false;
2824 throw new GPALException(
"Driver initialization error. Please see mesages.");
2832 private void ApplyGoogleReferrer()
2839 ((OpenQA.Selenium.Chromium.ChromiumDriver)
BrowserDriver).ExecuteCdpCommand(
"Network.setExtraHTTPHeaders",
new Dictionary<string, object>
2842 "headers",
new Dictionary<string, object>
2844 {
"Referer",
"https://www.google.com/" }
2849 GPAL.PublishSimpleEvent(GPALEventType.INFO,
"Stealth Google Referrer applied.",
this, GPALObjectType.Browser);
2855 private void ApplyNetworkMonitor()
2857 string networkMonitorScript =
@"
2859 if (!window.__gpalNetworkMonitor) {
2860 window.__gpalNetworkMonitor = { inflight: 0 };
2862 const origOpen = XMLHttpRequest.prototype.open;
2863 XMLHttpRequest.prototype.open = function() {
2864 this.addEventListener('loadstart', () => { window.__gpalNetworkMonitor.inflight++; });
2865 this.addEventListener('loadend', () => { window.__gpalNetworkMonitor.inflight--; });
2866 origOpen.apply(this, arguments);
2869 const origFetch = window.fetch;
2870 window.fetch = function(...args) {
2871 window.__gpalNetworkMonitor.inflight++;
2872 return origFetch(...args).finally(() => { window.__gpalNetworkMonitor.inflight--; });
2881 .GetAwaiter().GetResult();
2887 ((OpenQA.Selenium.IJavaScriptExecutor)
BrowserDriver).ExecuteScript(networkMonitorScript);
2891 ((OpenQA.Selenium.Chromium.ChromiumDriver)
BrowserDriver).ExecuteCdpCommand(
2892 "Page.addScriptToEvaluateOnNewDocument",
2893 new Dictionary<string, object>
2895 {
"source", networkMonitorScript }
2900 GPAL.PublishSimpleEvent(GPALEventType.INFO,
"Network idle monitor script injected.",
this, GPALObjectType.Browser);
2906 private void ApplyCDPProtection()
2912 string antiAntiBotScript =
@"
2914 var originalError = Error;
2915 Object.defineProperty(Error.prototype, 'stack', {
2916 configurable: false,
2919 value: (function() {
2921 throw new originalError();
2928 window.Error = new Proxy(originalError, {
2929 construct(target, args) {
2930 var instance = new target(...args);
2931 return Object.freeze(instance);
2943 ((OpenQA.Selenium.Chromium.ChromiumDriver)
BrowserDriver).ExecuteCdpCommand(
"Page.addScriptToEvaluateOnNewDocument",
new Dictionary<string, object>
2945 {
"source", antiAntiBotScript }
2949 string antiAntiBotScript2 =
"Object.defineProperty(navigator, 'webdriver', { get: () => undefined })";
2952 ((OpenQA.Selenium.Chromium.ChromiumDriver)
BrowserDriver).ExecuteCdpCommand(
"Page.addScriptToEvaluateOnNewDocument",
new Dictionary<string, object>
2954 {
"source", antiAntiBotScript2 }
2959 PuppeteerCommunicator.EnableFetchScriptInjection(antiAntiBotScript2,
null).GetAwaiter().GetResult();
2962 GPAL.PublishSimpleEvent(GPALEventType.INFO,
"Stealth CDP protection applied.",
this, GPALObjectType.Browser);
2970 private void ApplyToStringOverride()
2972 string toStringOverrideScript =
@"
2974 // Store the original toString
2975 const originalToString = Object.prototype.toString;
2977 // Create a proxy handler to intercept toString calls
2979 apply: function(target, thisArg, argumentsList) {
2980 if (thisArg instanceof Function && /antiAntiBotScript|CDP/.test(originalToString.call(thisArg))) {
2981 return 'function () { [native code] }';
2983 return target.apply(thisArg, argumentsList);
2987 // Wrap toString with a proxy
2988 Object.prototype.toString = new Proxy(originalToString, handler);
2990 console.log('toString override applied via Proxy');
2996 ((OpenQA.Selenium.Chromium.ChromiumDriver)
BrowserDriver).ExecuteCdpCommand(
"Page.addScriptToEvaluateOnNewDocument",
new Dictionary<string, object>
2998 {
"source", toStringOverrideScript }
3003 PuppeteerCommunicator.EnableFetchScriptInjection(toStringOverrideScript,
null).GetAwaiter().GetResult();
3006 GPAL.PublishSimpleEvent(GPALEventType.INFO,
"Stealth toString override applied.",
this, GPALObjectType.Browser);
3012 private void CheckAndApplyStealth()
3024 ApplyCDPProtection();
3026 if (
BrowserSettings.StealthType.HasFlag(StealthType.ToStringOverride))
3027 ApplyToStringOverride();
3035 ApplyGoogleReferrer();
3037 catch (GPALException)
3041 catch (Exception ex)
3043 GPAL.PublishSimpleEvent(GPALEventType.EXCEPTION, $
"Google Referrer stealth skipped",
this, GPALObjectType.Browser, ex);
3047 BrowserSettings.StealthApplied =
true;
3059 if (URLorTabId is
string s)
3061 else if (URLorTabId is
GPALUrl url)
3063 else if (URLorTabId is
int i)
3072 int currentTabId = GetActiveTabId();
3075 if (
true ==
string.IsNullOrEmpty(URLorTabId))
3080 else if (URLorTabId is
int tabId)
3085 else if (URLorTabId is
string url)
3091 UpdateActiveTab(tabTuple);
3095 if (
true ==
string.IsNullOrWhiteSpace(URLorTabId))
3097 ResolveGhostTabHandle();
3101 string closing =
null;
3102 string landOn =
null;
3110 int closingIdx = before.IndexOf(closing);
3111 for (
int idx = closingIdx - 1; 0 <= idx &&
null == landOn; idx--)
3112 if (before[idx] != GhostTabHandle)
3113 landOn = before[idx];
3116 landOn = before.FirstOrDefault(h => h != closing && h != GhostTabHandle);
3118 catch (Exception exRead)
3120 GPAL.
PublishSimpleEvent(GPALEventType.WARNING, $
"CloseTab: could not read the tab list before closing, will fall back to whatever the driver leaves us on.",
this, GPALObjectType.Browser, exRead);
3126 catch (Exception exClose) {
GPAL.
PublishSimpleEvent(GPALEventType.WARNING, $
"CloseTab: driver failed to close the current tab, continuing.",
this, GPALObjectType.Browser, exClose); }
3129 if (
false ==
string.IsNullOrEmpty(landOn))
3136 catch (Exception exSwitch)
3138 GPAL.
PublishSimpleEvent(GPALEventType.WARNING, $
"CloseTab: could not switch to tab [{landOn}] after closing.",
this, GPALObjectType.Browser, exSwitch);
3151 if (
true ==
string.IsNullOrEmpty(handle))
3152 GPAL.
PublishSimpleEvent(GPALEventType.WARNING, $
"CloseTab: no tab is on [{URLorTabId}], nothing closed. Call .CloseTab() with no url to close the current tab.",
this, GPALObjectType.Browser);
3159 catch (Exception exClose) {
GPAL.
PublishSimpleEvent(GPALEventType.WARNING, $
"CloseTab: driver failed to close tab [{URLorTabId}], continuing.",
this, GPALObjectType.Browser, exClose); }
3164 string landOn =
true == after.Contains(staying) ? staying : after.LastOrDefault(h => h != GhostTabHandle);
3166 if (
false ==
string.IsNullOrEmpty(landOn))
3183 tabsLeft = RealTabCount();
3188 catch (Exception ex)
3190 GPAL.
PublishSimpleEvent(GPALEventType.EXCEPTION, $
"Browser stopped answering after closing the tab.",
this, GPALObjectType.Browser, ex);
3194 GPAL.
PublishSimpleEvent(GPALEventType.INFO, $
"Closed the last tab, the browser closed with it.",
this, GPALObjectType.Browser);
3212 private List<string> TabsInThisWindow(IList<string> handles,
string current)
3214 List<string> siblings =
new List<string>();
3218 if (
false == tabWindows.ContainsKey(current))
3219 tabWindows[current] = current;
3221 string thisWindow = tabWindows[current];
3223 foreach (
string handle
in handles)
3225 if (
false == tabWindows.ContainsKey(handle))
3226 tabWindows[handle] = thisWindow;
3228 if (tabWindows[handle] == thisWindow && handle != GhostTabHandle)
3229 siblings.Add(handle);
3240 private void RecordTabWindow(
string newHandle,
string openedFrom)
3242 if (
true ==
string.IsNullOrEmpty(newHandle))
3245 if (
false ==
string.IsNullOrEmpty(openedFrom) &&
true == tabWindows.ContainsKey(openedFrom))
3246 tabWindows[newHandle] = tabWindows[openedFrom];
3248 tabWindows[newHandle] =
string.IsNullOrEmpty(openedFrom) ? newHandle : openedFrom;
3257 internal string ResolveGhostTabHandle()
3261 return GhostTabHandle;
3264 if (
false ==
string.IsNullOrEmpty(GhostTabHandle))
3265 return GhostTabHandle;
3270 if (handles.Count <= tabsWeOpened)
3275 catch {
return null; }
3277 foreach (
string handle
in handles)
3279 if (handle == current)
continue;
3281 string handleUrl =
null;
3288 finally {
try {
BrowserDriver.SwitchTo().Window(current); }
catch { } }
3294 if (
false ==
string.IsNullOrEmpty(handleUrl) &&
true == UrlHelper.SameBaseUrl(handleUrl, GhostTabUrl))
3296 GhostTabHandle = handle;
3297 GPAL.PublishSimpleEvent(GPALEventType.DEBUG, $
"Ghost tab found, will always skip [{handleUrl}]",
this, GPALObjectType.Browser);
3302 return GhostTabHandle;
3336 UpdateActiveTab(tabTuple);
3344 ResolveGhostTabHandle();
3352 List<string> siblings = TabsInThisWindow(handles, current);
3354 int idx = siblings.IndexOf(current);
3355 if (0 > idx) idx = 0;
3357 idx = (idx + 1) % siblings.Count;
3394 BrowserSettings.OpenInTab =
true;
3436 ResolveGhostTabHandle();
3442 List<string> siblings = TabsInThisWindow(handles, current);
3444 int idx = siblings.IndexOf(current);
3445 if (0 > idx) idx = 0;
3448 idx = (idx - 1 + siblings.Count) % siblings.Count;
3488 if (
true == InAction(WaitTime.Never < CurrentUOW.WaitForInMs))
3489 ElementHelper.LeftClick(
this, modifierKeys);
3493 ResolveGhostTabHandle();
3504 foreach (
string handle
in handles.Where(h =>
false == handlesBefore.Contains(h) && h != GhostTabHandle))
3506 RecordTabWindow(handle, currentHandle);
3510 if (0 < opened) tabsWeOpened += opened;
3520 string currentUrl = GetSetCurrentUrl();
3524 int tabId = GetActiveTabId();
3526 UpdateActiveTab(tabTuple);
3544 if (
null != selector)
3558 if (
true == InAction(WaitTime.Never < CurrentUOW.WaitForInMs))
3562 ElementHelper.GenericClick(
this, selectClickType);
3579 if (
null != selector)
3583 if (
true == InAction(WaitTime.Never < CurrentUOW.WaitForInMs))
3587 ElementHelper.LeftClick(
this);
3590 ((OpenQA.Selenium.Chromium.ChromiumDriver)
BrowserDriver).ExecuteCdpCommand(
"Runtime.disable",
new Dictionary<string, object> { });
3591 ElementHelper.LeftClick(
this);
3592 ((OpenQA.Selenium.Chromium.ChromiumDriver)
BrowserDriver).ExecuteCdpCommand(
"Runtime.enable",
new Dictionary<string, object> { });
3595 ElementHelper.LeftClick(
this);
3611 if (
true == InAction(WaitTime.Never < CurrentUOW.WaitForInMs))
3613 ElementHelper.GenericClick(
this, ClickType.LeftDoubleClick);
3633 if (
true == InAction(WaitTime.Never < CurrentUOW.WaitForInMs))
3634 ElementHelper.MiddleClick(
this);
3636 string currentUrl = GetSetCurrentUrl();
3640 ResolveGhostTabHandle();
3651 foreach (
string handle
in handles.Where(h =>
false == handlesBefore.Contains(h) && h != GhostTabHandle))
3653 RecordTabWindow(handle, currentHandle);
3657 if (0 < opened) tabsWeOpened += opened;
3660 if (
false == UrlHelper.AreEquivalent(priorUrl, currentUrl))
3664 int tabId = GetActiveTabId();
3666 UpdateActiveTab(tabTuple);
3684 if (
null != selector)
3688 selector.InteractionType = InteractionType.Selenium;
3692 if (
true == InAction(WaitTime.Never < CurrentUOW.WaitForInMs))
3694 ElementHelper.GenericClick(
this, ClickType.RightClick);
3712 bool useEAvars =
false;
3725 EAelement = webElement;
3726 EAselector = selector;
3728 LeftClickAndDownload(gPalFile);
3732 internal const int UnexpectedSaveAsWaitSeconds = 2;
3733 internal const int ProfileRestoreWaitMs = 5000;
3743 string fullFilePathToSave;
3744 HWND windowOpened = IntPtr.Zero;
3745 List<string> returnFilename =
new List<string>();
3746 bool noSaveAsDialog =
false;
3750 if (
true == useEAvars)
3752 DownloadWorkflow(EAelement, EAselector);
3760 returnFilename.Add(fullFilePathToSave);
3762 string currentUrl =
null, currentUrl2 =
null;
3768 GPAL.
PublishSimpleEvent(GPALEventType.INFO, $
"Downloading to [{fullFilePathToSave}]",
this, GPALObjectType.Browser);
3772 FileHelper.DownloadToFile(webElement, fullFilePathToSave);
3785 GPAL.
PublishSimpleEvent(GPALEventType.INFO, $
"Element [{webElement.TagName}] for selector [{selector.Name}] [{selector.InteractionType.ToString()}][Left] clicked with modifiers [NONE].");
3786 PuppeteerClient.LeftClickAndDownload(webElement.ElementHandle).DownloadTo(fileToSave).Execute();
3793 GPAL.
PublishSimpleEvent(GPALEventType.INFO, $
"Element [{webElement.TagName}] for selector [{selector.Name}] [{selector.InteractionType.ToString()}][Left] clicked with modifiers [NONE].");
3799 ElementHelper.Click(
this, selector, webElement, ClickType.LeftClick, ModifierKeys.NONE);
3809 if (
true == UrlHelper.AreEquivalent(currentUrl, urlAfterClick))
3810 CheckPersistentSelectors();
3812 if (
false == UrlHelper.AreEquivalent(currentUrl, urlAfterClick)
3813 &&
true == fileToSave.
Filename.EndsWith(
".pdf")
3827 HWND unexpectedDialog = IntPtr.Zero;
3830 unexpectedDialog = FormHelper.WaitForWindow(
"Save As", UnexpectedSaveAsWaitSeconds);
3833 unexpectedDialog = FormHelper.WaitForWindow(
"Enter name of file to save to…", UnexpectedSaveAsWaitSeconds);
3835 if (IntPtr.Zero != unexpectedDialog)
3837 GPAL.
PublishSimpleEvent(GPALEventType.WARNING, $
"[{BrowserSettings.BrowserType}] asked where to save the file even though prompting was turned off. Answering the dialog with [{newFilename}].",
this, GPALObjectType.Browser);
3839 HardwareHelper.SendString(newFilename);
3851 Stopwatch sinceProgress = Stopwatch.StartNew();
3852 long lastSeen =
long.MinValue;
3860 long progress =
FileHelper.DownloadProgress(watchedDirectory, fullFilePathToSave);
3862 if (progress != lastSeen)
3864 lastSeen = progress;
3865 sinceProgress.Restart();
3879 GPAL.
PublishSimpleEvent(GPALEventType.ERROR, $
"Testing [{currentUrl2}] for PDF file",
this, GPALObjectType.Browser);
3889 GPAL.
PublishSimpleEvent(GPALEventType.ERROR, $
"File not downloaded to [{fullFilePathToSave}].",
this, GPALObjectType.Browser);
3895 ElementHelper.Click(
this, selector, webElement, ClickType.LeftClick, ModifierKeys.NONE);
3903 if (
true == UrlHelper.AreEquivalent(currentUrl, urlAfterClick))
3904 CheckPersistentSelectors();
3910 if (
false == UrlHelper.AreEquivalent(currentUrl, urlAfterClick)
3911 &&
true == fileToSave.
Filename.EndsWith(
".pdf")
3920 windowOpened = FormHelper.WaitForWindow(
"Save As",
BrowserSettings.WaitForWindowTimeoutInSeconds);
3923 windowOpened = FormHelper.WaitForWindow(
"Enter name of file to save to…",
BrowserSettings.WaitForWindowTimeoutInSeconds);
3929 noSaveAsDialog =
false;
3931 else if (IntPtr.Zero != windowOpened)
3933 HardwareHelper.SendString(newFilename);
3938 windowOpened = FormHelper.WaitForWindow(
"Save As", 1);
3940 windowOpened = FormHelper.WaitForWindow(
"Enter name of file to save to…", 1);
3942 if (IntPtr.Zero != windowOpened)
3945 string actualDownloadedFilename =
null;
3949 FileDownloaded =
true !=
string.IsNullOrEmpty(actualDownloadedFilename);
3952 GPAL.
PublishSimpleEvent(GPALEventType.ERROR, $
"File not downloaded to [{fullFilePathToSave}]",
this, GPALObjectType.Browser);
3961 noSaveAsDialog =
true;
3970 GPAL.
PublishSimpleEvent(GPALEventType.ERROR, $
"Testing [{currentUrl2}] for PDF file",
this, GPALObjectType.Browser);
3983 if (
true == noSaveAsDialog)
3986 string downloadUrl =
FileHelper.GetDownloadUrl(webElement);
3987 string filename = Path.GetFileName(downloadUrl) ?? fullFilePathToSave;
3989 string wildcardFilename =
"*" + Path.GetExtension(filename);
3990 string systemDownloadedTo = Path.Combine(downloadDirectory, wildcardFilename);
3991 string actualDownloadedFilename =
null;
3993 actualDownloadedFilename =
FileHelper.WaitForDownloadToFinish(systemDownloadedTo,
BrowserSettings.DownloadTimeoutInSec);
3995 FileDownloaded =
true !=
string.IsNullOrEmpty(actualDownloadedFilename);
3999 if (
false == fullFilePathToSave.Equals(actualDownloadedFilename))
4001 File.Move(actualDownloadedFilename, fullFilePathToSave);
4002 if (
false == actualDownloadedFilename.Equals(fullFilePathToSave) &&
true == File.Exists(actualDownloadedFilename))
4003 GPAL.
PublishSimpleEvent(GPALEventType.INFO, $
"Downloaded [{actualDownloadedFilename}] unable to move to [{fullFilePathToSave}]",
null, GPALObjectType.None);
4005 GPAL.
PublishSimpleEvent(GPALEventType.INFO, $
"[{actualDownloadedFilename}] moved to [{fullFilePathToSave}]",
this, GPALObjectType.Browser);
4009 GPAL.
PublishSimpleEvent(GPALEventType.INFO, $
"[{filename}] not found in [{downloadDirectory}]",
this, GPALObjectType.Browser);
4017 FileHelper.watcher =
null;
4028 if (
int.MaxValue != CurrentUOW.WithAllThatMatch && ++rowCount == CurrentUOW.WithAllThatMatch)
4034 if (
true == InAction(WaitTime.Never < CurrentUOW.WaitForInMs))
4036 foreach (
Selector selector
in CurrentUOW.WithSelectorList)
4038 if (SelectorType.Selector != selector.SelectorType)
4041 ReadOnlyCollection<GPALElement> webelements = ElementHelper.FindWebElements(
this, CurrentUOW, selector, out
bool matchedAll, out List<GPALElement> matchedElements);
4042 if (
null != webelements)
4047 int matchCount = matchedElements.Count;
4049 for (
int elementIdx = 0; elementIdx < matchCount; elementIdx++)
4055 if (
false == UrlHelper.AreEquivalent(pageOfTheElements, urlNow))
4057 GoTo(pageOfTheElements);
4058 ElementHelper.FindWebElements(
this, CurrentUOW, selector, out
bool _, out matchedElements);
4060 if (matchedElements.Count != matchCount)
4061 GPAL.
PublishSimpleEvent(GPALEventType.WARNING, $
"[{selector.Name}] matched [{matchedElements.Count}] on the way back rather than [{matchCount}], so the remaining downloads may not be the ones first found.",
this, GPALObjectType.Browser);
4064 if (elementIdx >= matchedElements.Count)
4068 if (-1 == DownloadWorkflow(matchedElements[elementIdx], selector))
4074 foreach (
string filepath
in returnFilename)
4095 EAelement = webElement;
4096 EAselector = selector;
4098 LeftClickAndUpload(gPalFile);
4110 HWND windowOpened = IntPtr.Zero;
4112 if (
true == useEAvars)
4114 UploadWorkflow(EAelement, EAselector,
false);
4121 string fileToUpload =
null;
4129 && 1 < gPalFileToUpload.Count
4132 for (
int fileCount = 0; fileCount < gPalFileToUpload.
Count; fileCount++)
4134 fileToUpload = gPalFileToUpload.
Filenames[fileCount];
4136 GPAL.
PublishSimpleEvent(GPALEventType.INFO, $
"Uploading [{fileToUpload}]",
this, GPALObjectType.Browser);
4145 if (
true ==
string.IsNullOrEmpty(fileToUpload))
4146 GPAL.
PublishSimpleEvent(GPALEventType.WARNING,
"For some reason we have an empty filename", gPalFileToUpload, GPALObjectType.Other);
4151 if (
true == uploadAllFiles)
4153 PuppeteerClient.LeftClickAndUpload(webElement.ElementHandle).UploadFrom(gPalFileToUpload).Execute();
4156 PuppeteerClient.LeftClickAndUpload(webElement.ElementHandle).UploadFrom(fileToUpload).Execute();
4165 string filesToSend =
true == sendAllAtOnce ?
string.Join(
"\n", gPalFileToUpload.
Filenames) : fileToUpload;
4169 ((IWebElement)webElement.iWebElement).SendKeys(filesToSend);
4173 ElementHelper.FindWebElements(
this, CurrentUOW, selector, out
bool _, out List<GPALElement> matchedElements);
4174 webElement = 0 < matchedElements.Count ? matchedElements[0] :
null;
4175 webElement?.
SendKeys(filesToSend.Replace(
"\\",
"/"));
4178 if (
true == sendAllAtOnce)
4186 ElementHelper.Click(
this, selector, webElement, ClickType.LeftClick, ModifierKeys.NONE);
4189 windowOpened = FormHelper.WaitForWindow(
"Open",
BrowserSettings.WaitForWindowTimeoutInSeconds);
4192 windowOpened = FormHelper.WaitForWindow(
"File Upload",
BrowserSettings.WaitForWindowTimeoutInSeconds);
4195 if (IntPtr.Zero != windowOpened)
4197 int filesToSend = 1;
4199 if (
true == webElement.
GetAttribute(
"multiple")?.ToLower().Equals(
"true"))
4200 filesToSend = gPalFileToUpload.
Count;
4202 if (
true == uploadAllFiles)
4203 for (
int idx = 0; idx < gPalFileToUpload.
Count; idx++)
4205 fileToUpload = gPalFileToUpload.
Filenames[idx];
4206 HardwareHelper.SendString(
$@"""{fileToUpload}"" ");
4209 HardwareHelper.SendString(
$@"""{fileToUpload}"" ");
4215 windowOpened = FormHelper.WaitForWindow(
"Open", 1);
4217 windowOpened = FormHelper.WaitForWindow(
"File Upload", 1);
4219 if (IntPtr.Zero != windowOpened)
4222 if (
true == uploadAllFiles)
4242 if (
int.MaxValue != CurrentUOW.WithAllThatMatch && fileCount == CurrentUOW.WithAllThatMatch &&
false == uploadAllFiles)
4244 else if (
false == uploadAllFiles)
4251 if (
true == InAction(WaitTime.Never < CurrentUOW.WaitForInMs))
4253 foreach (
Selector selector
in CurrentUOW.WithSelectorList)
4255 if (SelectorType.Selector != selector.SelectorType)
4258 ReadOnlyCollection<GPALElement> webelements = ElementHelper.FindWebElements(
this, CurrentUOW, selector, out
bool matchedAll, out List<GPALElement> matchedElements);
4259 if (
null != webelements)
4261 foreach (
GPALElement webElement
in matchedElements)
4264 if (-1 == UploadWorkflow(webElement, selector, 1 == matchedElements.Count))
4287 string fullFilePathToSave;
4288 HWND windowOpened = IntPtr.Zero;
4289 List<string> returnFilename =
new List<string>();
4293 if (
true == InAction(WaitTime.Never < CurrentUOW.WaitForInMs))
4295 foreach (
Selector selector
in CurrentUOW.WithSelectorList)
4297 if (SelectorType.Selector != selector.SelectorType)
4300 ReadOnlyCollection<GPALElement> webelements = ElementHelper.FindWebElements(
this, CurrentUOW, selector, out
bool matchedAll, out List<GPALElement> matchedElements);
4301 if (
null != webelements)
4303 foreach (
GPALElement webElement
in matchedElements)
4308 returnFilename.Add(fullFilePathToSave);
4316 selector.InteractionType = InteractionType.JavaScript;
4318 selector.InteractionType = InteractionType.Hardware;
4323 bool noSaveAsDialog =
false;
4324 string currentUrl =
null, currentUrl2 =
null;
4333 ElementHelper.Click(
this, selector, webElement, ClickType.RightClick, ModifierKeys.NONE);
4357 selector.InteractionType = interactionType;
4360 windowOpened = FormHelper.WaitForWindow(
"Save As",
BrowserSettings.WaitForWindowTimeoutInSeconds);
4363 windowOpened = FormHelper.WaitForWindow(
"Enter name of file to save to…",
BrowserSettings.WaitForWindowTimeoutInSeconds);
4365 if (IntPtr.Zero == windowOpened)
4367 windowOpened = FormHelper.WaitForWindow(
"Save As",
BrowserSettings.WaitForWindowTimeoutInSeconds);
4369 if (IntPtr.Zero != windowOpened)
4370 GPAL.
PublishSimpleEvent(GPALEventType.WARNING, $
"GPAL thinks a misclick happened and the wrong item is saved. Please verify [{fullFilePathToSave}].",
this, GPALObjectType.Browser);
4374 if (IntPtr.Zero != windowOpened)
4376 HardwareHelper.SendString(fullFilePathToSave);
4382 windowOpened = FormHelper.WaitForWindow(
"Save As", 5);
4384 windowOpened = FormHelper.WaitForWindow(
"Enter name of file to save to…", 5);
4386 if (IntPtr.Zero != windowOpened)
4390 string actualDownloadedFilename2 =
null;
4392 actualDownloadedFilename2 =
FileHelper.WaitForDownloadToFinish(fullFilePathToSave,
BrowserSettings.DownloadTimeoutInSec);
4394 FileDownloaded =
true !=
string.IsNullOrEmpty(actualDownloadedFilename2);
4405 noSaveAsDialog =
true;
4419 if (
false ==
BrowserSettings.OpenPDFExternally &&
true == fullFilePathToSave.EndsWith(
".pdf"))
4421 GPAL.
PublishSimpleEvent(GPALEventType.ERROR, $
"Testing [{currentUrl2}] for PDF file",
this, GPALObjectType.Browser);
4433 if (
true == noSaveAsDialog)
4436 string downloadUrl =
FileHelper.GetDownloadUrl(webElement);
4437 string filename = Path.GetFileName(downloadUrl) ?? fullFilePathToSave;
4439 string wildcardFilename =
"*" + Path.GetExtension(filename);
4440 string systemDownloadedTo = Path.Combine(downloadDirectory, wildcardFilename);
4441 string actualDownloadedFilename2 =
null;
4443 actualDownloadedFilename2 =
FileHelper.WaitForDownloadToFinish(systemDownloadedTo,
BrowserSettings.DownloadTimeoutInSec);
4445 FileDownloaded =
true !=
string.IsNullOrEmpty(actualDownloadedFilename2);
4449 if (
false == fullFilePathToSave.Equals(actualDownloadedFilename2))
4451 File.Move(actualDownloadedFilename2, fullFilePathToSave);
4452 if (
false == actualDownloadedFilename2.Equals(fullFilePathToSave) &&
true == File.Exists(actualDownloadedFilename2))
4453 GPAL.
PublishSimpleEvent(GPALEventType.INFO, $
"Downloaded file [{actualDownloadedFilename2}] unable to move to [{fullFilePathToSave}]",
null, GPALObjectType.None);
4455 GPAL.
PublishSimpleEvent(GPALEventType.INFO, $
"[{actualDownloadedFilename2}] moved to [{fullFilePathToSave}]",
this, GPALObjectType.Browser);
4461 FileHelper.DownloadToFile(webElement, fullFilePathToSave);
4493 if (
int.MaxValue != CurrentUOW.WithAllThatMatch && ++rowCount == CurrentUOW.WithAllThatMatch)
4499 GPAL.
PublishSimpleEvent(GPALEventType.ERROR,
"Failed to detect Save As open window.",
this, GPALObjectType.Browser);
4503 foreach (
string filepath
in returnFilename)
4522 DatabaseHelper.TokenizeDatabase(CurrentUOW, inputDatabase);
4523 if (
true == InAction(WaitTime.Never < CurrentUOW.WaitForInMs))
4537 DatabaseHelper.TokenizeDatabase(CurrentUOW, inputDatabase);
4538 if (
true == InAction(WaitTime.Never < CurrentUOW.WaitForInMs))
4552 DatabaseHelper.TokenizeDatabase(CurrentUOW, inputDatabase);
4553 if (
true == InAction(WaitTime.Never < CurrentUOW.WaitForInMs))
4567 ((IGPALFileInternal)inputFile).TokenList =
FileHelper.TokenizeFile(CurrentUOW, inputFile);
4568 if (
true == InAction(WaitTime.Never < CurrentUOW.WaitForInMs))
4569 foreach (IGPALGrid<string> tokens
in ((IGPALFileInternal)inputFile).TokenList)
4583 ((IGPALFileInternal)inputFile).TokenList =
FileHelper.TokenizeFile(CurrentUOW, inputFile);
4584 if (
true == InAction(WaitTime.Never < CurrentUOW.WaitForInMs))
4585 foreach (IGPALGrid<string> tokens
in ((IGPALFileInternal)CurrentUOW.InputFile).TokenList)
4599 ((IGPALFileInternal)inputFile).TokenList =
FileHelper.TokenizeFile(CurrentUOW, inputFile);
4600 if (
true == InAction(WaitTime.Never < CurrentUOW.WaitForInMs))
4601 foreach (IGPALGrid<string> tokens
in ((IGPALFileInternal)inputFile).TokenList)
4615 if (
true == InAction(WaitTime.Never < CurrentUOW.WaitForInMs))
4629 if (
true == InAction(WaitTime.Never < CurrentUOW.WaitForInMs))
4643 if (
true == InAction(WaitTime.Never < CurrentUOW.WaitForInMs))
4655 if (
true == InAction(WaitTime.Never < CurrentUOW.WaitForInMs))
4656 ElementHelper.FillInFrom(
this, text, WriteMode.Append);
4668 if (
true == InAction(WaitTime.Never < CurrentUOW.WaitForInMs))
4669 ElementHelper.FillInFrom(
this, text, WriteMode.Overwrite);
4682 if (
true == InAction(WaitTime.Never < CurrentUOW.WaitForInMs))
4683 ElementHelper.FillInFrom(
this, textToUse, WriteMode.Insert);
4694 if (
null != selector)
4697 if (
true == InAction(WaitTime.Never < CurrentUOW.WaitForInMs))
4698 ElementHelper.Focus(
this);
4709 if (
null != selector)
4712 if (
true == InAction(WaitTime.Never < CurrentUOW.WaitForInMs))
4713 ElementHelper.Hide(
this);
4720 private string _pendingSetAttributeName;
4731 _pendingSetAttributeName = attribute;
4742 if (
true == InAction(WaitTime.Never < CurrentUOW.WaitForInMs))
4743 ElementHelper.
SetAttribute(
this, _pendingSetAttributeName, value);
4758 if (
true == InAction(WaitTime.Never < CurrentUOW.WaitForInMs))
4759 ElementHelper.SetValueFromElement(
this, selector);
4788 if (
null != selector)
4792 if (
true == InAction(WaitTime.Never < CurrentUOW.WaitForInMs))
4793 ElementHelper.MoveTo(
this);
4807 ReadOnlyCollection<GPALElement> tmpElems;
4809 if (
true == CurrentUOW.ActionCalled)
4811 GPAL.
PublishSimpleEvent(GPALEventType.INFO,
"Action has already been called. This workflow has already run. Continuing.",
this, GPALObjectType.Browser);
4815 GPAL.
PublishSimpleEvent(GPALEventType.INFO,
"Workflow starting, you should be careful if you follow this with actions. CallIf handlers will be invoked again.",
this, GPALObjectType.Browser);
4817 if (
true == WorkflowSetup(WaitTime.Never < CurrentUOW.WaitForInMs,
true))
4818 for (
int pages = 0; pages < CurrentUOW.PageCount; pages++)
4820 foreach (
Selector sel
in CurrentUOW.WithSelectorList)
4821 if (SelectorType.Selector == sel.SelectorType)
4822 tmpElems = ElementHelper.FindWebElements(
this, CurrentUOW, sel, out
bool matchedAll, out List<GPALElement> matchedElems);
4824 if (pages < CurrentUOW.PageCount - 1)
4830 GPAL.
PublishSimpleEvent(GPALEventType.WARNING, $
"No next page, breaking loop. Retrieved [{pages + 1}] out of [{CurrentUOW.PageCount}] pages.",
this, GPALObjectType.Browser);
4840 elements =
new List<GPALElement>();
4844 GPAL.
PublishSimpleEvent(GPALEventType.INFO,
"Action has already been called. Not retrieving elements.",
this, GPALObjectType.Browser);
4848 if (
true == WorkflowSetup(WaitTime.Never < CurrentUOW.WaitForInMs,
true))
4849 for (
int pages = 0; pages < CurrentUOW.
PageCount; pages++)
4852 if (SelectorType.Selector == sel.SelectorType)
4854 ElementHelper.FindWebElements(
this, CurrentUOW, sel, out
bool matchedAll, out List<GPALElement> matchedElems);
4855 if (sel.WebSelectorFoundResults !=
null)
4856 elements.AddRange(sel.WebSelectorFoundResults);
4861 BrowserHelper.TopBrowser(
this,
false);
4862 if (
false == BrowserHelper.GotoNextPage(
this))
4864 GPAL.PublishSimpleEvent(GPALEventType.WARNING, $
"No next page, breaking loop. Retrieved [{pages + 1}] out of [{CurrentUOW.PageCount}] pages.",
this, GPALObjectType.Browser);
4901 List<ReadOnlyCollection<GPALElement>> columns =
new List<ReadOnlyCollection<GPALElement>>();
4902 List<ReadOnlyCollection<GPALElement>> tmpColumns =
new List<ReadOnlyCollection<GPALElement>>();
4903 var selectorMeta =
new List<(string Name, string AttributeName)>();
4904 List<GPALElement> matchedElems =
new List<GPALElement>();
4905 dynamic foundElements =
null;
4907 CurrentUOW.GetGridCalled =
true;
4908 CurrentUOW.RetGrid.Clear();
4919 bool useDedupe = (returnGrid as GPALGrid<string>)?.UseDedupe ??
false;
4920 var seenRows =
new HashSet<string>();
4921 if (
true == useDedupe &&
null != returnGrid)
4922 foreach (List<string> existingRow
in returnGrid)
4923 seenRows.Add(RowDedupeKey(existingRow));
4924 bool capReached =
false;
4926 for (
int pages = 0; pages < CurrentUOW.PageCount && !capReached; pages++)
4929 if (!InAction(WaitTime.Never < CurrentUOW.WaitForInMs))
4931 GPAL.
PublishSimpleEvent(GPALEventType.WARNING,
"WaitFor did not find the elements for the grid. Not retrieving anything.",
this, GPALObjectType.Browser);
4937 CurrentUOW.MatchedRowIndexes.Clear();
4938 selectorMeta.Clear();
4943 foreach (
Selector sel
in CurrentUOW.WithSelectorList)
4946 sel.WebSelectorFoundResults =
null;
4947 sel.WebSelectorMatchedResults =
null;
4949 selectorMeta.Add((sel.
Name, sel.AttributeName));
4952 if (SelectorType.Selector == sel.selectorSettings.
SelectorType)
4954 bool matchedAll =
false;
4956 foundElements = ElementHelper.FindWebElements(
this, CurrentUOW, sel, out matchedAll, out matchedElems);
4960 if (matchedElems?.Count > 0)
4963 element.AttributeName = sel.AttributeName;
4965 tmpColumns.Add(
new ReadOnlyCollection<GPALElement>(matchedElems));
4971 var placeholder =
new List<GPALElement>();
4972 int rows = CurrentUOW.RowCount > 0 ? CurrentUOW.RowCount : 0;
4975 for (
int i = 0; i < rows; i++)
4976 placeholder.Add(
new GPALElement(
new Point(0, 0),
new Size(1, 1), $
"{GPAL.ErrorPlaceholder} : {sel.Name}",
BrowserDriver,
"GPALElement") { AttributeName = sel.AttributeName, Browser =
this,
Selector = sel });
4977 tmpColumns.Add(
new ReadOnlyCollection<GPALElement>(placeholder));
4981 if (matchedElems?.Count > 0)
4983 CurrentUOW.ColCount++;
4987 CurrentUOW.RowCount = Math.Max(CurrentUOW.RowCount, matchedElems.Count);
4988 else if (matchedElems?.Count > 0)
4990 CurrentUOW.PartialMatch =
true;
4992 foreach (var elem
in matchedElems)
4993 if ((tmpIdx = matchedElems.IndexOf(elem)) != -1 && !CurrentUOW.MatchedRowIndexes.ContainsKey(tmpIdx))
4994 CurrentUOW.MatchedRowIndexes.Add(tmpIdx, sel.AttributeName);
4995 CurrentUOW.RowCount = CurrentUOW.MatchedRowIndexes.Count;
4998 CurrentUOW.RowCount = int.MaxValue != CurrentUOW.WithAllThatMatch ? CurrentUOW.WithAllThatMatch : 0;
5001 else if (SelectorType.Data == sel.selectorSettings.
SelectorType)
5003 CurrentUOW.ColCount++;
5006 tmpColumns.Add(
new ReadOnlyCollection<GPALElement>(
new List<GPALElement>(1) { tmpElement }));
5008 else if (SelectorType.DataFunc == sel.selectorSettings.
SelectorType)
5010 CurrentUOW.ColCount++;
5012 tmpElement.Text = sel.selectorSettings.DataFunction();
5013 tmpColumns.Add(
new ReadOnlyCollection<GPALElement>(
new List<GPALElement>(1) { tmpElement }));
5018 GPAL.
PublishSimpleEvent(GPALEventType.INFO, $
"Grid for [{sel.Name}] page [{pages + 1}] returned [{CurrentUOW.RowCount}] rows of [{tmpColumns.Count()}] columns",
this, GPALObjectType.Browser);
5021 if (CurrentUOW.PartialMatch)
5024 foreach (var colElems
in tmpColumns)
5026 var newCol =
new List<GPALElement>();
5032 foreach (var idx
in CurrentUOW.MatchedRowIndexes.Keys)
5033 if (idx < colElems.Count)
5034 newCol.Add(colElems[idx]);
5035 columns.Add(
new ReadOnlyCollection<GPALElement>(newCol));
5039 columns = tmpColumns;
5042 int pageRows = 0 < columns.Count ? columns.Max(c => c.Count) : 0;
5043 int targetRows = CurrentUOW.RowCount > 0 ? CurrentUOW.RowCount : pageRows;
5047 if (
int.MaxValue != CurrentUOW.WithAllThatMatch && CurrentUOW.WithAllThatMatch <= pageRows)
5048 targetRows = Math.Min(targetRows, CurrentUOW.WithAllThatMatch);
5052 for (
int i = 0; i < columns.Count; i++)
5054 var col = columns[i].ToList();
5055 while (col.Count < targetRows)
5058 var firstNonEmpty = columns.FirstOrDefault(c => c.Count > 0)?.FirstOrDefault();
5059 var meta = selectorMeta[i];
5060 col.Add(
new GPALElement(
new Point(0, 0),
new Size(1, 1), $
"{GPAL.ErrorPlaceholder} : {meta.Name}",
BrowserDriver,
"GPALElement") { AttributeName = meta.AttributeName, Browser =
this, Css = firstNonEmpty?.Css, ElementBackendNodeId = (int)(firstNonEmpty?.ElementBackendNodeId) });
5062 columns[i] =
new ReadOnlyCollection<GPALElement>(col);
5064 CurrentUOW.RowCount = targetRows;
5071 for (
int r = 0; r < CurrentUOW.RowCount && !capReached; r++)
5073 var row =
new List<string>();
5074 for (
int c = 0; c < columns.Count; c++)
5076 var elem = columns[c][r];
5081 if (
false ==
BrowserSettings.UseHeadless &&
false ==
"GPALElement".Equals(elem.TagName))
5084 if (
false ==
string.IsNullOrEmpty(elem.Css))
5090 ElementHelper.ScrollIntoView(
this, elem);
5092 if (!
string.IsNullOrEmpty(elem.AttributeName))
5093 row.Add(elem.GetAttribute(elem.AttributeName));
5094 else if (
null != elem.TagName)
5096 switch (elem.TagName?.ToLower())
5098 case "a": row.Add(elem.GetAttribute(
"href"));
break;
5099 case "img": row.Add(elem.GetAttribute(
"src"));
break;
5100 default: row.Add(elem.Text);
break;
5108 if (
true == useDedupe &&
false == seenRows.Add(RowDedupeKey(row)))
5111 CurrentUOW.RetGrid.AddRow(row);
5112 if (
int.MaxValue != CurrentUOW.WithAllThatMatch && CurrentUOW.WithAllThatMatch > pageRows
5113 && CurrentUOW.RetGrid.Count() >= CurrentUOW.WithAllThatMatch)
5117 if (!capReached && pages < CurrentUOW.PageCount - 1)
5122 GPAL.
PublishSimpleEvent(GPALEventType.INFO, $
"No next page, breaking loop. Retrieved [{pages + 1}] out of [{CurrentUOW.PageCount}] pages.",
this, GPALObjectType.Browser);
5132 if (
null == returnGrid)
5133 returnGrid = (IGPALGrid<string>)CurrentUOW.RetGrid;
5135 returnGrid.Add((IGPALGrid<string>)CurrentUOW.RetGrid);
5144 private static string RowDedupeKey(List<string> row)
5146 return string.Join(
"\u001F", row);
5159 private bool TryGetGridFromTable(ref IGPALGrid<string> returnGrid)
5164 || SelectorType.Selector != CurrentUOW.
WithSelectorList[0].selectorSettings.SelectorType)
5171 bool matchedAll; List<GPALElement> matchedElems;
5172 ReadOnlyCollection<GPALElement> seeds = ElementHelper.FindWebElements(
this, CurrentUOW, sel, out matchedAll, out matchedElems);
5173 string tag = (
null != seeds && seeds.Count > 0) ? seeds[0].TagName?.ToLowerInvariant() :
null;
5174 bool isTable =
"table" == tag;
5175 bool isRows =
"tr" == tag;
5176 if (!isTable && !isRows)
5179 GPAL.
PublishSimpleEvent(GPALEventType.INFO, $
"GetGrid: [{sel.Name}] resolved to <{tag}>; extracting the table structurally.",
this, GPALObjectType.Browser);
5181 var seenRows =
new HashSet<string>();
5182 List<string> headers =
null;
5183 bool capReached =
false;
5187 for (
int page = 0; page < CurrentUOW.PageCount && !capReached; page++)
5191 seeds = ElementHelper.FindWebElements(
this, CurrentUOW, sel, out matchedAll, out matchedElems);
5192 if (
null == seeds || 0 == seeds.Count)
5195 BrowserHelper.CheckDocumentReady(
this);
5198 var bodyRows =
new List<GPALElement>();
5203 var headerRows = FindWithin(table,
"thead tr");
5204 var tableBody = FindWithin(table,
"tbody tr");
5205 if (0 == tableBody.Count && 0 == headerRows.Count)
5208 var all = FindWithin(table,
"tr");
5209 if (all.Count > 0 && RowIsAllHeader(all[0]))
5211 headerRows.Add(all[0]);
5212 tableBody.AddRange(all.Skip(1));
5215 tableBody.AddRange(all);
5217 if (
null == headers && headerRows.Count > 0)
5219 var hmatrix = BuildTableMatrix(headerRows);
5220 if (hmatrix.Count > 0)
5221 headers = hmatrix[0];
5223 bodyRows.AddRange(tableBody);
5228 bodyRows.AddRange(seeds);
5232 List<List<string>> matrix = BuildTableMatrix(bodyRows);
5234 foreach (List<string> row
in matrix)
5236 string key =
string.Join(
"␁", row);
5237 if (!seenRows.Add(key))
5239 CurrentUOW.RetGrid.AddRow(row);
5240 if (CurrentUOW.RetGrid.Count() >= cap)
5247 if (!capReached && page < CurrentUOW.PageCount - 1)
5249 BrowserHelper.TopBrowser(
this,
false);
5250 if (!BrowserHelper.GotoNextPage(
this))
5252 GPAL.PublishSimpleEvent(GPALEventType.INFO, $
"GetGrid: no next page after page [{page + 1}].",
this, GPALObjectType.Browser);
5258 if (
null != headers && headers.Count > 0
5259 && (
null == CurrentUOW.HeaderList || 0 == CurrentUOW.HeaderList.Count))
5260 CurrentUOW.HeaderList = headers;
5262 returnGrid = (IGPALGrid<string>)CurrentUOW.RetGrid;
5268 private List<GPALElement> FindWithin(GPALElement parent,
string css)
5270 Selector scoped = GPAL.Selector.WithCSS(css).ToGPALObject();
5271 bool matchedAll; List<GPALElement> matchedElems;
5272 ReadOnlyCollection<GPALElement> found = ElementHelper.FindWebElements(
this, CurrentUOW, scoped, out matchedAll, out matchedElems, webElement: parent);
5273 return (
null != found) ? found.ToList() :
new List<GPALElement>();
5277 private bool RowIsAllHeader(GPALElement tr)
5279 var cells = FindWithin(tr,
"td, th");
5280 if (0 == cells.Count)
5282 foreach (GPALElement c
in cells)
5283 if (!
"th".Equals(c.
TagName, StringComparison.OrdinalIgnoreCase))
5291 private List<List<string>> BuildTableMatrix(List<GPALElement> rowElems)
5293 var matrix =
new List<List<string>>();
5295 var pending =
new Dictionary<int, KeyValuePair<int, string>>();
5297 foreach (GPALElement tr
in rowElems)
5299 var cells = FindWithin(tr,
"td, th");
5300 var row =
new List<string>();
5301 int col = 0, ci = 0;
5303 while (ci < cells.Count || pending.Keys.Any(k => k >= col))
5305 KeyValuePair<int, string> carry;
5306 if (pending.TryGetValue(col, out carry))
5308 row.Add(carry.Value);
5309 int left = carry.Key - 1;
5310 if (left > 0) pending[col] =
new KeyValuePair<int, string>(left, carry.Value);
5311 else pending.Remove(col);
5316 if (ci < cells.Count)
5318 GPALElement cell = cells[ci++];
5319 string text = CellText(cell);
5320 int cspan = ParseSpan(cell,
"colspan");
5321 int rspan = ParseSpan(cell,
"rowspan");
5322 for (
int c = 0; c < cspan; c++)
5325 if (rspan > 1) pending[col] =
new KeyValuePair<int, string>(rspan - 1, text);
5332 row.Add(GPAL.ErrorPlaceholder);
5342 foreach (var r
in matrix)
if (r.Count > width) width = r.Count;
5343 foreach (var r
in matrix)
while (r.Count < width) r.Add(GPAL.ErrorPlaceholder);
5348 private static int ParseSpan(GPALElement cell,
string attr)
5352 return (
int.TryParse(v, out n) && n > 0) ? n : 1;
5356 private string CellText(GPALElement cell)
5360 switch (cell.
TagName?.ToLowerInvariant())
5364 default:
return cell.Text ??
string.Empty;
5420 BrowserSettings.CallFilter = urlFragment;
5448 if (
false == CanCaptureCalls)
5450 GPAL.
PublishSimpleEvent(GPALEventType.ERROR, $
"No template: capturing calls is not available on [{BrowserSettings.AutomationEngine}]",
this, GPALObjectType.Browser);
5458 Stopwatch waited = Stopwatch.StartNew();
5460 while (
null == found && CallTemplateWaitMilliseconds > waited.ElapsedMilliseconds)
5462 CaptureCalls(out List<GPALCall> calls);
5471 System.Threading.Thread.Sleep(250);
5478 GPAL.
PublishSimpleEvent(GPALEventType.ERROR, $
"No call matching [{BrowserSettings.CallFilter}] in [{waited.ElapsedMilliseconds}]ms, so there is no template",
this, GPALObjectType.Browser);
5484 .WithPath(found.
Url)
5485 .WithHttpMethod(
"POST".Equals(found.
Method, StringComparison.OrdinalIgnoreCase) ? Enums.HttpVerb.Post : Enums.HttpVerb.Get)
5491 foreach (KeyValuePair<string, string> header
in found.Headers)
5493 template.WithHeader($
"{header.Key}: {header.Value}");
5495 GPAL.
PublishSimpleEvent(GPALEventType.INFO, $
"Template taken from the page's own [{found.Method}] to [{found.Url}] after [{waited.ElapsedMilliseconds}]ms, with [{template.Headers.Count}] of its headers",
this, GPALObjectType.Browser);
5501 private const int CallTemplateWaitMilliseconds = 30_000;
5505 private bool CanCaptureCalls =>
true == BrowserSettings.UsePuppeteer
5506 ||
true == BrowserSettings.UseOttoMagic
5516 if (
false == CanCaptureCalls)
5520 BrowserSettings.CaptureUnavailableReported =
true;
5522 GPAL.
PublishSimpleEvent(GPALEventType.WARNING, $
"Capturing calls is not available on [{BrowserSettings.AutomationEngine}]. It needs a browser that reports its requests, and this one has none started",
this, GPALObjectType.Browser);
5532 BrowserSettings.CaptureCalls = capture;
5541 BrowserSettings.CaptureCalls = capture;
5543 if (
true == capture)
5546 BrowserHelper.SeleniumAddScriptToEvaluateOnNewDocument(
BrowserSettings, BrowserHelper.CallRecorderScript);
5548 BrowserHelper.ExecuteJavaScript(
this, BrowserHelper.CallRecorderScript);
5555 BrowserSettings.CaptureCalls = capture;
5560 BrowserSettings.PuppeteerCommunicator.CaptureCalls(capture).GetAwaiter().GetResult();
5585 calls =
true == BrowserSettings.UsePuppeteer
5586 ?
true == BrowserSettings.HasPuppeteerCommunicator
5587 ?
new List<GPALCall>(
BrowserSettings.PuppeteerCommunicator.CapturedCalls)
5588 :
new List<GPALCall>()
5589 :
true == BrowserSettings.UseOttoMagic
5590 ? ExtensionRecordedCalls()
5595 GPAL.
PublishSimpleEvent(GPALEventType.DEBUG, $
"Handing back [{calls.Count}] captured calls.",
this, GPALObjectType.Browser);
5624 GPAL.
PublishSimpleEvent(GPALEventType.DEBUG,
"Forgot the calls captured so far.",
this, GPALObjectType.Browser);
5632 private static string CdpResourceType(
string webRequestType)
5634 string retVal = webRequestType;
5636 switch (webRequestType)
5639 case "sub_frame": retVal =
"Document";
break;
5640 case "xmlhttprequest": retVal =
"XHR";
break;
5641 case "script": retVal =
"Script";
break;
5643 case "imageset": retVal =
"Image";
break;
5644 case "stylesheet": retVal =
"Stylesheet";
break;
5645 case "font": retVal =
"Font";
break;
5646 case "media": retVal =
"Media";
break;
5647 case "websocket": retVal =
"WebSocket";
break;
5648 case "ping": retVal =
"Ping";
break;
5649 case "csp_report": retVal =
"CSPViolationReport";
break;
5654 case "other": retVal =
"Other";
break;
5662 private List<GPALCall> ExtensionRecordedCalls()
5664 List<GPALCall> retVal =
new List<GPALCall>();
5668 if (
true ==
string.IsNullOrWhiteSpace(json))
return retVal;
5675 if (
true == json.StartsWith(
"\""))
5676 json = Newtonsoft.Json.JsonConvert.DeserializeObject<
string>(json);
5678 Newtonsoft.Json.Linq.JObject snapshot;
5684 snapshot = Newtonsoft.Json.Linq.JObject.Parse(json);
5686 catch (GPALException)
5690 catch (Exception ex)
5692 GPAL.PublishSimpleEvent(GPALEventType.ERROR, $
"The captured calls could not be read. [{json.Length}] characters starting [{json.Substring(0, Math.Min(120, json.Length))}]",
this, GPALObjectType.Browser, ex);
5699 int dropped = (
int?)snapshot[
"dropped"] ?? 0;
5702 GPAL.PublishSimpleEvent(GPALEventType.DEBUG, $
"The recorder dropped [{dropped}] older calls to stay within its limit",
this, GPALObjectType.Browser);
5704 foreach (Newtonsoft.Json.Linq.JObject recorded in snapshot[
"calls"] ??
new Newtonsoft.Json.Linq.JArray())
5706 GPALCall call =
new GPALCall
5708 Url = recorded[
"url"]?.ToString(),
5709 Method = recorded[
"method"]?.ToString(),
5710 ResourceType = CdpResourceType(recorded[
"type"]?.ToString()),
5711 PostData = recorded[
"postData"]?.ToString(),
5712 Initiator = recorded[
"initiator"]?.ToString(),
5713 Status = (
int?)recorded[
"status"] ?? 0
5716 if (recorded[
"headers"] is Newtonsoft.Json.Linq.JObject headers)
5717 foreach (KeyValuePair<string, Newtonsoft.Json.Linq.JToken> header
in headers)
5718 call.
Headers[header.Key] = header.Value?.ToString();
5728 private List<GPALCall> RecordedCalls()
5730 List<GPALCall> retVal =
new List<GPALCall>();
5734 string json = BrowserHelper.ExecuteJavaScript(
this,
"return JSON.stringify(window.__gpalCalls || []);");
5736 if (
true ==
string.IsNullOrWhiteSpace(json))
return retVal;
5738 foreach (Newtonsoft.Json.Linq.JObject recorded in Newtonsoft.Json.Linq.JArray.Parse(json))
5740 GPALCall call =
new GPALCall
5742 Url = recorded[
"url"]?.ToString(),
5743 Method = recorded[
"method"]?.ToString(),
5744 ResourceType = recorded[
"type"]?.ToString(),
5745 PostData = recorded[
"postData"]?.ToString(),
5746 Initiator =
"script",
5747 Status = (
int?)recorded[
"status"] ?? 0
5750 if (recorded[
"headers"] is Newtonsoft.Json.Linq.JObject headers)
5751 foreach (KeyValuePair<string, Newtonsoft.Json.Linq.JToken> header
in headers)
5752 call.
Headers[header.Key] = header.Value?.ToString();
5760 public IAllowWithHeaderOrFileActions Fetch(GPALRequest request)
5762 CurrentUOW.FetchResults.Clear();
5764 if (
null == request)
5765 GPAL.PublishSimpleEvent(GPALEventType.ERROR,
"No request given to fetch.",
this, GPALObjectType.Browser);
5767 BrowserHelper.FetchWithTokens(
this, request);
5780 List<string> values =
new List<string>();
5782 GPAL.
PublishSimpleEvent(GPALEventType.INFO, $
"Handing back [{CurrentUOW.FetchResults.Count}] fetch responses.",
this, GPALObjectType.Browser);
5787 foreach (
string body
in CurrentUOW.FetchResults)
5788 values.Add(
true == StringFormatDetector.IsJson(body.Trim()) ? body : Newtonsoft.Json.JsonConvert.SerializeObject(body));
5790 data =
"[" +
string.Join(
",", values) +
"]";
5797 var converter =
GPAL.
Converter.WithInput(CurrentUOW.RetGrid).WithColumnNames(ResolveHeaderColumns(file));
5801 converter.WithFirstLineHasColumnNames(
true);
5802 converter.SaveTo(file);
5814 GPAL.
Converter.WithInput(CurrentUOW.RetGrid).WithColumnNames(ResolveHeaderColumns(file)).AppendTo(file);
5826 private List<string> ResolveHeaderColumns(
GPALFile file)
5828 List<string> selectorNames = CurrentUOW.
WithSelectorList?.Where(s =>
null != s.Name).Select(s => s.Name).ToList()
5829 ??
new List<string>();
5831 List<string> fileColumns =
new List<string>();
5832 IGPALGrid<string> columnList = ((IGPALFileInternal)file).FileSettings.ColumnList;
5833 if (
null != columnList && 0 < columnList.Count() &&
null != columnList[0])
5834 fileColumns =
new List<string>(columnList[0]);
5836 List<string> resolved = (
null != CurrentUOW.HeaderList && 0 < CurrentUOW.
HeaderList.Count)
5838 :
new List<string>();
5839 for (
int i = resolved.Count; i < fileColumns.Count; i++)
5840 resolved.Add(fileColumns[i]);
5841 for (
int i = resolved.Count; i < selectorNames.Count; i++)
5842 resolved.Add(selectorNames[i]);
5858 if (
true == CurrentUOW.ActionCalled)
5860 GPAL.
PublishSimpleEvent(GPALEventType.INFO, $
"Sleeping for [{waitForTimeInMs}] ms",
this, GPALObjectType.Browser);
5861 Thread.Sleep(waitForTimeInMs);
5864 CurrentUOW.WaitForInMs = waitForTimeInMs;
5875 bool weSetWaitFor =
false;
5876 WaitTime safe = WaitTime.Never;
5879 if (WaitTime.Never == CurrentUOW.WaitForInMs)
5881 safe = CurrentUOW.WaitForInMs;
5882 CurrentUOW.WaitForInMs = WaitTime.Immediate;
5883 weSetWaitFor =
true;
5887 WorkflowSetup(
true,
true);
5889 if (
true == weSetWaitFor)
5890 CurrentUOW.WaitForInMs = safe;
5902 if (ElementState.NotSet != elementState)
5904 CurrentUOW.WaitForElementState = elementState;
5906 if (
true == CurrentUOW.ActionCalled)
5907 ElementHelper.WaitFor(
this, CurrentUOW.WaitForInMs, out
bool matchedAll);
5910 GPAL.
PublishSimpleEvent(GPALEventType.ERROR,
"ElementState NOTSET is not a valid wait condition. Please select a valid ElementState.",
null, GPALObjectType.None);
5922 FormHelper.WaitForWindow(waitForTitle,
BrowserSettings.WaitForWindowTimeoutInSeconds);
5933 FormHelper.WaitForWindow(waitForTitleRegex,
BrowserSettings.WaitForWindowTimeoutInSeconds);
5943 BrowserSettings.PageOrientation = pageOrientation;
5956 string fullHtml =
null;
5958 string selectorPath =
string.IsNullOrEmpty(CurrentUOW.CurrentSelector?.SelectorPaths[0].SelectorPath)
5960 : CurrentUOW.CurrentSelector?.SelectorPaths[0].SelectorPath;
5963 fullHtml =
MagicHelper.GetContentAndCss(selectorPath);
5967 $@"var div = document.querySelector('{selectorPath}'); return div.outerHTML;",
5973 function getAllCss() {{
5975 var stylesheets = document.styleSheets;
5976 for (var i = 0; i < stylesheets.length; i++) {{
5978 var rules = stylesheets[i].cssRules;
5979 for (var j = 0; j < rules.length; j++) {{
5980 css += rules[j].cssText + ' ';
5982 }} catch (e) {{ /* Ignore cross-origin stylesheet errors */ }}
5987 function getElementCss(element) {{
5989 var style = window.getComputedStyle(element);
5990 for (var i = 0; i < style.length; i++) {{
5991 css += style[i] + ': ' + style.getPropertyValue(style[i]) + '; ';
5996 var allCss = getAllCss();
5997 var div = document.querySelector('{selectorPath}');
5998 var elementCss = getElementCss(div);
6000 return '<style>' + allCss + ' ' + elementCss + '</style>';
6002 fullHtml =
$@"<html><head>{cssContent}</head><body>{elementContent}</body></html>";
6006 #region NEW get the browser to print to pdf without CDP
6008 string outputHtmlPath = $
"{gpalFile.Filename}.html";
6011 File.WriteAllText(outputHtmlPath, fullHtml);
6014 string pdfOptionsPath = $
"{gpalFile.Filename}_pdf_options.json";
6015 string pdfOptionsJson =
@"{
6016 ""printBackground"": true,
6017 ""landscape"":" + (PageOrientation.Landscape == BrowserSettings.PageOrientation ?
"true" :
"false")
6020 ""marginBottom"": 0,
6024 File.WriteAllText(pdfOptionsPath, pdfOptionsJson);
6027 List<string> addlArgs =
new List<string>
6029 "--no-pdf-header-footer",
6030 $
"--print-to-pdf=\"{gpalFile.Filename}\"",
6031 $
"--print-to-pdf-options=\"{pdfOptionsPath}\""
6035 string fileUrl = $
"file:///{Path.GetFullPath(outputHtmlPath).Replace("\\
", "/
")}";
6036 System.Diagnostics.Process pdfPrinter =
MagicHelper.LaunchBrowser(
this, fileUrl, addlArgs);
6039 bool exitedCleanly = pdfPrinter.WaitForExit(30000);
6045 if (!pdfPrinter.HasExited)
6051 "PrintToPDF process did not exit cleanly (timeout)",
6054 else if (pdfPrinter.ExitCode != 0)
6057 $
"PrintToPDF exited with code [{pdfPrinter.ExitCode}]",
6064 File.Delete(outputHtmlPath);
6065 File.Delete(pdfOptionsPath);
6071 catch (Exception ex)
6076 #endregion NEW get the browser to print to pdf without CDP
6078 #region OLD use CDP to page.PrintToPDF a window
6080 var newWindow = window.open('', '_blank');
6081 newWindow.document.open();
6082 newWindow.document.write('<html><head>' + arguments[0] + '</head><body>' + arguments[1] + '</body></html>');
6083 newWindow.document.close();
6085 ", browserSettings.Browser,
new { cssContent, elementContent });
6088 System.Threading.Thread.Sleep(2000);
6091 var windows = browserSettings.BrowserDriver.WindowHandles;
6092 browserSettings.BrowserDriver.SwitchTo().Window(windows[windows.Count - 1]);
6095 var printSettings =
new Dictionary<string, object>
6097 {
"printBackground",
true },
6098 {
"landscape",
false }
6102 dynamic result =
null;
6104 if (
BrowserType.Chrome == browserSettings.BrowserType)
6105 result = ((OpenQA.Selenium.Chromium.ChromiumDriver)browserSettings.BrowserDriver).ExecuteCdpCommand(
"Page.printToPDF", printSettings);
6107 result = ((OpenQA.Selenium.Edge.EdgeDriver)browserSettings.BrowserDriver).ExecuteCdpCommand(
"Page.printToPDF", printSettings);
6109 GPAL.
PublishSimpleEvent(GPALEventType.WARNING, $
"[{browserSettings.BrowserType}] does not support Page.printToPDF", browserSettings.Browser, GPALObjectType.Browser);
6111 var resultDict = result as IDictionary<string, object>;
6113 if (resultDict !=
null && resultDict.ContainsKey(
"data"))
6116 var pdfData = Convert.FromBase64String(resultDict[
"data"].ToString());
6118 System.IO.File.WriteAllBytes(gpalFile.
Filename, pdfData);
6121 browserSettings.BrowserDriver.Close();
6122 windows = browserSettings.BrowserDriver.WindowHandles;
6123 browserSettings.BrowserDriver.SwitchTo().Window(windows[windows.Count - 1]);
6124 #endregion OLD use CDP to page.PrintToPDF a window
6137 if (
true == InAction(WaitTime.Never < CurrentUOW.WaitForInMs))
6139 ElementHelper.SendString(
this, textToSend);
6163 GPAL.
PublishSimpleEvent(GPALEventType.INFO, $
"Pressed [{GetModifierKeys(modifierKeys)}]",
this, GPALObjectType.Browser);
6165 if (ModifierKeys.NONE != modifierKeys)
6168 HardwareHelper.PressModifierKey(modifierKeys);
6181 bool anyKeySet =
false;
6183 if (modifierKeys.HasFlag(ModifierKeys.Alt))
6185 actions.KeyDown(Keys.Alt);
6188 if (modifierKeys.HasFlag(ModifierKeys.Control))
6190 actions.KeyDown(Keys.Control);
6193 if (modifierKeys.HasFlag(ModifierKeys.Shift))
6195 actions.KeyDown(Keys.Shift);
6198 if (modifierKeys.HasFlag(ModifierKeys.Windows))
6200 actions.KeyDown(Keys.Meta);
6204 ModifierKeys unsupported = modifierKeys & (ModifierKeys.Application | ModifierKeys.ScrollLock);
6205 if (ModifierKeys.NONE != unsupported)
6206 GPAL.
PublishSimpleEvent(GPALEventType.WARNING, $
"PressModifierKey: [{GetModifierKeys(unsupported)}] is not supported via Selenium/WebDriver Actions and was ignored.",
this, GPALObjectType.Browser);
6208 if (
true == anyKeySet)
6231 public IAllowBrowserActionOrAnySelector
ReleaseModifierKey(ModifierKeys modifierKeys)
6233 GPAL.PublishSimpleEvent(GPALEventType.INFO, $
"Released [{GetModifierKeys(modifierKeys)}]",
this, GPALObjectType.Browser);
6235 if (ModifierKeys.NONE != modifierKeys)
6238 HardwareHelper.ReleaseModifierKeys(modifierKeys);
6251 bool anyKeySet =
false;
6253 if (modifierKeys.HasFlag(ModifierKeys.Alt))
6255 actions.KeyUp(Keys.Alt);
6258 if (modifierKeys.HasFlag(ModifierKeys.Control))
6260 actions.KeyUp(Keys.Control);
6263 if (modifierKeys.HasFlag(ModifierKeys.Shift))
6265 actions.KeyUp(Keys.Shift);
6268 if (modifierKeys.HasFlag(ModifierKeys.Windows))
6270 actions.KeyUp(Keys.Meta);
6274 ModifierKeys unsupported = modifierKeys & (ModifierKeys.Application | ModifierKeys.ScrollLock);
6275 if (ModifierKeys.NONE != unsupported)
6276 GPAL.
PublishSimpleEvent(GPALEventType.WARNING, $
"ReleaseModifierKey: [{GetModifierKeys(unsupported)}] is not supported via Selenium/WebDriver Actions and was ignored.",
this, GPALObjectType.Browser);
6278 if (
true == anyKeySet)
6305 if (
true == InAction(WaitTime.Never < CurrentUOW.WaitForInMs))
6307 ElementHelper.SendKey(
this, VKCode);
6338 MagicHelper.ScrollWindowByVertical(scrollAmountInPixels);
6340 PuppeteerClient.ScrollWindowByVertical(scrollAmountInPixels).Execute();
6354 ElementHelper.ScrollElement(
this, scrollAmountInPixels, ScrollTypes.Horizontal);
6365 ElementHelper.ScrollElement(
this, scrollAmountInPixels, ScrollTypes.Vertical);
6385 public IAllowBrowserActionOrAnySelector
GoToTab(dynamic URLorTabTuple =
null)
6390 if (URLorTabTuple is TabTuple)
6391 UpdateActiveTab(URLorTabTuple);
6393 UpdateActiveTab(gotoTabTuple);
6397 if (URLorTabTuple is GPALUrl url)
6399 else if (URLorTabTuple is
int tabId)
6406 string currentUrl = this.
BrowserDriver.SwitchTo().Window(handle).Url;
6407 if (currentUrl == URLorTabTuple)
6410 BrowserSettings.CurrentURL = URLorTabTuple;
6420 GPAL.
PublishSimpleEvent(GPALEventType.ERROR, $
"Failed to detect tab with URL [{URLorTabTuple}]",
this, GPALObjectType.Browser);
6432 public IAllowBrowserActionOrAnySelector
DragAndDrop(ModifierKeys modifierKeys)
6435 if (
true == InAction(WaitTime.Never < CurrentUOW.WaitForInMs))
6437 ElementHelper.DragAndDrop(
this, modifierKeys);
6450 if (
null != selector)
6454 if (
true == InAction(WaitTime.Never < CurrentUOW.WaitForInMs))
6456 ElementHelper.DragAndDrop(
this);
6483 private readonly List<string> _injectedScriptIdentifiers =
new List<string>();
6487 private bool _scriptsRegistered =
false;
6488 private readonly List<string> _pendingInjectedScripts =
new List<string>();
6491 private bool _dialogRegistered =
false;
6494 private string _dialogPushed =
null;
6497 private int _dialogSeq = 0;
6503 private bool EngineReachable
6510 retVal =
false ==
string.IsNullOrEmpty(
BrowserSettings.RestApiBaseUrl);
6525 private void RegisterInjectedScript(
string script)
6527 _scriptsRegistered =
true;
6539 var identifier = BrowserHelper.SeleniumAddScriptToEvaluateOnNewDocument(
BrowserSettings, script);
6540 if (
false ==
string.IsNullOrEmpty(identifier))
6541 _injectedScriptIdentifiers.Add(identifier);
6550 internal void ApplyPendingInjectedScripts()
6552 if (0 < _pendingInjectedScripts.Count &&
true == EngineReachable)
6554 foreach (var script
in _pendingInjectedScripts)
6555 RegisterInjectedScript(script);
6557 _pendingInjectedScripts.Clear();
6583 GPAL.
PublishSimpleEvent(GPALEventType.WARNING, $
"Selenium takes this when the browser starts, and this one is already running, so dialogs are not answered. Call WithDialogsAccepted before the first GoTo",
this, GPALObjectType.Browser);
6585 BrowserSettings.DialogsAccepted = accept;
6587 ArmDialogHandling();
6599 BrowserSettings.DialogText = text;
6604 BrowserSettings.DialogsAccepted =
true;
6606 ArmDialogHandling();
6624 BrowserSettings.DialogsAccepted =
null;
6625 BrowserSettings.DialogText =
null;
6627 ArmDialogHandling();
6636 internal void ArmDialogHandling()
6640 if (
false == EngineReachable || (
null ==
BrowserSettings.DialogsAccepted &&
false == _dialogRegistered))
6643 if (
true == InPageDialogs)
6645 if (
false == _dialogRegistered &&
null !=
BrowserSettings.DialogsAccepted)
6647 string script = DialogScript();
6649 _dialogRegistered =
true;
6657 if (
true == _navigated)
6658 BrowserHelper.ExecuteJavaScript(
this, script);
6660 GPAL.
PublishSimpleEvent(GPALEventType.INFO, $
"Answering dialogs in the page, accept [{BrowserSettings.DialogsAccepted}]",
this, GPALObjectType.Browser);
6668 if (
true == _navigated && written != _dialogPushed)
6670 _dialogPushed = written;
6671 PushDialogAnswers();
6680 if (
true ==
UseOttoMagic &&
true == _navigated &&
false == _dialogChecked)
6682 _dialogChecked =
true;
6686 string seen = BrowserHelper.ExecuteJavaScript(
this,
"window.__gpalDialogSeq");
6688 if (
true ==
string.IsNullOrEmpty(seen) ||
"null" == seen)
6689 GPAL.PublishSimpleEvent(GPALEventType.WARNING, $
"The dialog override is not on the page, so dialogs will stop this workflow. The usual cause is Allow user scripts being off for the extension",
this, GPALObjectType.Browser);
6696 _dialogRegistered =
true;
6700 GPAL.PublishSimpleEvent(GPALEventType.INFO, $
"Answering dialogs over CDP, accept [{BrowserSettings.DialogsAccepted}]",
this, GPALObjectType.Browser);
6721 private bool _navigated =
false;
6724 private bool _dialogChecked =
false;
6731 private void PushDialogAnswers()
6734 BrowserHelper.ExecuteJavaScript(
this,
"if (window.__gpalDialogSeq) { " + DialogAnswers() +
" }");
6742 private string DialogAnswers()
6744 string accept =
null == BrowserSettings.DialogsAccepted
6746 :
true == BrowserSettings.DialogsAccepted ?
"true" :
"false";
6747 string typed =
null == BrowserSettings.DialogText ?
"null" : JsonConvert.SerializeObject(
BrowserSettings.DialogText);
6749 return "window.__gpalDialogAccept = " + accept +
"; window.__gpalDialogAnswer = " + typed +
";";
6758 private string DialogScript()
6762 " var seq = " + (++_dialogSeq) +
";" +
6763 " if (window.__gpalDialogSeq >= seq) return;" +
6764 " window.__gpalDialogSeq = seq;" +
6765 " " + DialogAnswers() +
6766 " var asText = Function.prototype.toString;" +
6767 " function stand(name, fn) {" +
6768 " Object.defineProperty(fn, 'name', { value: name, configurable: true });" +
6773 " var onto = Object.getOwnPropertyDescriptor(window, name) ? window : Window.prototype;" +
6774 " Object.defineProperty(onto, name," +
6775 " { value: fn, writable: true, enumerable: true, configurable: true });" +
6779 " var real = { alert: window.alert, confirm: window.confirm, prompt: window.prompt };" +
6780 " function off() { return null === window.__gpalDialogAccept; }" +
6781 " stand('alert', function (message) {" +
6782 " if (off()) return real.alert.call(window, message);" +
6784 " stand('confirm', function (message) {" +
6785 " if (off()) return real.confirm.call(window, message);" +
6786 " return true === window.__gpalDialogAccept;" +
6788 " stand('prompt', function (message, preset) {" +
6789 " if (off()) return real.prompt.call(window, message, preset);" +
6790 " if (true !== window.__gpalDialogAccept) return null;" +
6791 " return null !== window.__gpalDialogAnswer ? window.__gpalDialogAnswer : (preset || '');" +
6795 " Function.prototype.toString = function () {" +
6796 " if (this === window.alert || this === window.confirm || this === window.prompt" +
6797 " || this === Function.prototype.toString)" +
6798 " return 'function ' + this.name + '() { [native code] }';" +
6799 " return asText.call(this);" +
6820 window = Rectangle.Empty;
6822 if (
true == InAction(
false))
6827 ElementHelper.GetWindowRectangle(
this, out window);
6844 if (
false == EngineReachable)
6845 _pendingInjectedScripts.Add(script);
6847 RegisterInjectedScript(script);
6861 foreach (var filename
in scriptFile.
Filenames)
6874 _pendingInjectedScripts.Clear();
6876 if (
true == EngineReachable)
6888 foreach (var identifier
in _injectedScriptIdentifiers)
6890 _injectedScriptIdentifiers.Clear();
6902 public IAllowBrowserActionOrAnySelector
SetRange(
int rangeValue)
6904 if (
true == InAction(WaitTime.Never < CurrentUOW.WaitForInMs))
6907 ElementHelper.FindWebElements(
this, CurrentUOW, selector, out _, out List<GPALElement> elems);
6910 ElementHelper.ScrollIntoView(
this, elem);
6920 arguments[0].value = arguments[1];
6921 arguments[0].dispatchEvent(new Event('input', { bubbles: true }));
6922 arguments[0].dispatchEvent(new Event('change', { bubbles: true }));
6932 #endregion <Browser Actions>
6934 private bool _started =
false;
6938 internal bool Started
6941 set => _started = value;
6949 set => BrowserSettings.ServerResponseCode = value;
6958 internal set => BrowserSettings.CurrentURL = value;
6994 internal set => BrowserSettings.AutomationEngine = value;
7008 BrowserSettings.FileDownloaded = value;
7025 internal UnitOfWork CurrentUOW {
get;
set; } =
null;
7029 internal WorkflowManager WorkflowManager {
get;
set; }
7033 internal string Name
7042 BrowserSettings.BrowserName = value;
7059 BrowserSettings.BrowserDriver = value;
7080 return _browserSettings;
7085 _browserSettings = value;
7091 internal string DownloadLocation
7104 internal set => BrowserSettings.PuppeteerCommunicator = value;
7120 try { System.Diagnostics.Process.GetProcessById(
BrowserSettings.ServiceDriverPid);
return true; }
7121 catch (ArgumentException) {
return false; }
7132 internal set => BrowserSettings.PuppeteerClient = value;
7142 public IRESTClient OttoMagicClient =>
MagicHelper.Client;
7149 public IHiddenDesktop
HiddenDesktop =>
true == BrowserSettings.HiddenDesktop
7152 #endregion <Getters>
7161 bool ScrollPage(
string direction)
7166 if (direction !=
"up" && direction !=
"down")
7173 (function(direction) {
7174 function scrollPage(direction) {
7176 const isDown = direction === 'down';
7177 const scrollAmount = isDown ? window.innerHeight : -window.innerHeight;
7178 const keyName = isDown ? 'PageDown' : 'PageUp';
7179 const keyCode = isDown ? 34 : 33;
7182 const overflow = getComputedStyle(document.documentElement).overflow;
7184 // Try window.scrollBy if overflow is not 'hidden'
7185 const initialScrollY = window.scrollY;
7186 if (overflow !== 'hidden') {
7187 window.scrollBy({ left: 0, top: scrollAmount, behavior: 'instant' });
7188 if (window.scrollY !== initialScrollY) {
7191 method: 'window.scrollBy',
7192 scrollPosition: window.scrollY,
7193 elementClass: 'window'
7198 // Try scrolling the scrollable element
7199 const scrollable = Array.from(document.querySelectorAll('*')).find(
7200 el => el.scrollHeight > el.clientHeight && getComputedStyle(el).overflowY !== 'hidden'
7202 const initialScrollTop = scrollable.scrollTop;
7203 scrollable.scrollBy({ left: 0, top: scrollAmount, behavior: 'instant' });
7204 const elementClass = scrollable.className || 'body';
7206 if (scrollable.scrollTop !== initialScrollTop) {
7209 method: 'element.scrollBy',
7210 scrollPosition: scrollable.scrollTop,
7215 // Fallback to Page Up/Down key press
7217 const event = new KeyboardEvent('keydown', {
7225 scrollable.dispatchEvent(event);
7227 if (scrollable.scrollTop !== initialScrollTop) {
7231 scrollPosition: scrollable.scrollTop,
7236 // No scroll method worked
7239 message: 'No scroll method worked',
7240 scrollPosition: scrollable.scrollTop,
7246 message: error.message,
7248 elementClass: 'none'
7252 return scrollPage(direction);
7253 })('" + direction +
"')";
7256 var result = BrowserHelper.ExecuteJavaScript(
this, script);
7259 dynamic response = result;
7260 return response.status ==
"success";
7273 private bool ScrollToPosition(
string position)
7278 if (position !=
"top" && position !=
"end")
7285 (function(position) {
7286 function scrollToPosition(position) {
7288 const isEnd = position === 'end';
7289 const scrollTarget = isEnd ? document.body.scrollHeight : 0;
7290 const keyName = isEnd ? 'End' : 'Home';
7291 const keyCode = isEnd ? 35 : 36;
7294 const overflow = getComputedStyle(document.documentElement).overflow;
7296 // Try window.scrollTo if overflow is not 'hidden'
7297 const initialScrollY = window.scrollY;
7298 if (overflow !== 'hidden') {
7299 window.scrollTo({ left: 0, top: scrollTarget, behavior: 'instant' });
7300 if (window.scrollY !== initialScrollY) {
7303 method: 'window.scrollTo',
7304 scrollPosition: window.scrollY,
7305 elementClass: 'window'
7310 // Try scrolling the scrollable element
7311 const scrollable = Array.from(document.querySelectorAll('*')).find(
7312 el => el.scrollHeight > el.clientHeight && getComputedStyle(el).overflowY !== 'hidden'
7314 const initialScrollTop = scrollable.scrollTop;
7315 const elementScrollTarget = isEnd ? scrollable.scrollHeight : 0;
7316 scrollable.scrollTo({ left: 0, top: elementScrollTarget, behavior: 'instant' });
7317 const elementClass = scrollable.className || 'body';
7319 if (scrollable.scrollTop !== initialScrollTop) {
7322 method: 'element.scrollTo',
7323 scrollPosition: scrollable.scrollTop,
7328 // Fallback to Home/End key press
7330 const event = new KeyboardEvent('keydown', {
7338 scrollable.dispatchEvent(event);
7340 if (scrollable.scrollTop !== initialScrollTop) {
7344 scrollPosition: scrollable.scrollTop,
7349 // No scroll method worked
7352 message: 'No scroll method worked',
7353 scrollPosition: scrollable.scrollTop,
7359 message: error.message,
7361 elementClass: 'none'
7365 return scrollToPosition(position);
7366 })('" + position +
"')";
7369 var result = BrowserHelper.ExecuteJavaScript(
this, script);
7372 dynamic response = result;
7373 return response.status ==
"success";
7385 private static void SelectorListMaintenanceSweep(UnitOfWork CurrentUOW)
7387 for (
int idx = 0; idx < CurrentUOW.WithSelectorList.Count; idx++)
7388 if (
true == CurrentUOW.WithSelectorList[idx].DeleteMe)
7389 CurrentUOW.WithSelectorList.RemoveAt(idx);
7391 for (
int idx = 0; idx < CurrentUOW.InSelectorList.Count; idx++)
7392 if (
true == CurrentUOW.InSelectorList[idx].DeleteMe)
7393 CurrentUOW.InSelectorList.RemoveAt(idx);
7403 private void CheckPersistentSelectors()
7405 UnitOfWork safeUOW = CurrentUOW;
7408 if (0 < (persistentUOW.WithSelectorList?.Count ?? 0))
7410 GPAL.PublishSimpleEvent(GPALEventType.INFO, $
"Checking [{persistentUOW.WithSelectorList?.Count}] persistent selector(s)");
7412 foreach (Selector selector
in persistentUOW.WithSelectorList)
7414 if (SelectorType.Selector != selector.SelectorType)
7418 selector.WebSelectorFoundResults =
null;
7419 selector.WebSelectorMatchedResults =
null;
7420 CurrentUOW.CurrentSelector = selector;
7423 if (
null != selector.selectorSettings.InFrame)
7427 ElementHelper.FindWebElements(
this, persistentUOW, selector.selectorSettings.InFrame, out _, out var iFrameMatches,
null,
false,
true);
7429 if (0 == (iFrameMatches?.Count ?? 0))
7435 MagicHelper.SwitchToElement(iFrameMatches[0].Css);
7439 BrowserDriver.SwitchTo().Frame((IWebElement)iFrameMatches[0].WebElement);
7441 ElementHelper.FindWebElements(
this, persistentUOW, selector, out _, out _,
null,
true,
true);
7453 if (0 < (CurrentUOW.ContextPath?.Count ?? 0))
7454 foreach (UnitOfWork.WebElementWithType contextType in CurrentUOW.ContextPath)
7456 if (ElementType.IFrame == contextType.elementType)
7459 MagicHelper.SwitchToElement(iFrameMatches[0].Css);
7463 BrowserDriver.SwitchTo().Frame((IWebElement)contextType.gPalElement.WebElement);
7465 else if (ElementType.ShadowRoot == contextType.elementType)
7468 MagicHelper.SwitchToElement(contextType.gPalElement.Css);
7470 PuppeteerClient.SwitchToShadowRoot(contextType.gPalElement.Css).Execute();
7477 ElementHelper.FindWebElements(
this, persistentUOW, selector, out _, out _,
null,
true,
true);
7480 CurrentUOW = safeUOW;
7486 private bool CheckWaitFor()
7488 bool matchFound =
true;
7489 List<GPALElement> foundElements =
null;
7494 if (
null != CurrentUOW.WaitForSatisfied)
7496 GPAL.PublishSimpleEvent(GPALEventType.DEBUG, $
"Selectors for this unit of work already found at [{CurrentUOW.WaitForSatisfied:HH:mm:ss.fff}], skipping redundant wait.",
this, GPALObjectType.Browser);
7502 if (WaitTime.Never < CurrentUOW.WaitForInMs)
7505 foundElements = ElementHelper.WaitFor(
this, CurrentUOW.WaitForInMs, out matchFound);
7507 if (0 < foundElements?.Count)
7508 CurrentUOW.WaitForSatisfied = DateTime.Now;
7511 return (0 < foundElements?.Count);
7521 internal bool WorkflowSetup(
bool checkWaitFor,
bool doMaintenance, GPALUrl URL =
null)
7527 BrowserSettings.HiddenDesktopReported =
true;
7536 GPAL.PublishSimpleEvent(GPALEventType.WARNING, $
"[{BrowserSettings.AutomationEngine}] cannot drive a hidden desktop, so this browser runs on [{steppedDownTo}]",
this, GPALObjectType.Browser);
7538 BrowserSettings.AutomationEngine = steppedDownTo;
7545 GPAL.PublishSimpleEvent(GPALEventType.WARNING, $
"No hidden desktop could be made, so this browser opens on the desktop you are working on",
this, GPALObjectType.Browser);
7547 BrowserSettings.HiddenDesktop =
false;
7578 BrowserSettings.Process = LaunchWhereAsked(() =>
MagicHelper.LaunchBrowser(
this, URL));
7580 BrowserSettings.Process = LaunchWhereAsked(() => Puppeteer.LaunchBrowser(
this, URL));
7588 BrowserSettings.HiddenDesktopChecked =
true;
7590 if (
false == Desktops.HasWindows(
BrowserSettings.HiddenDesktopName, 10_000))
7591 GPAL.PublishSimpleEvent(GPALEventType.WARNING, $
"Nothing arrived on [{BrowserSettings.HiddenDesktopName}], so this browser is on the desktop you are working on",
this, GPALObjectType.Browser);
7597 BrowserHelper.TopBrowser(
this,
false);
7599 CurrentUOW.ActionCalled =
true;
7601 if (
true == doMaintenance)
7603 SelectorListMaintenanceSweep(CurrentUOW);
7604 SelectorListMaintenanceSweep(persistentUOW);
7605 CheckPersistentSelectors();
7608 if (
true == checkWaitFor)
7613 BrowserSettings.PuppeteerUrl = $
"http://localhost:{BrowserSettings.DebugPort}";
7622 BrowserSettings.DownloadBehaviorSet =
true;
7628 ApplyPendingInjectedScripts();
7629 ArmDialogHandling();
7633 bool firstRun {
get;
set; } =
true;
7634 bool inAction =
false;
7647 internal bool InAction(
bool checkWaitFor,
bool doMaintenance =
true, GPALUrl URL =
null)
7650 if (
false == inAction)
7652 string saveURL = URL;
7663 retval = WorkflowSetup(checkWaitFor, doMaintenance, URL);
7695 private void BackOutOfSelfClosedTab(GPALUrl URL,
string priorUrl, GPALUrl priorGPALUrl, TabTuple priorTab,
string priorHandle)
7710 string landOn =
true == handles.Contains(priorHandle) ? priorHandle : handles[handles.Count - 1];
7716 BrowserSettings.CurrentURL = priorUrl;
7717 BrowserSettings.CurrentGPALUrl = priorGPALUrl;
7719 GPAL.PublishSimpleEvent(GPALEventType.INFO, $
"[{URL?.Url}] downloaded rather than opening a page, so its tab closed itself. Back on [{priorUrl}].",
this, GPALObjectType.Browser);
7730 internal int RealTabCount()
7745 private int GetActiveTabId()
7756 private void UpdateActiveTab(TabTuple activeTabTuple)
7758 string priorUrl =
null;
7759 if (
null != activeTabTuple)
7770 priorUrl = activeTabTuple.Url;
7771 BrowserSettings.TabIdsToURL.Add(activeTabTuple.TabId,
new TabInfo { URL = activeTabTuple.Url, ActiveTab = true });
7775 foreach (var key
in BrowserSettings.TabIdsToURL.Keys.Where(k => k != activeTabTuple.TabId))
7778 if (
null != activeTabTuple.Url)
7780 URLInfo uRLInfo =
new URLInfo { TabId = activeTabTuple.TabId, ActiveTab =
true };
7786 if (
false == priorUrl.Equals(activeTabTuple.Url))
7790 BrowserSettings.URLsToTabId.Add(activeTabTuple.Url,
new URLInfo { TabId = activeTabTuple.TabId, ActiveTab = true });
7793 foreach (var key
in BrowserSettings.URLsToTabId.Keys.Where(k => k != activeTabTuple.Url))
7796 BrowserSettings.CurrentURL = activeTabTuple.Url;
7801 GPAL.PublishSimpleEvent(GPALEventType.ERROR,
"ActiveTabTuple from last operation is null",
this, GPALObjectType.Browser);
7812 internal bool TryDecodeNumberToIpAddress(
string number, out
string ipAddress)
7814 bool retVal =
false;
7819 if (
true == UInt32.TryParse(number, out numberUInt))
7823 byte[] bytes = BitConverter.GetBytes(numberUInt);
7824 if (BitConverter.IsLittleEndian)
7825 Array.Reverse(bytes);
7830 var ip =
new System.Net.IPAddress(bytes);
7831 ipAddress = ip.ToString();
7834 var octets = ipAddress.Split(
'.');
7835 if (octets.Length != 4)
return false;
7836 if (octets.All(o =>
byte.TryParse(o, out
byte value) && value <= 255))
7840 var host = System.Net.Dns.GetHostEntry(ip);
7844 catch (GPALException)
7848 catch (Exception ex)
7850 GPAL.
PublishSimpleEvent(GPALEventType.WARNING, $
"[{number}][{ip}] doesn't resolve to a hostname.",
this, GPALObjectType.Browser, ex);
7855 catch (GPALException)
7859 catch (Exception ex)
7861 GPAL.PublishSimpleEvent(GPALEventType.EXCEPTION, $
"[{number}] doesn't resolve to an IP address",
this, GPALObjectType.Browser, ex);
7867 #endregion <Private>
7874 public void Close(
bool killWebDrivers =
false)
7876 InAction(
false,
false);
7879 BrowserLaunched =
null;
7884 if (
true ==
UseOttoMagic &&
true == _scriptsRegistered)
7892 BrowserSettings.DownloadBehaviorSet =
false;
7901 else if (1 == GPAL.Browsers.Count ||
true == killWebDrivers)
7904 BrowserSettings.PuppeteerCommunicator._isRunning =
false;
7907 BrowserHelper.KillAllRunningProcesses(killWebDrivers,
this);
7923 BrowserHelper.WaitForBrowserToExit(
BrowserSettings, ProfileRestoreWaitMs);
7925 BrowserSettings.PreviousFirefoxPreferences =
null;
7932 BrowserHelper.WaitForBrowserToExit(
BrowserSettings, ProfileRestoreWaitMs);
7934 BrowserSettings.PreviousDownloadPreferences =
null;
7942 BrowserSettings.Process =
null;
7944 GPAL.Browsers.Remove(
this);
7977 internal void ResolveInteractionType(Selector selector)
7979 InteractionType engineDefault =
true ==
UseOttoMagic ? InteractionType.OttoMagic
7981 : InteractionType.Selenium;
7982 InteractionType resolved = engineDefault;
7983 bool wantsHardware = InteractionType.Hardware == selector.RequestedInteractionType ||
true == BrowserSettings.UseHardware ||
true == GPAL.GPALSettings.UseHardware;
7985 if (
true == wantsHardware)
7988 resolved = InteractionType.Hardware;
7989 else if (InteractionType.Hardware == selector.RequestedInteractionType)
7990 GPAL.PublishSimpleEvent(GPALEventType.WARNING, $
"Selector [{selector.Name}] asked for hardware interaction, which a headless browser has no mouse or keyboard to perform. Using [{engineDefault}].",
this, GPALObjectType.Browser);
7992 else if (InteractionType.JavaScript == selector.RequestedInteractionType ||
true ==
BrowserSettings.UseJavaScript)
7994 resolved =
true ==
UseOttoMagic ? InteractionType.OttoMagic : InteractionType.JavaScript;
7995 else if (InteractionType.Selenium == selector.RequestedInteractionType &&
false ==
UseSelenium)
7996 GPAL.PublishSimpleEvent(GPALEventType.WARNING, $
"Selector [{selector.Name}] asked for selenium interaction, which the [{AutomationEngine}] engine has no driver for. Using [{engineDefault}].",
this, GPALObjectType.Browser);
7998 selector.InteractionType = resolved;
8006 if (
false == selector.RemovedMethods.Contains(func))
8007 selector.RemovedMethods.Add(func);
8010 if (
false == selector.RemovedMethods.Contains(func))
8011 selector.RemovedMethods.Add(func);
8014 if (
false == selector.RemovedMethods.Contains(func))
8015 selector.RemovedMethods.Add(func);
8049 return (function() {
8051 const overflow = getComputedStyle(document.documentElement).overflow;
8052 if (overflow !== 'hidden') {
8053 return (window.innerHeight + window.scrollY) >= document.body.scrollHeight;
8055 const scrollable = Array.from(document.querySelectorAll('*')).find(
8056 el => el.scrollHeight > el.clientHeight && getComputedStyle(el).overflowY !== 'hidden'
8058 return (scrollable.clientHeight + scrollable.scrollTop) >= scrollable.scrollHeight;
8080 #endregion <Helpers>
8081 #region Window Handling
8086 public IAllowBrowserActionOrAnySelector
NextWindow
8096 BrowserSettings.WindowIdsToURL[window.WindowId] =
new WindowInfo { URL = window.Url, ActiveWindow =
true };
8097 BrowserSettings.CurrentURL = window.Url;
8108 if (trackedWindows.Count <= 1)
8112 string currentHandle = trackedWindows.FirstOrDefault(h =>
8116 if (
string.IsNullOrEmpty(currentHandle))
8120 int currentIndex = trackedWindows.IndexOf(currentHandle);
8121 if (currentIndex == -1) currentIndex = 0;
8124 int nextIndex = (currentIndex + 1) % trackedWindows.Count;
8125 string nextHandle = trackedWindows[nextIndex];
8152 public IAllowBrowserActionOrAnySelector
OpenWindow(GPALUrl URL)
8156 string urlString =
MagicHelper.GetFullUrl(URL?.Url,
this, out _areRobotsAllowed);
8160 GPAL.PublishSimpleEvent(GPALEventType.ERROR, $
"Visiting [{URL?.Url}] is disallowed by robots.txt and your request to honor it. Not going to URL. Workflow will fail.",
this, GPALObjectType.Browser);
8161 URL.ForUrl(
"https://google.com");
8166 WindowTuple window =
MagicHelper.OpenWindow(urlString);
8181 BrowserSettings.CurrentURL = window.Url ?? urlString;
8187 BrowserSettings.CurrentURL = urlString;
8199 tabWindows[newHandle] = newHandle;
8219 URL = GetSetCurrentUrl();
8222 if (
true ==
BrowserSettings.RespectRobotMetaTags &&
false == UrlHelper.CheckRobotMetaTags(URL,
this))
8224 GPAL.
PublishSimpleEvent(GPALEventType.WARNING, $
"Visiting [{BrowserSettings.CurrentURL}] is disallowed by robot meta tags and your request to honor them..");
8225 URL.ForUrl(
"https://google.com");
8230 UpdateActiveTab(tabTuple);
8261 WindowTuple window =
MagicHelper.PreviousWindow();
8272 BrowserSettings.CurrentURL = window.Url;
8283 if (trackedWindows.Count <= 1)
8287 string currentHandle = trackedWindows.FirstOrDefault(h =>
8291 if (
string.IsNullOrEmpty(currentHandle))
8295 int currentIndex = trackedWindows.IndexOf(currentHandle);
8296 if (currentIndex == -1) currentIndex = 0;
8299 int previousIndex = (currentIndex - 1 + trackedWindows.Count) % trackedWindows.Count;
8300 string previousHandle = trackedWindows[previousIndex];
8327 public IAllowBrowserActionOrAnySelector
GoToWindow(dynamic urlOrId)
8332 string windowId =
null;
8333 WindowTuple window =
null;
8335 if (urlOrId is GPALUrl url)
8337 windowId =
BrowserSettings.WindowIdsToURL.FirstOrDefault(x => x.Value.URL.Contains(url.Url) || url.Url.Contains(x.Value.URL)).Key;
8338 BrowserSettings.CurrentGPALUrl = url;
8340 else if (urlOrId is
string urlStr)
8341 windowId =
BrowserSettings.WindowIdsToURL.FirstOrDefault(x => x.Value.URL.Contains(urlStr) || urlStr.Contains(x.Value.URL)).Key;
8343 if (
true ==
string.IsNullOrEmpty(windowId))
8351 if (
false ==
BrowserSettings.WindowIdsToURL.ContainsKey(window.WindowId))
8352 BrowserSettings.WindowIdsToURL[window.WindowId] =
new WindowInfo { URL = window.Url, ActiveWindow =
true };
8356 BrowserSettings.CurrentURL = window.Url;
8360 string windowId =
null;
8361 WindowTuple window =
null;
8363 if (urlOrId is GPALUrl url)
8365 windowId =
BrowserSettings.WindowIdsToURL.FirstOrDefault(x => x.Value.URL.Contains(url.Url) || url.Url.Contains(x.Value.URL)).Key;
8366 BrowserSettings.CurrentGPALUrl = url;
8368 else if (urlOrId is
string urlStr)
8369 windowId =
BrowserSettings.WindowIdsToURL.FirstOrDefault(x => x.Value.URL.Contains(urlStr) || urlStr.Contains(x.Value.URL)).Key;
8371 if (
true ==
string.IsNullOrEmpty(windowId))
8373 else if (
true == Int32.TryParse(windowId, out
int windowIdInt))
8374 window =
PuppeteerClient.GoToWindow(windowIdInt).Execute<WindowTuple>();
8379 if (
false ==
BrowserSettings.WindowIdsToURL.ContainsKey(window.WindowId))
8380 BrowserSettings.WindowIdsToURL[window.WindowId] =
new WindowInfo { URL = window.Url, ActiveWindow =
true };
8384 BrowserSettings.CurrentURL = window.Url;
8388 string theUrl =
null;
8389 string targetHandle =
null;
8391 if (urlOrId is GPALUrl url)
8397 if (urlOrId is
string str && (handle == str ||
BrowserDriver.Url.Contains(str)))
8399 targetHandle = handle;
8404 if (targetHandle !=
null)
8411 GPAL.
PublishSimpleEvent(GPALEventType.ERROR, $
"Failed to detect window with URL/ID [{urlOrId}]",
this, GPALObjectType.Browser);
8424 public IAllowBrowserActionOrAnySelector
CloseWindow(dynamic urlOrId =
null)
8427 urlOrId =
MagicHelper.GetFullUrl(urlOrId,
this, out
bool _);
8431 BrowserSettings.CurrentGPALUrl =
null;
8435 string windowIdToClose =
null;
8437 if (urlOrId is GPALUrl url)
8439 if (
false ==
string.IsNullOrEmpty(url.Url))
8441 windowIdToClose = BrowserSettings.WindowIdsToURL
8442 .FirstOrDefault(x => x.Value.URL.Contains(url.Url) || url.Url.Contains(x.Value.URL))
8445 else if (urlOrId is
string urlStr)
8447 if (
false ==
string.IsNullOrEmpty(urlStr))
8449 windowIdToClose = BrowserSettings.WindowIdsToURL
8450 .FirstOrDefault(x => x.Value.URL.Contains(urlStr) || urlStr.Contains(x.Value.URL))
8456 windowIdToClose = BrowserSettings.WindowIdsToURL
8457 .FirstOrDefault(x =>
true == x.Value.ActiveWindow)
8461 if (
string.IsNullOrEmpty(windowIdToClose))
8463 GPAL.PublishSimpleEvent(GPALEventType.ERROR,
8464 $
"No window found to close (URL containing [{urlOrId}])",
this, GPALObjectType.Browser);
8469 WindowTuple newActiveWindow =
MagicHelper.CloseWindow(windowIdToClose);
8475 if (newActiveWindow !=
null && !
string.IsNullOrEmpty(newActiveWindow.WindowId))
8480 BrowserSettings.WindowIdsToURL[newActiveWindow.WindowId] =
new WindowInfo
8482 URL = newActiveWindow.Url,
8486 BrowserSettings.CurrentURL = newActiveWindow.Url;
8490 BrowserSettings.CurrentURL =
null;
8496 if (urlOrId is GPALUrl url)
8498 else if (urlOrId is
string urlStr)
8500 else if (urlOrId is
int id)
8507 string handleToClose =
null;
8509 if (urlOrId is
string urlStr)
8512 handleToClose = BrowserSettings.WindowIdsToURL
8513 .FirstOrDefault(x => x.Value.URL.Contains(urlStr) || urlStr.Contains(x.Value.URL))
8519 handleToClose = BrowserSettings.WindowIdsToURL
8520 .FirstOrDefault(x => x.Value.ActiveWindow)
8524 if (
string.IsNullOrEmpty(handleToClose))
8528 if (
string.IsNullOrEmpty(handleToClose) ||
8531 GPAL.PublishSimpleEvent(GPALEventType.ERROR,
8532 $
"No window found to close (URL containing [{urlOrId}])",
this, GPALObjectType.Browser);
8546 GPAL.PublishSimpleEvent(GPALEventType.ERROR,
"No windows remain after closing.",
this, GPALObjectType.Browser);
8565 var firstRemaining =
BrowserSettings.WindowIdsToURL.Keys.FirstOrDefault();
8566 if (firstRemaining !=
null)
8578 #endregion Window Handling
8579 #region WithWorkFlow, Run, While, Until, Generated by Grok
8585 public IAllowBrowserActionOrAnySelector
Run()
8587 WorkflowManager.Run();
8624 return WorkflowManager.WithWorkflow(workflow);
8636 GPAL.
PublishSimpleEvent(GPALEventType.WARNING, $
"Invalid timeout ([{timeoutMs}]ms) specified. Using default [{BrowserSettings.WhileLoopTimeoutMs}]ms.",
this, GPALObjectType.Browser);
8640 BrowserSettings.WhileLoopTimeoutMs = timeoutMs;
8652 if (maxIterations <= 0)
8654 GPAL.
PublishSimpleEvent(GPALEventType.WARNING, $
"Invalid max iterations ([{maxIterations}]) specified. Using default [{BrowserSettings.WhileLoopMaxIterations}].",
this, GPALObjectType.Browser);
8658 BrowserSettings.WhileLoopMaxIterations = maxIterations;
8671 return WorkflowManager.While(predicate);
8682 return WorkflowManager.Until(selector);
8693 return WorkflowManager.Until(predicate);
8695 #endregion WithWorkFlow, Run, While, Until, Generated by Grok
8698 #region Getters/Setters
8705 internal set => BrowserSettings.RobotsTxt = value;
8713 internal set => BrowserSettings.JavaScriptResultObj = value;
8721 internal set => BrowserSettings.JavaScriptResultStr = value;
8729 internal set => BrowserSettings.Version = value;
8739 internal static string GetModifierKeys(ModifierKeys modifierKeys)
8741 if (modifierKeys == ModifierKeys.NONE)
8744 var setFlags = Enum.GetValues(typeof(ModifierKeys))
8745 .Cast<ModifierKeys>()
8746 .Where(flag => flag != ModifierKeys.NONE && modifierKeys.HasFlag(flag))
8747 .Select(flag => flag.ToString());
8749 return string.Join(
", ", setFlags);
8756 internal string GetSetCurrentUrl()
8758 string currentUrl =
null;
8765 currentUrl =
BrowserSettings.Browser.PuppeteerClient.GetCurrentUrl().Execute<dynamic>();
8771 currentUrl = currentUrl?.Trim(
'"');
8773 if (
false == UrlHelper.AreEquivalent(currentUrl,
BrowserSettings.CurrentURL))
8775 BrowserSettings.CurrentURL = currentUrl;
8778 foreach (Selector selector
in this.CurrentUOW.WithSelectorList)
8780 selector.WebSelectorFoundResults =
null;
8781 selector.WebSelectorMatchedResults =
null;
8785 catch (GPALException)
8789 catch (Exception ex)
8791 GPAL.PublishSimpleEvent(GPALEventType.EXCEPTION, $
"Unable to get URL, returning [{BrowserSettings.CurrentURL}]",
this, GPALObjectType.Browser, ex);
8799 internal void SetReferrer()
8801 string message = $
"[{BrowserSettings.Referrer}] referrer applied.";
8802 var parms =
new Dictionary<string, object>
8805 "headers",
new Dictionary<string, object>
8807 {
"Referer", BrowserSettings.Referrer }
8819 ((OpenQA.Selenium.Chromium.ChromiumDriver)
BrowserDriver).ExecuteCdpCommand(
"Network.setExtraHTTPHeaders", parms);
8824 BrowserSettings.Referrer =
null;
8825 message = $
"Cleared referrer.";
8828 GPAL.PublishSimpleEvent(GPALEventType.INFO, message,
this, GPALObjectType.Browser);
8835 internal void SetUserAgent()
8837 string message = $
"[{BrowserSettings.OverrideUserAgent}] user agent applied.";
8838 var parms =
new Dictionary<string, object> { {
"userAgent", BrowserSettings.OverrideUserAgent } };
8847 ((OpenQA.Selenium.Chromium.ChromiumDriver)
BrowserDriver).ExecuteCdpCommand(
"Network.setUserAgentOverride", parms);
8852 BrowserSettings.OverrideUserAgent =
null;
8853 message = $
"Cleared user agent override.";
8860 #region Sitep Operations
8866 public IAllowBrowserActionOrAnySelector
GetHydratedData(out NextJsHydrationResult hydrationResult)
8868 hydrationResult = CachedHydration();
8876 private NextJsHydrationResult CachedHydration()
8878 string key = GetSetCurrentUrl();
8880 if (
false ==
string.IsNullOrEmpty(key) &&
true == _hydrationCache.TryGetValue(key, out NextJsHydrationResult cached))
8882 GPAL.PublishSimpleEvent(GPALEventType.DEBUG, $
"Hydrated data cache hit for [{UrlHelper.GetOrigin(CurrentUrl)}]",
this, GPALObjectType.Browser);
8886 var result = NextJsHydrationExtractor.Extract(
this, GetHydrationSource());
8888 if (
false ==
string.IsNullOrEmpty(key))
8889 _hydrationCache[key] = result;
8901 private string GetHydrationSource()
8903 string raw =
string.Empty;
8916 catch (Exception ex)
8918 GPAL.
PublishSimpleEvent(GPALEventType.ERROR,
"Failed to read PageSource for hydration [" + ex.Message +
"]",
this, GPALObjectType.Browser);
8922 if (
string.IsNullOrWhiteSpace(raw))
8923 return string.Empty;
8925 string peeled = JsonUnescapeToStable(raw.Trim().Trim(
'\"').Trim(
'\''));
8927 GPAL.
PublishSimpleEvent(GPALEventType.INFO, $
"Hydrated data created for [{UrlHelper.GetOrigin(CurrentUrl)}]",
this, GPALObjectType.Browser);
8929 return Regex.Replace(peeled,
@"[\x00-\x08\x0B\x0C\x0E-\x1F\x7F]",
"");
8939 NextJsHydrationExtractor.SaveToFile(CachedHydration(), hydrationFile.
Filename);
8941 GPAL.
PublishSimpleEvent(GPALEventType.INFO, $
"Saved hydrated data [{hydrationFile.Filename}]",
this, GPALObjectType.Browser);
8956 digestResult = CachedDigest(ruleSetName);
8966 string key = GetSetCurrentUrl() +
"|" + (ruleSetName ??
string.Empty);
8968 if (
true == _digestCache.TryGetValue(key, out
LLMDigestResult cached))
8970 GPAL.
PublishSimpleEvent(GPALEventType.DEBUG, $
"LLM Digest cache hit for [{UrlHelper.GetOrigin(CurrentUrl)}]",
this, GPALObjectType.Browser);
8975 LLMDigestResult digestResult = HtmlToOptimizedMarkdown.CreateDigest(
this, pageSource, ruleSetName);
8976 _digestCache[key] = digestResult;
8978 GPAL.
PublishSimpleEvent(GPALEventType.INFO, $
"LLM Digest created for [{UrlHelper.GetOrigin(CurrentUrl)}]",
this, GPALObjectType.Browser);
8980 return digestResult;
8993 HtmlToOptimizedMarkdown.SaveToFile(CachedDigest(ruleSetName), llmDigestFile.
Filename);
8995 GPAL.
PublishSimpleEvent(GPALEventType.INFO, $
"Saved LLM Digest [{llmDigestFile.Filename}]",
this, GPALObjectType.Browser);
9004 public IAllowBrowserActionOrAnySelector
GetPageSource(out
string pageSource)
9006 pageSource =
string.Empty;
9022 catch (GPALException)
9026 catch (Exception ex)
9028 GPAL.PublishSimpleEvent(
9029 GPALEventType.ERROR,
9030 "Failed to read page source in Selenium [" + ex.Message +
"]",
9032 GPALObjectType.Browser);
9036 pageSource = NormalizePageSource(pageSource);
9038 Uri currUrl =
new Uri(
CurrentUrl, UriKind.Absolute);
9040 GPAL.PublishSimpleEvent(GPALEventType.INFO, $
"Retrieved page source for [{UrlHelper.NormalizePath(currUrl.AbsolutePath)}]",
this, GPALObjectType.Browser);
9052 private static string NormalizePageSource(
string raw)
9054 if (
string.IsNullOrWhiteSpace(raw))
9055 return string.Empty;
9059 string working = SafeJsUnescape(raw.Trim().Trim(
'\"').Trim(
'\''));
9061 if (working.IndexOf(
"webkit-xml-viewer-source-xml", StringComparison.Ordinal) != -1)
9063 string fileSource = ConverterHelper.ExtractRawXmlFromBrowserSource(working);
9064 if (!
string.IsNullOrWhiteSpace(fileSource))
9068 return Regex.Replace(fileSource.Trim(),
@"[\x00-\x08\x0B\x0C\x0E-\x1F\x7F]",
"");
9073 return SafeCleanPageSource(raw);
9080 private static string SafeCleanPageSource(
string input)
9082 if (
string.IsNullOrWhiteSpace(input))
9083 return string.Empty;
9085 string working = input.Trim().Trim(
'\"').Trim(
'\'');
9090 working = SafeJsUnescape(working);
9092 working = System.Web.HttpUtility.HtmlDecode(working);
9095 working = Regex.Replace(working,
@"[\x00-\x08\x0B\x0C\x0E-\x1F\x7F]",
"");
9097 return working.Trim();
9105 private static string SafeJsUnescape(
string input)
9107 if (
string.IsNullOrEmpty(input))
9116 string result = JsonUnescapeToStable(input);
9124 result = result.Replace(
@"\""",
"\"")
9125 .Replace(
@"\'",
"'")
9126 .Replace(
@"\\",
"\\")
9127 .Replace(
@"\/",
"/");
9130 result = Regex.Replace(result,
@"\\u([0-9a-fA-F]{4})", m =>
9134 int code =
int.Parse(m.Groups[1].Value, System.Globalization.NumberStyles.HexNumber);
9135 if (code < 0 || code > 0x10FFFF || (code >= 0xD800 && code <= 0xDFFF))
9137 return char.ConvertFromUtf32(code);
9146 result = Regex.Replace(result,
@"\\([^""'\\/bfnrtu])",
"$1");
9159 private static string JsonUnescapeToStable(
string input)
9161 string current = input;
9164 for (
int pass = 0; pass < 5; pass++)
9166 string next = TryJsonStringDecode(current);
9167 if (
null == next || next == current)
9183 private static string TryJsonStringDecode(
string s)
9185 if (
string.IsNullOrEmpty(s))
9190 return JsonConvert.DeserializeObject(
"\"" + s +
"\"") as string;
9209 Dictionary<string, List<string>> siteMaps =
new Dictionary<string, List<string>>();
9210 List<string> suggestedSitemaps =
new List<string>();
9216 private readonly Dictionary<string, List<string>> _sitemapCache =
new Dictionary<string, List<string>>(StringComparer.OrdinalIgnoreCase);
9217 private readonly Dictionary<string, LLMDigestResult> _digestCache =
new Dictionary<string, LLMDigestResult>(StringComparer.OrdinalIgnoreCase);
9218 private readonly Dictionary<string, NextJsHydrationResult> _hydrationCache =
new Dictionary<string, NextJsHydrationResult>(StringComparer.OrdinalIgnoreCase);
9227 _sitemapCache.Clear();
9228 _digestCache.Clear();
9229 _hydrationCache.Clear();
9230 GPAL.
PublishSimpleEvent(GPALEventType.INFO,
"Cleared sitemap/digest/hydration result cache.",
this, GPALObjectType.Browser);
9242 sitemapUrls = CachedSitemapUrls();
9250 private List<string> CachedSitemapUrls()
9252 string origin = UrlHelper.GetOrigin(
CurrentUrl);
9254 if (
false ==
string.IsNullOrEmpty(origin) &&
true == _sitemapCache.TryGetValue(origin, out List<string> cached))
9256 GPAL.
PublishSimpleEvent(GPALEventType.DEBUG, $
"Sitemap cache hit for [{origin}] ([{cached.Count}] URLs).",
this, GPALObjectType.Browser);
9260 List<string> walked = WalkSitemapUrls();
9262 if (
false ==
string.IsNullOrEmpty(origin))
9263 _sitemapCache[origin] = walked;
9268 private List<string> WalkSitemapUrls()
9270 List<string> sitemapUrls =
new List<string>();
9272 bool isFromRobotsTxt =
false;
9273 SitemapExtractor sme =
new SitemapExtractor(
this);
9274 List<string> potentialSitemapUrls = sme.GetSitemapUrl(currentUrl, out isFromRobotsTxt);
9275 bool urlIsSitemap =
false;
9276 IGPALGrid<string> retGrid =
new GPALGrid<string>();
9278 string pageSource =
string.Empty;
9282 string potentialSitemapUrl =
string.Empty;
9283 if (idx < potentialSitemapUrls.Count)
9284 potentialSitemapUrl = potentialSitemapUrls[idx++];
9289 if (
false == isFromRobotsTxt)
9292 if (
true == SitemapExtractor.IsGzipSitemapUrl(currentUrl))
9293 pageSource = SitemapExtractor.FetchAndInflateGzipSitemap(currentUrl,
this);
9301 if (
true == isFromRobotsTxt ||
9302 (
false == currentUrl.ToLower().Contains(
"sitemap") &&
9303 false == currentUrl.ToLower().EndsWith(
".xml") &&
9304 false == SitemapExtractor.IsValidSitemapContent(pageSource) &&
9305 false == suggestedSitemaps.Contains(potentialSitemapUrl)
9310 if (
true == SitemapExtractor.IsGzipSitemapUrl(potentialSitemapUrl))
9311 pageSource = SitemapExtractor.FetchAndInflateGzipSitemap(potentialSitemapUrl,
this);
9314 GoTo(potentialSitemapUrl);
9318 if (
false == suggestedSitemaps.Contains(potentialSitemapUrl))
9319 suggestedSitemaps.Add(potentialSitemapUrl);
9323 if (
true == SitemapExtractor.IsBotCheckPage(pageSource))
9325 GPAL.PublishSimpleEvent(GPALEventType.WARNING, $
"Bot-check interstitial detected at [{currentUrl}], skipping sitemap walk.",
this, GPALObjectType.Browser);
9329 sitemapUrls = sme.ExtractSitemapUrls(pageSource, Int32.MaxValue, out urlIsSitemap);
9330 retGrid.AddRow(sitemapUrls);
9331 string currentUrl2 =
string.Empty;
9340 currentUrl2 = currentUrl2.Trim(
'\"');
9345 if (0 == potentialSitemapUrls.Count || potentialSitemapUrls.Count == idx)
9347 if (
false == UrlHelper.AreEquivalent(currentUrl, currentUrl2))
9354 sitemapUrls = retGrid.ToList();
9367 List<string> sitemapUrls = CachedSitemapUrls();
9370 IGPALGrid<string> grid =
new GPALGrid<string>();
9371 foreach (
string url
in sitemapUrls)
9372 grid.AddRow(
new List<string> { url });
9376 .WithColumnName(
"url")
9377 .SaveTo(sitemapUrlsFile);
9379 GPAL.
PublishSimpleEvent(GPALEventType.INFO, $
"Saved [{sitemapUrls.Count}] sitemap URLs to [{sitemapUrlsFile.Filename}]",
this, GPALObjectType.Browser);
9383 #endregion Sitemap Operations
9384 #region Cast Operations
9388 private string SinkName {
get;
set; }
9398 SinkName = sinkName;
9404 CastTo_Selenium(sinkName, CastMode.Tab);
9419 SinkName = sinkName;
9425 CastTo_Selenium(sinkName, CastMode.Desktop);
9436 public IAllowBrowserActionOrAnySelector
StopCasting()
9438 if (
false ==
string.IsNullOrEmpty(SinkName))
9447 PuppeteerCommunicator.SinkName =
null;
9452 var driver = (OpenQA.Selenium.Chromium.ChromiumDriver)
BrowserDriver;
9454 var param =
new Dictionary<string, object> { [
"sinkName"] = SinkName };
9456 if (
null != SinkName)
9460 driver.ExecuteCdpCommand(
"Cast.stopCasting", param);
9463 catch (Exception ex)
9465 GPAL.PublishSimpleEvent(GPALEventType.DEBUG, $
"Cast.stopCasting failed, which it does sometimes",
this, GPALObjectType.Browser, ex);
9479 private IAllowBrowserActionOrAnySelector CastTo_Selenium(
string sinkName, CastMode mode)
9483 if (
string.IsNullOrEmpty(sinkName))
9486 ((OpenQA.Selenium.Chromium.ChromiumDriver)
BrowserDriver).ExecuteCdpCommand(
"Cast.enable",
9487 new Dictionary<string, object>());