GPAL - Generally Positive Automation Library v1.0
GPAL The Fluent Automation LIbrary
Loading...
Searching...
No Matches
GPAL.cs
1// =============================================================================
2// GPAL - Generally Positive Automation Library
3// Copyright © 2026 Software Decisions, Inc. All rights reserved.
4//
5// This file is part of GPAL.
6// Licensed under the Business Source License 1.1
7//
8// Primary development, architecture, and vision by Michael B. Vederman,
9// CEO of Software Decisions, Inc., Texas.
10//
11// Internal development maintained privately.
12// Public releases appear on GitHub: https://github.com/SoftwareDecisionsInc/GPAL.
13//
14// See LICENSE for full terms, including Additional Use Grant.
15// =============================================================================
16
17using DocumentFormat.OpenXml.Office.CustomUI;
21using OpenQA.Selenium;
22using OpenQA.Selenium.Remote;
23using System;
24using System.Collections;
25using System.Collections.Generic;
26using System.Data.SqlClient;
27using System.Diagnostics;
28using System.Drawing;
29using System.Drawing.Imaging;
30using System.IO;
31using System.Linq;
32using System.Net.Http;
33using System.Runtime.CompilerServices;
34using System.Text;
35using System.Threading.Tasks;
36using System.Windows.Automation;
37using System.Windows.Forms;
39using static GenerallyPositive.Enums;
41
42namespace GenerallyPositive
43{
48 public class GPAL
49 {
50 private static Stopwatch _appTimer;
51 private static DateTime _startTime;
52
53 #region <Virtual Keys>
58 public static byte VK_ALT { [Keys(Description = "Virtual key code for Alt, for use with keybd_event")] get { return 0x12; } }
62 public static byte VK_DELETE { [Keys(Description = "Virtual key code for Delete, for use with keybd_event")] get { return 0x2e; } }
66 public static byte VK_F10 { [Keys(Description = "Virtual key code for F10, for use with keybd_event")] get { return 0x79; } }
70 public static byte VK_PRIOR { [Keys(Description = "Virtual key code for Page Up, for use with keybd_event")] get { return 0x21; } } // page up - to scroll up a page
74 public static byte VK_NEXT { [Keys(Description = "Virtual key code for Page Down, for use with keybd_event")] get { return 0x22; } } // page down - to scroll down a page
78 public static byte VK_END { [Keys(Description = "Virtual key code for End, for use with keybd_event")] get { return 0x23; } } // end key
82 public static byte VK_HOME { [Keys(Description = "Virtual key code for Home, for use with keybd_event")] get { return 0x24; } } // home key
86 public static byte VK_DOWN { [Keys(Description = "Virtual key code for the Down Arrow, for use with keybd_event")] get { return 0x28; } } // down arrow
90 public static byte VK_UP { [Keys(Description = "Virtual key code for the Up Arrow, for use with keybd_event")] get { return 0x26; } } // up arrow
94 public static byte VK_TAB { [Keys(Description = "Virtual key code for Tab, for use with keybd_event")] get { return 0x09; } } // tab
98 public static byte VK_RETURN { [Keys(Description = "Virtual key code for Enter, for use with keybd_event")] get { return 0x0D; } } // enter
102 public static byte VK_SHIFT_RIGHT { [Keys(Description = "Virtual key code for the right Shift, for use with keybd_event")] get { return 0xA1; } }
106 public static byte VK_SHIFT_LEFT { [Keys(Description = "Virtual key code for the left Shift, for use with keybd_event")] get { return 0xA0; } }
110 public static byte VK_CONTROL_LEFT { [Keys(Description = "Virtual key code for the left Control, for use with keybd_event")] get { return 0xA2; } }
114 public static byte VK_CONTROL_RIGHT { [Keys(Description = "Virtual key code for the right Control, for use with keybd_event")] get { return 0xA3; } }
118 public static byte VK_LWIN { [Keys(Description = "Virtual key code for the left Windows key, for use with keybd_event")] get { return 0x5B; } }
122 public static byte VK_RWIN { [Keys(Description = "Virtual key code for the right Windows key, for use with keybd_event")] get { return 0x5C; } }
126 public static byte VK_SCROLL { [Keys(Description = "Scroll Lock key virtual keycode (VK) for use with keybd_event")] get { return 0x91; } }
130 public static byte VK_LMENU { [Keys(Description = "Left Alt key virtual keycode (VK) for use with keybd_event")] get { return 0xA4; } }
134 public static byte VK_RMENU { [Keys(Description = "Right Alt key virtual keycode (VK) for use with keybd_event")] get { return 0xA5; } }
138 public static byte VK_APPS { [Keys(Description = "Application (Menu) key virtual keycode (VK) for use with keybd_event")] get { return 0x5D; } }
142 public static byte VK_SPACE { [Keys(Description = "Virtual key code for the space bar, for use with keybd_event")] get { return 0x20; } }
146 public static byte VK_BACK { [Keys(Description = "Virtual key code for Backspace, for use with keybd_event")] get { return 0x08; } }
160 [Keys(Description = "keybd_event flag marking an extended key, such as the right-hand Alt and Control")]
161 public static uint KEYEVENTF_EXTENDEDKEY { [Keys(Description = "keybd_event flag marking an extended key, such as the right-hand Alt and Control")] get { return 0x0001; } }
171 public static uint KEYEVENTF_KEYUP { [Keys(Description = "keybd_event flag that sends the release of a key rather than the press")] get { return 0x0002; } }
172 #endregion <Virtual Keys>
173 #region <Working Variables>
174 private static List<Application.Application> ApplicationList { get; } = new List<Application.Application>();
175 private static List<Browser.Browser> BrowserList { get; } = new List<Browser.Browser>();
176 private static List<Selector> SelectorList { get; } = new List<Selector>();
177 private static List<GPALForm.GPALForm> FormList { get; } = new List<GPALForm.GPALForm>();
178 private static List<IGPALAI> AIList { get; } = new List<IGPALAI>();
179 private static List<GPALFile> FileList { get; } = new List<GPALFile>();
180 private static List<IGPALMail> MailList { get; } = new List<IGPALMail>();
181 public static List<IGPALLogger> LoggerList { get; } = new List<IGPALLogger>();
182 private static List<GPALControl> ControlList { get; } = new List<GPALControl>();
183 private static List<IGPALGrid<string>> GridList { get; } = new List<IGPALGrid<string>>();
184
185 private static bool SettingsLoaded
186 {
187 get
188 {
189 return gpalSettings.SettingsLoaded;
190 }
191
192 set
193 {
194 gpalSettings.SettingsLoaded = value;
195 }
196 }
197 // deep copy cannot copy to properties
198 private static GPALSettings gpalSettings = new GPALSettings(true); // this will be initialized if we can't load from GPAL.yaml
199 private static string _settingsFilePath = "GPAL.yaml";
200 public static GPALSettings GPALSettings
201 {
202 [GPALSettings(Description = "The global settings GPAL is running with")] get
203 {
204 return gpalSettings;
205 }
206 set
207 {
208 gpalSettings = value;
209 }
210 }
211 private static Browser.ElementAssistant elementAssistant { get; } = new Browser.ElementAssistant();
212 private static Application.ElementAssistant applicationElementAssistant { get; } = new Application.ElementAssistant();
213
214 private static Browser.Browser tmpBrowser { get; set; } = null;
215 private static Application.Application tmpApplication { get; set; } = null;
216 private static bool UncaughtExceptionHandlerSet { get; set; } = false;
217
218 private static int applicationCount = 1;
219 private static int browserCount = 1;
220 private static int selectorCount = 1;
221 private static int formCount = 1;
222 private static int controlCount = 1; // will not count each type of control (but should we?)
223 private static int executableCount = 1;
224 private static int restClientCount = 1;
225
226 private static readonly string applicationName = "Application";
227 private static readonly string browserName = "Browser";
228 private static readonly string chartName = "Chart";
229 private static readonly string selectorName = "Selector";
230 private static readonly string formName = "Form";
231 private static readonly string buttonName = "Button";
232 private static readonly string checkboxName = "Checkbox";
233 private static readonly string inputName = "Input";
234 private static readonly string labelName = "Label";
235 private static readonly string linkLabelName = "LinkLabel";
236 private static readonly string spacerName = "Spacer";
237 private static readonly string radioButtonName = "Radiobutton";
238 private static readonly string textAreaName = "TextArea";
239 private static readonly string richTextBoxName = "RichTextBox";
240 private static readonly string executableName = "Executable";
241 private static readonly string restClientName = "RESTClient";
242
243 private static bool inPublishEvent = false;
244 // which loggers are part way through a write on this thread, so one can be kept from receiving what it
245 // raised while writing without keeping it from the others
246 [ThreadStatic] private static HashSet<IGPALLogger> loggersWriting;
247 #endregion <Working Variables>
248 #region Constructor
249 static bool inStaticInit = false;
250 // Private constructor to prevent instantiation
251 private GPAL() { }
257 static GPAL()
258 {
259 if (false == inStaticInit)
260 {
261 inStaticInit = true;
262
263 // Initialize timer
264 _startTime = DateTime.Now;
265 _appTimer = Stopwatch.StartNew();
266
267 GPAL.LoadSettings();
268
269 if (ConsoleEvents.HasFlag(GPALEventType.INFO))
270 {
271 PublishSimpleEvent(GPALEventType.INFO, $">>> GPAL v[{GPAL.Version}]");
272 PublishSimpleEvent(GPALEventType.INFO, $">>> Application Start Time: [{_startTime.ToString("yyyy-MM-dd HH:mm:ss")}] <<<");
273 }
274 else if (ConsoleEvents.HasFlag(GPALEventType.NOTICE))
275 {
276 PublishSimpleEvent(GPALEventType.NOTICE, $">>> GPAL v[{GPAL.Version}]");
277 PublishSimpleEvent(GPALEventType.NOTICE, $">>> Application Start Time: [{_startTime.ToString("yyyy-MM-dd HH:mm:ss")}] <<<");
278 }
279
280 SetCatchUncaughtExceptions();
281
282 inStaticInit = false;
283 }
284 }
285 #endregion Constructor
286 #region <Exception & Information EventHandler>
294 public class GPALEventArgs : System.EventArgs
295 {
296 public Enums.GPALEventType GPALEventType { get; set; }
300 public dynamic GPALObject { get; set; }
304 public Enums.GPALObjectType GPALObjectType { get; set; }
309 public UnitOfWork CurrentUOW { get; set; }
313 public string Message { get; set; }
317 public Exception ExceptionRaised { get; set; }
321 public Image ScreenShot { get; set; }
325 public IWebDriver BrowserDriver { get; set; }
329 public Selector Selector { get; set; }
333 public GPALElement GPALElement { get; set; }
337 public List<GPALElement> WebElements { get; set; }
345 public List<GPALAutomationElement> AutomationElements { get; set; }
354 public IGPALGrid<string> Tokens { get; set; }
358 public List<IGPALGrid<string>> TokenList { get; set; }
362 public SqlCommand SqlCommand { get; set; }
366 public dynamic Data { get; set; }
370 public DateTime DateTimeStamp { get; set; }
371 }
372
373 internal static EventHandler<GPALEventArgs> ExceptionEventHandler { get; set; }
374 internal static EventHandler<GPALEventArgs> InformationEventHandler { get; set; }
375 #endregion <Exception & Information EventHandler>
376 #region <Globals>
396 internal static EventHandler<GPALEventArgs> ExceptionHandler
397 {
398 get
399 {
400 return ExceptionEventHandler;
401 }
402 }
423 internal static EventHandler<GPALEventArgs> InformationHandler
424 {
425 get
426 {
427 return InformationEventHandler;
428 }
429 }
430 #endregion <Globals>
431 #region <GPAL Object Getters>
432 public static Cryptography Cryptography
433 {
434 [Component(Description = "Encrypt, decrypt, hash, sign and verify data", Submenu = "Security")] get => new Cryptography();
435 }
436 public static Base64Helper Base64Helper
437 {
438 [Component(Description = "Encode and decode Base64", Submenu = "Security")] get => new Base64Helper();
439 }
460 public static IAllowAIProvider AI
461 {
462 [Component(Description = "Entry point for the GPAL AI subsystem, providing a fluent interface for AI tasks", Submenu = "Services")] get
463 {
464 GPALAI ai = new GPALAI();
465 AIList.Add(ai);
466 return ai;
467 }
468 }
469
470 public static IGPALYouTube YouTube { [Component(Description = "Upload videos to YouTube, manage playlists and search", Submenu = "Google")] get => new Browser.GPALYouTube(); }
471
472 // OCR is on the roadmap: it registers the same way GPAL.Image does, and is left here until then
473 //private static Func<OCRInterfaces.IGPALOCR> _ocrFactory;
474 //[Action(Description = "Register the OCR provider from the GenerallyPositive.OCR package")]
475 //public static void RegisterOCR(Func<OCRInterfaces.IGPALOCR> factory) => _ocrFactory = factory;
476 //public static OCRInterfaces.IGPALOCR OCR { [Component(Description = "Read text from an image, once an OCR provider is registered", Submenu = "System")] get => _ocrFactory != null
477 // ? _ocrFactory()
478 // : throw new InvalidOperationException("OCR provider not registered. Add the GenerallyPositive.OCR NuGet and call GPAL.RegisterOCR(() => new GPALOCR())."); }
479
480 // what finds an image on the screen, handed in by the GPAL.Image package. Without it an image selector
481 // finds nothing and says why, so GPAL itself carries none of OpenCV's weight
482 internal static IImageMatcher ImageMatcher { get; private set; }
483
484 [Action(Description = "Register the image matcher from the GPAL.Image package, which image selectors need")]
485 public static void RegisterImageMatcher(IImageMatcher imageMatcher) => ImageMatcher = imageMatcher;
486
487 internal static ImageHelper imageHelper;
488 public static IImageHelper ImageHelper
489 {
490 [Component(Description = "Screenshots and image matching", Submenu = "System")] get
491 {
492 if (null == imageHelper)
493 imageHelper = new ImageHelper();
494 return imageHelper;
495 }
496 }
497
518 public static IAllowFileName File
519 {
520 [Component(Description = "A file, or several, for a step to read from or write to, such as FillInFrom or SaveTo", Submenu = "Data")]
521 get
522 {
523 GPALFile file = new GPALFile();
524 FileList.Add(file);
525 return (IAllowFileName)file;
526 }
527 }
528
531 public static IAllowGridActions<string> Grid
532 {
533 [Component(Description = "A grid of rows and columns that steps fill in from and save data into", Submenu = "Data")]
534 get
535 {
536 IGPALGrid<string> grid = new GPALGrid<string>();
537 GridList.Add(grid);
538 return grid;
539 }
540 }
541
545 {
546 [Component(Description = "A logger that records the events GPAL publishes", Submenu = "System")]
547 get
548 {
549 IGPALLogger logger = (IGPALLogger)new GPALLogger();
550 LoggerList.Add(logger);
551 return (IAllowLoggingSettings)logger;
552 }
553 }
554
558 {
559 [Component(Description = "Send and receive email over SMTP, POP or IMAP", Submenu = "Services")]
560 get
561 {
562 GPALMail mail = new GPALMail();
563 MailList.Add(mail);
564 return (IAllowEmailServerSettings)mail;
565 }
566 }
567
571 {
572 [Fluent(Description = "Convert data from one shape to another: file, grid, database, class, and the CSV, JSON, XML, YAML and Excel formats")]
573 get
574 {
575 GPALConverter converter = new GPALConverter();
576 return (IAllowConverterInput)converter;
577 }
578 }
579
597 {
598 [Fluent(Description = "Start or attach to a desktop application and automate it")]
599 get
600 {
601 tmpApplication = new Application.Application();
602 tmpApplication.Name = applicationName + applicationCount++.ToString();
603 ApplicationList.Add(tmpApplication);
604 return tmpApplication;
605 }
606 }
607
628 {
629 [Fluent(Description = "Start or attach to a browser and automate it on Selenium, Puppeteer or OttoMagic")]
630 get
631 {
632 tmpBrowser = new Browser.Browser();
633 tmpBrowser.Name = browserName + browserCount++.ToString();
634
635 CheckVersions();
636
637 // if we already have a browser and instantiate a new one, it MUST use the same automation engine as the first browser instantiated.
638 if (0 < BrowserList.Count)
639 tmpBrowser.WithAutomationEngine(BrowserList[0].AutomationEngine);
640
641 BrowserList.Add(tmpBrowser);
642 tmpBrowser.BrowserSettings.MagicHelper = new GenerallyPositive.Browser.MagicHelper(tmpBrowser); // one helper per browser, talking to that browser's port
643 return tmpBrowser;
644 }
645 }
646
687 {
688 [Component(Description = "Read or set the Windows clipboard", Submenu = "Data")]
689 get
690 {
691 GPALClipboard clipboard = new GPALClipboard();
692 return (IAllowClipboardInput)clipboard;
693 }
694 }
695 public static IAllowDatabaseSettings Database
696 {
697 [Component(Description = "Read from and write to a database table or stored procedure", Submenu = "Data")]
698 get
699 {
700 GPALDatabase database = new GPALDatabase();
701 return (IAllowDatabaseSettings)database;
702 }
703 }
729 {
730 [Component(Description = "Describe how to find an element in a browser or an application", Submenu = "Selectors")]
731 get
732 {
733 Selector tmpSelector = null;
734 if (null != tmpBrowser)
735 tmpSelector = new Selector(tmpBrowser);
736 else if (null != tmpApplication)
737 tmpSelector = new Selector(tmpApplication);
738 else
739 tmpSelector = new Selector();
740 tmpSelector.Name = selectorName + selectorCount++.ToString();
741 SelectorList.Add(tmpSelector);
742 return tmpSelector;
743 }
744 }
745
760 {
761 [Component(Description = "Describe an API request for Fetch to send from inside the page, carrying the session the browser already has", Submenu = "Browser")]
762 get
763 {
764 return new GPALRequest();
765 }
766 }
767 public static IAllowExecutableSetup Launcher
768 {
769 [Component(Description = "Start a program or open a document, with arguments, and optionally read what it prints", Submenu = "System")]
770 get
771 {
772 Launcher launcher = new Launcher
773 {
774 Name = executableName + executableCount++.ToString()
775 };
776 return launcher;
777 }
778
779 }
950 {
951 [Component(Description = "Call a REST API: base url, endpoint, parameters and credentials", Submenu = "Services")]
952 get
953 {
954 RESTClient restClient = new RESTClient
955 {
956 Name = restClientName + restClientCount++.ToString()
957 };
958 return restClient;
959 }
960 }
961
965 {
966 [Component(Description = "Credentials from a password manager, an API key or an OAuth provider", Submenu = "Security")]
967 get => new Credentials((Browser.Browser)Browser); // TODO: CAVEAT: BUG: implement correct, this browser is a placeholder
968 }
969 public static IAllowGiven Gherkin
970 {
971 [Component(Description = "Write a workflow as Given, When and Then steps", Submenu = "System")]
972 get => new Gherkin();
973 }
978 {
979 [Component(Description = "Read and write a Google Sheets spreadsheet", Submenu = "Google")]
980 get => new GoogleSheets();
981 }
982
986 {
987 [Component(Description = "Work with files in Google Drive", Submenu = "Google")]
988 get => new GoogleDrive();
989 }
990 public static IGPALExcel Excel
991 {
992 [Component(Description = "Read and write an Excel workbook", Submenu = "Data")]
993 get => new GPALExcel();
994 }
995 public static IAllowGPALUrlForUrl Url
996 {
997 [Component(Description = "A url, and the website storage on it to read or change", Submenu = "Browser")]
998 get => new GPALUrl(null);
999 }
1000
1001 #region Internal GPAL factory for ActivatorHelper
1002 internal static GPALElement Element
1003 {
1004 get => new GPALElement();
1005 }
1006 internal static GPALFileSettings FileSettings
1007 {
1008 get => new GPALFileSettings();
1009 }
1010 internal static GPALMailSettings MailSettings
1011 {
1012 get => new GPALMailSettings();
1013 }
1014 internal static GPALSettings Settings
1015 {
1016 get => new GPALSettings();
1017 }
1018 #endregion Internal GPAL factory for ActivatorHelper
1019 #endregion <GPAL Objects>
1020 #region Shorthand Helpers
1021 [Component(Description = "An Excel workbook for the given file", Submenu = "Data")]
1022 public static IGPALExcel ExcelFor(GPALFile excelFile)
1023 {
1024 return new GPALExcel().WithFile(excelFile).ToGPALObject();
1025 }
1026
1027 [Component(Description = "Credentials for the given service", Submenu = "Security")]
1028 public static ICredentials CredentialsFor(CredentialServiceType credentialServiceType)
1029 {
1030 return new Credentials((Browser.Browser)Browser).WithService(credentialServiceType).ToGPALObject();
1031 }
1032 [Component(Description = "A Google Sheets spreadsheet, opened by name or id", Submenu = "Google")]
1033 public static IGoogleSheets GoogleSheetsFor(string spreadsheetName)
1034 {
1035 return new GoogleSheets().WithSpreadsheet(spreadsheetName).ToGPALObject();
1036 }
1037 [Component(Description = "Google Drive, signed in with the given credentials", Submenu = "Google")]
1038 public static IGoogleDrive GoogleDriveFor(ICredentials credentials)
1039 {
1040 return new GoogleDrive().WithCredentials(credentials).ToGPALObject();
1041 }
1042 [Component(Description = "A grid whose rows are objects of the given class", Submenu = "Data")]
1043 public static IGPALGrid<T> GridForType<T>()
1044 {
1045 return new GPALGrid<T>();
1046 }
1061 [Component(Description = "A selector that finds an element by CSS, optionally named", Submenu = "Selectors")]
1062 public static Selector CssSelector(string css, string selectorName = null)
1063 {
1064 if (null != selectorName)
1065 return Selector.WithCSS(css).WithSelectorName(selectorName).ToGPALObject();
1066 else
1067 return Selector.WithCSS(css).ToGPALObject();
1068 }
1069
1070 [Component(Description = "A selector that finds an element by XPath, optionally named", Submenu = "Selectors")]
1071 public static Selector XPathSelector(string xpath, string selectorName = null)
1072 {
1073 if (null != selectorName)
1074 return Selector.WithXPath(xpath).WithSelectorName(selectorName).ToGPALObject();
1075 else
1076 return Selector.WithXPath(xpath).ToGPALObject();
1077 }
1078 [Component(Description = "A GPALFile for the given path", Submenu = "Data")]
1079 public static GPALFile FileFor(string path)
1080 {
1081 return File.WithFileName(path).ToGPALObject();
1082 }
1083 // New browser shorthand methods
1084 [Component(Description = "Start a browser with no window on the given url", Submenu = "Browser")]
1085 public static IBrowser BrowserGet(GPALUrl url)
1086 {
1087 return Browser
1088 .Get(url) // Headless navigation
1089 .ToGPALObject();
1090
1091 }
1092 [Component(Description = "Start a browser window on the given url", Submenu = "Browser")]
1093 public static IBrowser BrowserGoto(GPALUrl url)
1094 {
1095 return Browser
1096 .GoTo(url) // Windowed navigation
1097 .ToGPALObject();
1098 }
1099 [Component(Description = "A button with the given text, for a GPAL form", Submenu = "Form Controls/Buttons and Choices")]
1100 public static GPALButton ButtonFor(string text)
1101 {
1102 var c = new GPALButton { Name = buttonName + controlCount++.ToString() };
1103 c.IsDefault = false;
1104 c.Text = text;
1105 ControlList.Add(c);
1106 return c;
1107 }
1108 [Component(Description = "A label with the given text, for a GPAL form", Submenu = "Form Controls/Text")]
1109 public static GPALLabel LabelFor(string text)
1110 {
1111 var c = new GPALLabel { Name = labelName + controlCount++.ToString() };
1112 c.Text = text;
1113 ControlList.Add(c);
1114 return c;
1115 }
1116 [Component(Description = "Empty space this many pixels tall between two controls on a GPAL form", Submenu = "Form Controls/Layout")]
1117 public static GPALSpacer SpacerFor(int pixels)
1118 {
1119 var c = new GPALSpacer { Name = spacerName + controlCount++.ToString() };
1120 c.Height = pixels;
1121 ControlList.Add(c);
1122 return c;
1123 }
1124 [Component(Description = "A link for a GPAL form that opens this address in the default browser, showing the address as its text", Submenu = "Form Controls/Text")]
1125 public static GPALLinkLabel LinkLabelFor(string url)
1126 {
1127 var c = new GPALLinkLabel { Name = linkLabelName + controlCount++.ToString() };
1128 c.Url = url;
1129 c.Text = url;
1130 ControlList.Add(c);
1131 return c;
1132 }
1133 [Component(Description = "A one-line input box for a GPAL form, optionally with starting text", Submenu = "Form Controls/Text")]
1134 public static GPALInput InputFor(string text = "")
1135 {
1136 var c = new GPALInput { Name = inputName + controlCount++.ToString() };
1137 c.Text = text;
1138 ControlList.Add(c);
1139 return c;
1140 }
1141 [Component(Description = "A multi-line text box for a GPAL form, optionally with starting text", Submenu = "Form Controls/Text")]
1142 public static GPALTextArea TextAreaFor(string text = "")
1143 {
1144 var c = new GPALTextArea { Name = textAreaName + controlCount++.ToString() };
1145 c.Text = text;
1146 ControlList.Add(c);
1147 return c;
1148 }
1149 [Component(Description = "A rich text box for a GPAL form that renders Markdown, optionally with starting text", Submenu = "Form Controls/Text")]
1150 public static GPALRichTextBox RichTextBoxFor(string text = "")
1151 {
1152 var c = new GPALRichTextBox { Name = richTextBoxName + controlCount++.ToString() };
1153 c.Text = text;
1154 ControlList.Add(c);
1155 return c;
1156 }
1157 [Component(Description = "A checkbox with the given text, for a GPAL form, checked or not", Submenu = "Form Controls/Buttons and Choices")]
1158 public static GPALCheckbox CheckboxFor(string text, bool isChecked = false)
1159 {
1160 var c = new GPALCheckbox { Name = checkboxName + controlCount++.ToString() };
1161 c.Text = text;
1162 c.IsChecked = isChecked;
1163 ControlList.Add(c);
1164 return c;
1165 }
1166 [Component(Description = "A radio button with the given text, for a GPAL form, selected or not", Submenu = "Form Controls/Buttons and Choices")]
1167 public static GPALRadioButton RadioButtonFor(string text, bool isChecked = false)
1168 {
1169 var c = new GPALRadioButton { Name = radioButtonName + controlCount++.ToString() };
1170 c.Text = text;
1171 c.IsChecked = isChecked;
1172 ControlList.Add(c);
1173 return c;
1174 }
1175 [Component(Description = "A tab with the given title, for a GPAL form", Submenu = "Form Controls/Layout")]
1176 public static GPALTab TabFor(string text)
1177 {
1178 var c = new GPALTab { Name = radioButtonName + controlCount++.ToString() };
1179 c.Text = text;
1180 ControlList.Add(c);
1181 return c;
1182 }
1183 [Component(Description = "A chart for a GPAL form, optionally with a title", Submenu = "Form Controls/Lists and Data")]
1184 public static GPALChart ChartFor(string title = "")
1185 {
1186 var c = new GPALChart { Name = chartName + controlCount++.ToString() };
1187 if (!string.IsNullOrEmpty(title)) c.WithTitle(title);
1188 ControlList.Add(c);
1189 return c;
1190 }
1191 [Component(Description = "A drop-down list with the given items, for a GPAL form", Submenu = "Form Controls/Buttons and Choices")]
1192 public static GPALComboBox ComboBoxFor(params string[] items)
1193 {
1194 var c = new GPALComboBox { Name = "ComboBox" + controlCount++.ToString() };
1195 if (items != null && items.Length > 0) c.WithItems(items);
1196 return c;
1197 }
1198 [Component(Description = "A data grid with the given columns, for a GPAL form", Submenu = "Form Controls/Lists and Data")]
1199 public static GPALDataGridView DataGridViewFor(params string[] columns)
1200 {
1201 var c = new GPALDataGridView { Name = "DataGridView" + controlCount++.ToString() };
1202 if (columns != null && columns.Length > 0) c.WithColumns(columns);
1203 return c;
1204 }
1205 [Component(Description = "A list view with the given columns, for a GPAL form", Submenu = "Form Controls/Lists and Data")]
1206 public static GPALListView ListViewFor(params string[] columns)
1207 {
1208 var c = new GPALListView { Name = "ListView" + controlCount++.ToString() };
1209 if (columns != null && columns.Length > 0) c.WithColumns(columns);
1210 return c;
1211 }
1212 [Component(Description = "A picture box showing the given image file, for a GPAL form", Submenu = "Form Controls/Lists and Data")]
1213 public static GPALPicture PictureFor(GPALFile file)
1214 {
1215 var c = new GPALPicture { Name = "Picture" + controlCount++.ToString() };
1216 c.WithImage(file);
1217 return c;
1218 }
1219 [Component(Description = "A slider from 0 to the given maximum, for a GPAL form", Submenu = "Form Controls/Buttons and Choices")]
1220 public static GPALSlider SliderFor(int maximum = 100)
1221 {
1222 var c = new GPALSlider { Name = "Slider" + controlCount++.ToString() };
1223 c.WithMaximum(maximum);
1224 return c;
1225 }
1226 [Component(Description = "A progress bar from 0 to the given maximum, for a GPAL form", Submenu = "Form Controls/Lists and Data")]
1227 public static GPALProgressBar ProgressBarFor(int maximum = 100)
1228 {
1229 var c = new GPALProgressBar { Name = "ProgressBar" + controlCount++.ToString() };
1230 c.WithMaximum(maximum);
1231 return c;
1232 }
1233 [Component(Description = "A status strip with the given text, for a GPAL form", Submenu = "Form Controls/Menus and Bars")]
1234 public static GPALStatusStrip StatusStripFor(string text = "Ready")
1235 {
1236 var c = new GPALStatusStrip { Name = "StatusStrip" + controlCount++.ToString() };
1237 c.Text = text;
1238 return c;
1239 }
1240 [Component(Description = "A number box held between the given minimum and maximum, for a GPAL form", Submenu = "Form Controls/Buttons and Choices")]
1241 public static GPALNumericUpDown NumericUpDownFor(decimal minimum = 0, decimal maximum = 100)
1242 {
1243 var c = new GPALNumericUpDown { Name = "NumericUpDown" + controlCount++.ToString() };
1244 c.WithMinimum(minimum);
1245 c.WithMaximum(maximum);
1246 return c;
1247 }
1248 [Component(Description = "A date and time picker for a GPAL form, optionally set to a value", Submenu = "Form Controls/Buttons and Choices")]
1249 public static GPALDateTimePicker DateTimePickerFor(DateTime? value = null)
1250 {
1251 var c = new GPALDateTimePicker { Name = "DateTimePicker" + controlCount++.ToString() };
1252 if (value.HasValue) c.WithValue(value.Value);
1253 return c;
1254 }
1255 [Component(Description = "An empty tree view for a GPAL form", Submenu = "Form Controls/Lists and Data")]
1256 public static GPALTreeView TreeViewFor()
1257 {
1258 return new GPALTreeView { Name = "TreeView" + controlCount++.ToString() };
1259 }
1260 [Component(Description = "A file or folder picker with the given dialog title, for a GPAL form", Submenu = "Form Controls/Buttons and Choices")]
1261 public static GPALFileSelector FileSelectorFor(string title = "Select File/Folder")
1262 {
1263 var c = new GPALFileSelector { Name = "FileSeleector" + controlCount++.ToString() };
1264 c.WithTitle(title);
1265 return c;
1266 }
1267 [Component(Description = "A layout panel with the given columns and rows, for a GPAL form", Submenu = "Form Controls/Layout")]
1268 public static GPALTableLayoutPanel TableLayoutPanelFor(int columns = 1, int rows = 1)
1269 {
1270 var c = new GPALTableLayoutPanel { Name = "TableLayoutPanel" + controlCount++.ToString() };
1271 c.WithColumnCount(columns);
1272 c.WithRowCount(rows);
1273 return c;
1274 }
1275 #endregion Shorthand Helpers
1276 #region <ElementAssitant>
1299 [ElementAssitant(Description = "Work with the elements a browser selector found, from inside a callback")]
1301 {
1302 elementAssistant.Browser = selector.Browser;
1303 elementAssistant.ForSelector(selector);
1304 return elementAssistant;
1305 }
1306
1330 [ElementAssitant(Description = "Work with the elements an application selector found, from inside a callback")]
1331 public static Application.IAllowForSelector ElementAssistant(Application.Application application)
1332 {
1333 applicationElementAssistant.Application = application;
1334 return applicationElementAssistant;
1335 }
1336 #endregion <ElementAssitant>
1337 #region <Form Controls>
1369 {
1370 [Fluent(Description = "Build a form of controls to give a workflow a user interface")]
1371 get
1372 {
1373 GPALForm.GPALForm tmpForms = new GPALForm.GPALForm()
1374 {
1375 FormName = formName + formCount++.ToString(),
1376 };
1377 FormList.Add(tmpForms);
1378 return tmpForms;
1379 }
1380 }
1381
1414 {
1415 [Forms(Description = "A button for a GPAL form, calling back when it is clicked", Submenu = "Buttons and Choices")]
1416 get
1417 {
1418 GPALButton tmpControl = new GPALButton
1419 {
1420 Name = buttonName + controlCount++.ToString(),
1421 IsDefault = false
1422 };
1423 //CAVEAT: very strange bug, isdefault keeps getting set to true
1424 tmpControl.IsDefault = false;
1425 ControlList.Add(tmpControl);
1426 return tmpControl;
1427 }
1428 }
1429
1471 {
1472 [Forms(Description = "A chart for a GPAL form", Submenu = "Lists and Data")]
1473 get
1474 {
1475 GPALChart tmpControl = new GPALChart
1476 {
1477 Name = chartName + controlCount++.ToString()
1478 };
1479 ControlList.Add(tmpControl);
1480 return tmpControl;
1481 }
1482 }
1483
1514 {
1515 [Forms(Description = "A checkbox for a GPAL form, calling back when it is checked or cleared", Submenu = "Buttons and Choices")]
1516 get
1517 {
1518 GPALCheckbox tmpControl = new GPALCheckbox
1519 {
1520 Name = checkboxName + controlCount++.ToString()
1521 };
1522 ControlList.Add(tmpControl);
1523 return tmpControl;
1524 }
1525 }
1526
1553 {
1554 [Forms(Description = "Instantiates a new GPALComboBox for use on a GPAL form", Submenu = "Buttons and Choices")] get
1555 {
1556 GPALComboBox tmpControl = new GPALComboBox
1557 {
1558 Name = "ComboBox" + controlCount++.ToString() // or use your naming scheme
1559 };
1560 // ControlList.Add(tmpControl); ← usually done in WithFormControl / AddComboBox
1561 return tmpControl;
1562 }
1563 }
1564
1581 {
1582 [Forms(Description = "Instantiates a new GPALDataGridView for use on a GPAL form", Submenu = "Lists and Data")] get
1583 {
1585 {
1586 Name = "DataGridView" + controlCount++.ToString()
1587 };
1588 return tmp;
1589 }
1590 }
1591
1610 {
1611 [Forms(Description = "Instantiates a new GPALDateTimePicker for use on a GPAL form", Submenu = "Buttons and Choices")] get
1612 {
1614 {
1615 Name = "DateTimePicker" + controlCount++.ToString()
1616 };
1617 return tmp;
1618 }
1619 }
1620
1662 {
1663 [Forms(Description = "Instantiates a new GPALFileSelector for use on a GPAL form", Submenu = "Buttons and Choices")] get
1664 {
1666 {
1667 Name = "FileSeleector" + controlCount++.ToString()
1668 };
1669 return gfs;
1670 }
1671 }
1672
1693 {
1694 [Forms(Description = "A one-line input box for a GPAL form, calling back when its text changes", Submenu = "Text")]
1695 get
1696 {
1697 GPALInput tmpControl = new GPALInput
1698 {
1699 Name = inputName + controlCount++.ToString()
1700 };
1701 ControlList.Add(tmpControl);
1702 return tmpControl;
1703 }
1704 }
1705
1718 {
1719 [Forms(Description = "A link for a GPAL form that opens an address in the default browser", Submenu = "Text")]
1720 get
1721 {
1722 GPALLinkLabel tmpControl = new GPALLinkLabel
1723 {
1724 Name = linkLabelName + controlCount++.ToString()
1725 };
1726 ControlList.Add(tmpControl);
1727 return tmpControl;
1728 }
1729 }
1730
1750 {
1751 [Forms(Description = "A label for a GPAL form", Submenu = "Text")]
1752 get
1753 {
1754 GPALLabel tmpControl = new GPALLabel
1755 {
1756 Name = labelName + controlCount++.ToString()
1757 };
1758 ControlList.Add(tmpControl);
1759 return tmpControl;
1760 }
1761 }
1762
1790 {
1791 [Forms(Description = "Instantiates a new GPALListView for use on a GPAL form", Submenu = "Lists and Data")] get
1792 {
1793 GPALListView tmp = new GPALListView
1794 {
1795 Name = "ListView" + controlCount++.ToString()
1796 };
1797 return tmp;
1798 }
1799 }
1800
1821 {
1822 [Forms(Description = "Instantiates a new GPALNumericUpDown for use on a GPAL form", Submenu = "Buttons and Choices")] get
1823 {
1825 {
1826 Name = "NumericUpDown" + controlCount++.ToString()
1827 };
1828 return tmp;
1829 }
1830 }
1831
1849 {
1850 [Forms(Description = "A picture control for GPAL forms", Submenu = "Lists and Data")] get
1851 {
1852 GPALPicture tmp = new GPALPicture
1853 {
1854 Name = "Picture" + controlCount++.ToString()
1855 };
1856 return tmp;
1857 }
1858 }
1859
1879 {
1880 [Forms(Description = "A slider control for GPAL forms", Submenu = "Buttons and Choices")] get
1881 {
1882 GPALSlider tmp = new GPALSlider
1883 {
1884 Name = "Slider" + controlCount++.ToString()
1885 };
1886 return tmp;
1887 }
1888 }
1889
1910 {
1911 [Forms(Description = "A progress bar control for GPAL forms", Submenu = "Lists and Data")] get
1912 {
1914 {
1915 Name = "ProgressBar" + controlCount++.ToString()
1916 };
1917 return tmp;
1918 }
1919 }
1920
1959 {
1960 [Forms(Description = "A radio button for a GPAL form, calling back when it is selected", Submenu = "Buttons and Choices")]
1961 get
1962 {
1963 GPALRadioButton tmpControl = new GPALRadioButton
1964 {
1965 Name = radioButtonName + controlCount++.ToString()
1966 };
1967 ControlList.Add(tmpControl);
1968 return tmpControl;
1969 }
1970 }
1971
1988 {
1989 [Forms(Description = "A status strip control (docked at the bottom) for GPAL forms", Submenu = "Menus and Bars")] get
1990 {
1992 {
1993 Name = "StatusStrip" + controlCount++.ToString()
1994 };
1995 return tmp;
1996 }
1997 }
1998
2018 {
2019 [Forms(Description = "A menu bar docked to the top of a GPAL form", Submenu = "Menus and Bars")] get
2020 {
2021 GPALMenuBar tmp = new GPALMenuBar
2022 {
2023 Name = "MenuBar" + controlCount++.ToString()
2024 };
2025 return tmp;
2026 }
2027 }
2028
2045 {
2046 [Forms(Description = "A single item shared by menu bars and toolbars: text, optional image, optional shortcut key, and a callback wired to its click", Submenu = "Menus and Bars")] get
2047 {
2048 GPALBarItem tmp = new GPALBarItem
2049 {
2050 Name = "BarItem" + controlCount++.ToString()
2051 };
2052 return tmp;
2053 }
2054 }
2055
2071 {
2072 [Forms(Description = "A toolbar docked below the menu bar, holding GPAL.BarItem buttons and optional separators", Submenu = "Menus and Bars")] get
2073 {
2074 GPALToolbar tmp = new GPALToolbar
2075 {
2076 Name = "Toolbar" + controlCount++.ToString()
2077 };
2078 return tmp;
2079 }
2080 }
2081
2106 {
2107 [Forms(Description = "A tab page for a GPAL form", Submenu = "Layout")]
2108 get
2109 {
2110 GPALTab tmpControl = new GPALTab
2111 {
2112 Name = radioButtonName + controlCount++.ToString()
2113 };
2114 ControlList.Add(tmpControl);
2115 return tmpControl;
2116 }
2117 }
2118
2134 public static IAllowSplitterControlSettings Splitter { [Forms(Description = "Instantiates a new GPALSplitter for use on a GPAL form: two panes with a bar between them, built whole and then laid out like any other control", Submenu = "Layout")] get => new GPALSplitter(); }
2135
2140 public static IAllowGroupBoxControlSettings GroupBox { [Forms(Description = "Instantiates a new GPALGroupBox for use on a GPAL form: a titled box around a set of controls, laid out like any other control", Submenu = "Layout")] get => new GPALGroupBox(); }
2141
2143 public static GPALSpliter SplitRight { [Forms(Description = "Marks the end of a splitter's left pane", Submenu = "Layout")] get => new GPALSpliter(); }
2144
2146 public static GPALSpliter SplitBottom { [Forms(Description = "Marks the end of a splitter's top pane", Submenu = "Layout")] get => new GPALSpliter(); }
2147
2170 {
2171 [Forms(Description = "A multi-line text box for a GPAL form, calling back when its text changes", Submenu = "Text")]
2172 get
2173 {
2174 GPALTextArea tmpControl = new GPALTextArea
2175 {
2176 Name = textAreaName + controlCount++.ToString()
2177 };
2178 ControlList.Add(tmpControl);
2179 return tmpControl;
2180 }
2181 }
2182
2204 {
2205 [Forms(Description = "A rich text box for a GPAL form that renders Markdown", Submenu = "Text")]
2206 get
2207 {
2208 GPALRichTextBox tmpControl = new GPALRichTextBox
2209 {
2210 Name = richTextBoxName + controlCount++.ToString()
2211 };
2212 ControlList.Add(tmpControl);
2213 return tmpControl;
2214 }
2215 }
2216
2236 {
2237 [Forms(Description = "Instantiates a new GPALTreeView for use on a GPAL form", Submenu = "Lists and Data")] get
2238 {
2239 GPALTreeView tmp = new GPALTreeView
2240 {
2241 Name = "TreeView" + controlCount++.ToString()
2242 };
2243 return tmp;
2244 }
2245 }
2246 #endregion <Form Controls>
2247 #region <Getters/Setters>
2248 public static bool UseHardware
2249 {
2250 [GPALSettings(Description = "Whether GPAL uses the real mouse and keyboard by default")] get => gpalSettings.UseHardware;
2251 }
2252 public static bool UseJavaSCript
2253 {
2254 [GPALSettings(Description = "Whether GPAL interacts through injected JavaScript by default")] get => gpalSettings.UseJavaScript;
2255 }
2256
2272 {
2273 [Component(Description = "Run whole workflows at once, each owning the browser or application it drives", Submenu = "System")]
2274 get
2275 {
2276 return new Browser.WorkflowScheduler();
2277 }
2278 }
2279
2296 {
2297 [Component(Description = "The hidden desktops this program made, to peek at, return from or close", Submenu = "System")]
2298 get
2299 {
2300 return new Browser.GPALHiddenDesktops();
2301 }
2302 }
2303 public static string Version
2304 {
2305 [Data(Description = "The version of GPAL that is running")] get => GPALSettings.Version;
2306 }
2312 public static string OAuthTokenUrl
2313 {
2314 [Data(Description = "Where an OAuth redirect lands and the token is read back from, e.g. http://localhost:3117/")] get
2315 {
2316 return gpalSettings.OAuthTokenUrl;
2317 }
2318 set
2319 {
2320 gpalSettings.OAuthTokenUrl = value;
2321 }
2322 }
2323 internal static List<IGPALLogger> PublishToLoggers
2324 {
2325 get
2326 {
2327 return GPALSettings.PublishToLoggers;
2328 }
2329 }
2330 // The text area set by WithPublishToControl, or null
2331 internal static GPALTextArea PublishControl
2332 {
2333 get
2334 {
2335 return GPALSettings.PublishControl;
2336 }
2337 }
2338
2339 // Rolling line cap, so a long run does not grow the control without bound
2340 private const int PublishControlMaxLines = 2_000;
2341
2342 internal static bool PublishToConsole
2343 {
2344 get
2345 {
2346 return GPALSettings.PublishToConsole;
2347 }
2348 set
2349 {
2350 GPALSettings.PublishToConsole = value;
2351 }
2352 }
2353 internal static bool PublishToDebug
2354 {
2355 get
2356 {
2357 return GPALSettings.PublishToDebug;
2358 }
2359 }
2360 internal static GPALEventType ConsoleEvents
2361 {
2362 get => GPALSettings.ConsoleEvents;
2363 set => GPALSettings.ConsoleEvents = value;
2364 }
2365 internal static GPALEventType DebugEvents
2366 {
2367 get => GPALSettings.DebugEvents;
2368 }
2369 internal static bool PublishStackTrace
2370 {
2371 get
2372 {
2373 return GPALSettings.PublishStackTrace;
2374 }
2375 }
2376 internal static string ErrorPlaceholder
2377 {
2378 get
2379 {
2380 return GPALSettings.ErrorPlaceholder;
2381 }
2382 }
2383 internal static string DriverLocation
2384 {
2385 get
2386 {
2387 return GPALSettings.DriverLocation;
2388 }
2389 }
2390 internal static bool SimulateMouse
2391 {
2392 get
2393 {
2394 return GPALSettings.SimulateMouse;
2395 }
2396 }
2397 internal static string TempProfileDirectory
2398 {
2399 get
2400 {
2401 return GPALSettings.TempProfileDirectory;
2402 }
2403 }
2404 internal static List<Browser.Browser> Browsers
2405 {
2406 get
2407 {
2408 return BrowserList;
2409 }
2410 }
2411 internal static int MatchingPercentage
2412 {
2413 get
2414 {
2415 return GPALSettings.MatchingPercentage;
2416 }
2417 }
2418 internal static int TypingDelay
2419 {
2420 get
2421 {
2422 return GPALSettings.TypingDelay;
2423 }
2424 }
2425 internal static bool StopOnNotFound
2426 {
2427 get
2428 {
2429 return GPALSettings.StopOnNotFound;
2430 }
2431 }
2432 internal static bool NoFallbackRecoveryActions
2433 {
2434 get
2435 {
2436 return gpalSettings.NoFallbackRecoveryActions;
2437 }
2438 }
2439 #endregion <Getters/Setters>
2440 #region <Helpers>
2441 [Helper(Description = "Load GPAL's settings from GPAL.yaml or the given file, creating it with the defaults if it is missing")]
2442 public static void LoadSettings(GPALFile settingsFile = null)
2443 {
2444 bool hold = GPAL.PublishToConsole;
2445 GPALEventType events = GPAL.GPALSettings.ConsoleEvents;
2446
2447 try
2448 {
2449 if (settingsFile != null)
2450 {
2451 _settingsFilePath = settingsFile.Filenames[0];
2452 SettingsLoaded = false;
2453 }
2454
2455 if (false == SettingsLoaded)
2456 {
2457 SettingsLoaded = true;
2458 string currentVersion = System.Reflection.Assembly.GetExecutingAssembly().GetName().Version.ToString();
2459 GPALFile resolvedFile = _settingsFilePath;
2460
2461 if (true == System.IO.File.Exists(resolvedFile.Filenames[0]))
2462 {
2463 // load setting which contains default events to publish
2464 GPAL.Converter.WithInput(resolvedFile).SaveTo(ref gpalSettings);
2465
2466 hold = GPAL.PublishToConsole;
2467 events = GPAL.GPALSettings.ConsoleEvents;
2468
2469 // NOTE: CAVEAT: WE CANNOT EVER send anything on stdio when we are running as a browser Native App - that will disconnect it as garbage received
2470 if ("1" == Environment.GetEnvironmentVariable("GPAL_IS_SILENT"))
2471 GPAL.PublishToConsole = false;
2472
2473 GPAL.PublishSimpleEvent(GPALEventType.INFO, $"[{resolvedFile.Filenames[0]}] loaded, using settings.", gpalSettings, GPALObjectType.Other);
2474
2475 if (false == gpalSettings.Version.Equals(currentVersion))
2476 {
2477 GPAL.PublishSimpleEvent(GPALEventType.INFO, $"[{resolvedFile.Filenames[0]}] updated to current GPAL version [{currentVersion}].", gpalSettings, GPALObjectType.Other);
2478 gpalSettings.Version = currentVersion;
2479 SaveSettings();
2480 }
2481 }
2482 else // if file doesn't exist, initialize default settings and save a settings file, the user can then tweak those settings for next run/load
2483 {
2484 GPAL.PublishSimpleEvent(GPALEventType.WARNING, $"[{resolvedFile.Filenames[0]}] does not exist. Using default settings and creating GPAL.yaml.", gpalSettings, GPALObjectType.Other);
2485 SaveSettings();
2486 }
2487 }
2488
2489 AIProvidersConfig.TryAutoLoad();
2490 }
2491 catch (Exception ex)
2492 {
2493 GPAL.PublishSimpleEvent(GPALEventType.EXCEPTION, $"[{_settingsFilePath}] load error. Try deleting it and restarting.", gpalSettings, GPALObjectType.Other, ex);
2494 }
2495 finally
2496 {
2497 GPAL.PublishToConsole = hold;
2498 GPAL.GPALSettings.ConsoleEvents = events;
2499 }
2500 }
2501 [Helper(Description = "Save GPAL's current settings to GPAL.yaml or the given file")]
2502 public static void SaveSettings(GPALFile settingsFile = null)
2503 {
2504 if (settingsFile != null) _settingsFilePath = settingsFile.Filenames[0];
2505 GPAL.Converter.WithInput(GPALSettings).SaveTo((GPALFile)_settingsFilePath);
2506 }
2514 [Helper(Description = "Take a screenshot and keep it on the browser's settings")]
2515 public static IAllowGraphicSettings CaptureScreen(Browser.IBrowser browser)
2516 {
2517 ((ImageHelper)GPAL.ImageHelper).CaptureScreen(browser).ToBitmap(out ((Browser.Browser)browser).BrowserSettings.ScreenShot);
2518
2519 return ImageHelper;
2520 }
2521
2533 // https://stackoverflow.com/questions/44153/can-you-use-reflection-to-find-the-name-of-the-currently-executing-method/67372375#67372375
2534 [MethodImpl(MethodImplOptions.NoInlining)]
2535 [Helper(Description = "The name of the method that called it, for event messages")]
2536 public static string MyMethodName()
2537 {
2538 var st = new StackTrace();
2539 var sf = st.GetFrame(1);
2540 string name = sf.GetMethod().Name;
2541
2542 if (name.Equals("MoveNext"))
2543 {
2544 // We're inside an async method
2545 name = sf.GetMethod().ReflectedType.Name
2546 .Split(new char[] { '<', '>' }, StringSplitOptions.RemoveEmptyEntries)[0];
2547 }
2548
2549 return name;
2550 }
2551
2566 [MethodImpl(MethodImplOptions.NoInlining)]
2567 [Helper(Description = "Publish an event to the information or exception handlers")]
2568 public static void PublishSimpleEvent(GPALEventType gPALEventType, string msg, dynamic gPALObject = null, Enums.GPALObjectType gPALObjectType = GPALObjectType.None, Exception ex = null)
2569 {
2570 // Early-out BEFORE the costly new StackTrace()/reflection below: skip entirely when no sink will
2571 // consume this event level. Console/Debug are level-masked here (ConsoleEvents/DebugEvents), so a
2572 // high-volume low level like DEEPDEBUG no longer pays for a full stack capture when it is not in the
2573 // Each destination is tested against its own mask. Masked-out events return below before the
2574 // StackTrace is taken, which is most of the cost of a published event
2575 // Decided once and used for both the mask and the handler, which have to agree. An event belongs
2576 // to the exception channel when it carries an exception or is typed EXCEPTION
2577 bool toExceptionChannel = null != ex || true == gPALEventType.HasFlag(GPALEventType.EXCEPTION);
2578
2579 // Tested per handler, so an event bound for one is not published when only the other is attached
2580 bool handlerWants = true == toExceptionChannel
2581 ? null != ExceptionHandler && gpalSettings.ExceptionHandlerEvents.HasFlag(gPALEventType)
2582 : null != InformationHandler && gpalSettings.InformationHandlerEvents.HasFlag(gPALEventType);
2583 bool consoleWants = true == GPAL.PublishToConsole && gpalSettings.ConsoleEvents.HasFlag(gPALEventType);
2584 bool debugWants = true == GPAL.PublishToDebug && gpalSettings.DebugEvents.HasFlag(gPALEventType);
2585 bool controlWants = null != GPAL.PublishControl && gpalSettings.ControlEvents.HasFlag(gPALEventType);
2586 bool loggerWants = 0 < GPAL.PublishToLoggers.Count;
2587
2588 if (false == handlerWants && false == consoleWants && false == debugWants && false == controlWants && false == loggerWants)
2589 return;
2590
2591 if (true == handlerWants || true == controlWants || true == consoleWants || true == debugWants || true == loggerWants)
2592 {
2593 var st = new StackTrace();
2594 var sf = st.GetFrame(1);
2595 string name = sf.GetMethod().Name;
2596 DateTime dateTime = DateTime.Now;
2597 string stackTrace = null != ex && true == GPAL.PublishStackTrace ? $"[{ex.StackTrace}]" : string.Empty;
2598
2599 if (name.Equals("MoveNext"))
2600 {
2601 // We're inside an async method
2602 name = sf.GetMethod().ReflectedType.Name
2603 .Split(new char[] { '<', '>' }, StringSplitOptions.RemoveEmptyEntries)[0];
2604 }
2605
2606 // padded inside the tag to the length of WARNING, CAUTION and FAILURE, so the method names line up down
2607 // the log. EXCEPTION and DEEPDEBUG run two past it, which makes an exception easy to spot
2608 string outMessage = $"[{dateTime.ToString("yyyy-MM-dd HH:mm:ss.ffff")}][{gPALEventType,-7}][{name}]: {msg}";
2609 Bitmap ss = null;
2610
2611 // NOTE: we now only capture a screen image on error or exception
2612 if (GPALEventType.ERROR == gPALEventType || GPALEventType.EXCEPTION == gPALEventType)
2613 if (false == inPublishEvent && false == handlingTermination)
2614 {
2615 try
2616 {
2617 inPublishEvent = true; // avoid reentrant calls from events published in different paths calling capture screen
2618 if (true == gpalSettings.TakeEventScreenshot)
2619 if (typeof(Browser.Browser) == gPALObject?.GetType())
2620 ((ImageHelper)GPAL.ImageHelper).CaptureScreen(gPALObject).ToBitmap(out ss);
2621 else
2622 ((ImageHelper)GPAL.ImageHelper).CaptureScreen().ToBitmap(out ss);
2623 }
2624 catch // (Exception ex2)
2625 {
2626 // Console.WriteLine($"Unable to get screenshot in PublishSimpleEvent. [{ex2.Message}]");
2627 // Debug.WriteLine($"Unable to get screenshot in PublishSimpleEvent. [{ex2.Message}]");
2628 // if we are using ottomagic, nothing will be started up to call capture-visible-tab, so ignore
2629 }
2630 finally
2631 {
2632 inPublishEvent = false;
2633 }
2634 }
2635
2636 GPALEventArgs args = new GPALEventArgs()
2637 {
2638 GPALEventType = gPALEventType,
2639 GPALObject = gPALObject,
2640 GPALObjectType = gPALObjectType,
2641 CurrentUOW = (Enums.GPALObjectType.UnitOfWork == gPALObjectType ? (UnitOfWork)gPALObject : null),
2642 Message = $"{name}: {msg}",
2643 ScreenShot = ss,
2644 ExceptionRaised = ex,
2645 Data = (Enums.GPALObjectType.Converter == gPALObjectType ? gPALObject : string.Empty),
2646 DateTimeStamp = dateTime
2647 };
2648
2649 // Same mask as the early-out above
2650 if (true == handlerWants)
2651 {
2652 if (true == toExceptionChannel)
2653 ExceptionHandler?.Invoke(gPALObject, args);
2654 else
2655 InformationHandler?.Invoke(gPALObject, args);
2656 }
2657
2658 if (true == controlWants)
2659 {
2660 GPAL.PublishControl.AppendLine(outMessage, PublishControlMaxLines);
2661
2662 // Exception text on a second line, under the same DEBUG rule the console below uses
2663 if (null != ex && true == gpalSettings.ControlEvents.HasFlag(GPALEventType.DEBUG))
2664 GPAL.PublishControl.AppendLine($"[{ex.Message}]{stackTrace}", PublishControlMaxLines);
2665 }
2666
2667 if (true == GPAL.PublishToConsole)
2668 {
2669 // NOTE: silent init is used by gpal rest api, we must never output anything on stdio
2670 if (gpalSettings.ConsoleEvents.HasFlag(gPALEventType) && Environment.GetEnvironmentVariable("GPAL_IS_SILENT") != "1")
2671 {
2672 Console.WriteLine(outMessage);
2673
2674 // the exception text and its stack trace are diagnostics, not the event. Our message already
2675 // says what failed, so turn DEBUG on when you also need to know how it failed underneath
2676 if (null != ex && gpalSettings.ConsoleEvents.HasFlag(GPALEventType.DEBUG))
2677 Console.WriteLine($"[{ex.Message}]{stackTrace}");
2678 }
2679 }
2680
2681 if (true == GPAL.PublishToDebug)
2682 {
2683 if (gpalSettings.DebugEvents.HasFlag(gPALEventType) && Environment.GetEnvironmentVariable("GPAL_IS_SILENT") != "1")
2684 {
2685 Debug.WriteLine(outMessage);
2686
2687 // see the console block above: exception text and stack trace ride on DEBUG
2688 if (null != ex && gpalSettings.DebugEvents.HasFlag(GPALEventType.DEBUG))
2689 Debug.WriteLine($"[{ex.Message}]{stackTrace}");
2690 }
2691 }
2692
2693 // the logger decides where this lands, a file or a database, and whether it wants this type at
2694 // all. the event's own type is handed over so the entry is labelled with what actually happened.
2695 // guarded because the logger's write publishes events of its own, and those would arrive straight
2696 // back here
2697 if (0 < GPAL.PublishToLoggers.Count && true == Enum.TryParse(gPALEventType.ToString(), out Enums.LogType logType)) // Enums. because selenium has a LogType too
2698 {
2699 if (null == loggersWriting)
2700 loggersWriting = new HashSet<IGPALLogger>();
2701
2702 // a copy, because a logger raising an event while it writes could attach another one
2703 foreach (IGPALLogger logger in GPAL.PublishToLoggers.ToList())
2704 {
2705 // this one is already writing, so this event is one it raised itself. handing it back is
2706 // how a logger feeds itself its own failures. every other logger still gets it: a
2707 // database that will not take a row says nothing about a file that will
2708 if (true == loggersWriting.Contains(logger))
2709 continue;
2710
2711 try
2712 {
2713 loggersWriting.Add(logger);
2714
2715 // through the class rather than the interface: Log is not something a workflow calls
2716 // any more, it is how an event becomes an entry
2717 GPALLogger writer = (GPALLogger)logger;
2718
2719 // the type goes in with the entry rather than being set on the logger first, so two
2720 // threads publishing at once cannot swap each other's severity between the calls
2721 writer.Log(msg, name, logType);
2722
2723 // the exception text rides on DEBUG here the same way it does in the console and
2724 // debug blocks above, so a logger that only wants exceptions is not handed them.
2725 // without it an EXCEPTION entry says what failed and never why, and a log file is
2726 // the only account of a run nobody watched
2727 if (null != ex)
2728 writer.Log($"[{ex.Message}]{stackTrace}", name, Enums.LogType.DEBUG);
2729 }
2730 catch
2731 {
2732 // one logger that cannot write is not the others' problem, and not worth the run
2733 }
2734 finally
2735 {
2736 loggersWriting.Remove(logger);
2737 }
2738 }
2739 }
2740
2741 // flush any pending output, following operations may block or delay output
2742 // also flush for user who might be outputting and not realizing they need to flush output. :)
2743 if (Environment.GetEnvironmentVariable("GPAL_IS_SILENT") != "1")
2744 {
2745 Console.Out.Flush();
2746 Debug.Flush();
2747 }
2748 }
2749 }
2750
2762 string title,
2763 IDictionary<(TRow row, TCol col), TValue> data,
2764 string cornerLabel = "",
2765 Func<TValue, string> formatCell = null,
2766 string emptyCell = "-")
2767 {
2768 if (null == data || 0 == data.Count)
2769 return;
2770
2771 formatCell = formatCell ?? (v => v?.ToString() ?? string.Empty);
2772
2773 var rows = data.Keys.Select(k => k.row).Distinct().OrderBy(r => r, Comparer<TRow>.Default).ToList();
2774 var cols = data.Keys.Select(k => k.col).Distinct().OrderBy(c => c, Comparer<TCol>.Default).ToList();
2775
2776 string Cell(TRow r, TCol c) => data.TryGetValue((r, c), out var v) ? formatCell(v) : emptyCell;
2777
2778 int rowHeaderWidth = cornerLabel.Length;
2779 foreach (var r in rows)
2780 rowHeaderWidth = Math.Max(rowHeaderWidth, (r?.ToString() ?? string.Empty).Length);
2781
2782 var colWidth = new Dictionary<TCol, int>();
2783 foreach (var c in cols)
2784 {
2785 int w = (c?.ToString() ?? string.Empty).Length;
2786 foreach (var r in rows)
2787 w = Math.Max(w, Cell(r, c).Length);
2788 colWidth[c] = w;
2789 }
2790
2791 var sb = new StringBuilder();
2792 sb.AppendLine();
2793 sb.AppendLine($"=== {title} ===");
2794
2795 sb.Append(cornerLabel.PadRight(rowHeaderWidth));
2796 foreach (var c in cols)
2797 sb.Append(" | " + (c?.ToString() ?? string.Empty).PadRight(colWidth[c]));
2798 sb.AppendLine();
2799
2800 sb.Append(new string('-', rowHeaderWidth));
2801 foreach (var c in cols)
2802 sb.Append("-+-" + new string('-', colWidth[c]));
2803 sb.AppendLine();
2804
2805 foreach (var r in rows)
2806 {
2807 sb.Append((r?.ToString() ?? string.Empty).PadRight(rowHeaderWidth));
2808 foreach (var c in cols)
2809 sb.Append(" | " + Cell(r, c).PadRight(colWidth[c]));
2810 sb.AppendLine();
2811 }
2812
2813 // NOTICE rather than INFO: a table is something the workflow asked to be shown, not GPAL narrating
2814 // itself, so it belongs with the workflow's own output and survives a subscription that excludes ours
2815 PublishSimpleEvent(GPALEventType.NOTICE, sb.ToString());
2816 }
2817
2822 public static void EmitRuntimeTable(string title, IDictionary<(BrowserType bt, AutomationEngine ae), TimeSpan> runtimes)
2823 => EmitTable(title + " runtime (min:sec.ms)", runtimes, "Browser",
2824 ts => $"{(int)ts.TotalMinutes}:{ts.Seconds:D2}.{ts.Milliseconds:D3}");
2825 [GPALSettings(Description = "Global Browser CallIfFound handler called for every selector in every Unit of Work")]
2826 #endregion <Helpers>
2827 #region <GPAL Settings>
2828 // due to the fluent nature, we have to return an object with the interface IAllowGPALSettings
2829 // these are implemented in GPAL but also in GPALSettings, the object we chain to
2830 // but we want our language syntax to start with GPAL.
2831 // and then chain quietly, from GPAL to GPALSettings.
2832 // Problem is static classes cannot inherit interfaces, so we have to return the setting object.
2833
2839 public static IAllowGPALSettings AppCallIfFound(Application.Application.CallIfDelegate callIfFound)
2840 {
2841 gpalSettings.AppCallIfFoundList.Add(callIfFound);
2842 return (IAllowGPALSettings)GPALSettings;
2843 }
2844
2849 [GPALSettings(Description = "Global Browser CallIfNotFound handler called for every selector in every Unit of Work")]
2850 public static IAllowGPALSettings AppCallIfNotFound(Application.Application.CallIfDelegate callIfNotFound)
2851 {
2852 gpalSettings.AppCallIfNotFoundList.Add(callIfNotFound);
2853 return (IAllowGPALSettings)GPALSettings;
2854 }
2855
2860 [GPALSettings(Description = "Global Browser CallIfFound handler called for every selector in every Unit of Work")]
2861 public static IAllowGPALSettings CallIfFound(Browser.Browser.CallIfDelegate callIfFound)
2862 {
2863 gpalSettings.CallIfFoundList.Add(callIfFound);
2864 return (IAllowGPALSettings)GPALSettings;
2865 }
2866
2871 [GPALSettings(Description = "Global Browser CallIfNotFound handler called for every selector in every Unit of Work")]
2872 public static IAllowGPALSettings CallIfNotFound(Browser.Browser.CallIfDelegate callIfNotFound)
2873 {
2874 gpalSettings.CallIfNotFoundList.Add(callIfNotFound);
2875 return (IAllowGPALSettings)GPALSettings;
2876 }
2877
2882 [GPALSettings(Description = "Print specified GPAL events to the console. [none (default)]")]
2883 public static IAllowGPALSettings WithPublishToConsole(GPALEventType eventType = GPALEventType.INFO | GPALEventType.WARNING | GPALEventType.ERROR | GPALEventType.EXCEPTION | GPALEventType.NOTICE | GPALEventType.CAUTION | GPALEventType.FAILURE)
2884 {
2885 GPALSettings.ConsoleEvents = eventType;
2886 if (GPALEventType.NONE == eventType)
2887 GPALSettings.PublishToConsole = false;
2888 else
2889 GPALSettings.PublishToConsole = true;
2890 return (IAllowGPALSettings)GPALSettings;
2891 }
2892
2909 [GPALSettings(Description = "Write specified GPAL events to a text area on a form. [none (default)]")]
2911 {
2912 GPALSettings.PublishControl = control;
2913 return (IAllowGPALSettings)GPALSettings;
2914 }
2915
2927 [GPALSettings(Description = "Which GPAL events reach the form control. [all (default)]")]
2928 public static IAllowGPALSettings WithControlEvents(GPALEventType eventTypes)
2929 {
2930 GPALSettings.ControlEvents = eventTypes;
2931 return (IAllowGPALSettings)GPALSettings;
2932 }
2933
2944 [GPALSettings(Description = "Which GPAL events reach the information handler. [all (default)]")]
2945 public static IAllowGPALSettings WithInformationHandlerEvents(GPALEventType eventTypes)
2946 {
2947 GPALSettings.InformationHandlerEvents = eventTypes;
2948 return (IAllowGPALSettings)GPALSettings;
2949 }
2950
2961 [GPALSettings(Description = "Which GPAL events reach the exception handler. [all (default)]")]
2962 public static IAllowGPALSettings WithExceptionHandlerEvents(GPALEventType eventTypes)
2963 {
2964 GPALSettings.ExceptionHandlerEvents = eventTypes;
2965 return (IAllowGPALSettings)GPALSettings;
2966 }
2967
2973 [GPALSettings(Description = "Print specified GPAL events to the output window in VisualStudio. [none (default)]")]
2974 public static IAllowGPALSettings WithPublishToDebug(GPALEventType eventType = GPALEventType.INFO | GPALEventType.WARNING | GPALEventType.ERROR | GPALEventType.EXCEPTION | GPALEventType.NOTICE | GPALEventType.CAUTION | GPALEventType.FAILURE)
2975 {
2976 GPALSettings.DebugEvents = eventType;
2977 if (GPALEventType.NONE == eventType)
2978 GPALSettings.PublishToDebug = false;
2979 else
2980 GPALSettings.PublishToDebug = true;
2981 return (IAllowGPALSettings)GPALSettings;
2982 }
2983
2990 [GPALSettings(Description = "Publish GPAL events to a logger, which writes them to whatever it was given: a file or a database.")]
2992 {
2993 GPALSettings.WithPublishToLogger(logger);
2994 return (IAllowGPALSettings)GPALSettings;
2995 }
2996
3001 [GPALSettings(Description = "Stop publishing GPAL events to a logger that was previously given one.")]
3003 {
3004 GPALSettings.RemoveLogger(logger);
3005 return (IAllowGPALSettings)GPALSettings;
3006 }
3007
3022 [GPALSettings(Description = "Which GPAL events reach an attached Information or Exception handler. [All (default)]")]
3023 public static IAllowGPALSettings WithHandlerEvents(GPALEventType eventTypes)
3024 {
3025 GPALSettings.InformationHandlerEvents = eventTypes;
3026 GPALSettings.ExceptionHandlerEvents = eventTypes;
3027 return (IAllowGPALSettings)GPALSettings;
3028 }
3029
3034 [GPALSettings(Description = "Print stack trace when PublishToConsole or PublishToDebug is true. [true (default)]")]
3035 public static IAllowGPALSettings WithPublishStackTrace(bool trueFalse = true)
3036 {
3037 GPALSettings.PublishStackTrace = trueFalse;
3038 return (IAllowGPALSettings)GPALSettings;
3039 }
3040
3049 [GPALSettings(Description = "Callback for failures a workflow can decide about, used by any browser without its own CallOnFail")]
3050 public static IAllowGPALSettings CallOnFail(Browser.Browser.CallOnFailDelegate callOnFail)
3051 {
3052 GPALSettings.CallOnFailHandlers.Add(callOnFail);
3053 return (IAllowGPALSettings)GPALSettings;
3054 }
3055
3061 [GPALSettings(Description = "Substitue string when data cannot be retrieved for an element [##UNABLE TO GET## (default)]")]
3062 public static IAllowGPALSettings WithDefaultErrorPlaceholder(string message = "##UNABLE TO GET##")
3063 {
3064 GPALSettings.ErrorPlaceholder = message;
3065 return (IAllowGPALSettings)GPALSettings;
3066 }
3067
3073 [GPALSettings(Description = "Set the default timeout for waiting for a download to start.")]
3075 {
3076 GPALSettings.DownloadTimeoutInSec = seconds;
3077 return (IAllowGPALSettings)GPALSettings;
3078 }
3079
3087 [GPALSettings("DO NOT use GPAL fallback actions on failure, just fail. Usually involves javascript recovery attempt. [false (default)")]
3088 public static IAllowGPALSettings WithNoFallbackActions(bool trueFalse = true)
3089 {
3090 GPALSettings.NoFallbackRecoveryActions = trueFalse;
3091 return (IAllowGPALSettings)GPALSettings;
3092 }
3093
3098 [GPALSettings(Description = "Global value for selector matches so it's not set UOW at a time")]
3099 public static IAllowGPALSettings WithAllThatMatch(int matchCount = int.MaxValue)
3100 {
3101 GPALSettings.WithAllThatMatch = matchCount;
3102 return (IAllowGPALSettings)GPALSettings;
3103 }
3104
3111 [GPALSettings(Description = "Automatically download and unzip the web driver that matches the version of the browser being run")]
3112 public static IAllowGPALSettings WithAutoUpdateWebDriver(bool trueFalse = true)
3113 {
3114 GPALSettings.AutoUpdateWebDriver = trueFalse;
3115 return (IAllowGPALSettings)GPALSettings;
3116 }
3117
3123 [GPALSettings(Description = "Set the automation type to use for all browsers")]
3124 public static IAllowGPALSettings WithAutomationEngine(AutomationEngine automationEngine)
3125 {
3126 GPALSettings.AutomationEngine = automationEngine;
3127 return (IAllowGPALSettings)GPALSettings;
3128 }
3129
3136 [GPALSettings(Description = "Global location for browser drivers. [./ (default)(same dir as exe)]")]
3137 public static IAllowGPALSettings WithDriverLocation(string driverPath)
3138 {
3139 GPALSettings.DriverLocation = FileHelper.EnsureDirectoryEndsWithBackslash(driverPath);
3140 return (IAllowGPALSettings)GPALSettings;
3141 }
3142
3150 [GPALSettings(Description = "Where GPAL writes files it created itself and nobody asked to keep. [GPALfilesSafeToDelete beside the exe (default)]")]
3151 public static IAllowGPALSettings WithTempDirectory(string tempPath)
3152 {
3153 GPALSettings.TempDirectory = FileHelper.EnsureDirectoryEndsWithBackslash(tempPath);
3154 return (IAllowGPALSettings)GPALSettings;
3155 }
3156
3161 internal static string TempDirectory()
3162 {
3163 string tempPath = true == string.IsNullOrWhiteSpace(GPALSettings.TempDirectory)
3164 ? Path.Combine(AppDomain.CurrentDomain.BaseDirectory, "GPALfilesSafeToDelete") + @"\"
3165 : GPALSettings.TempDirectory;
3166
3167 if (false == Directory.Exists(tempPath))
3168 Directory.CreateDirectory(tempPath);
3169
3170 return tempPath;
3171 }
3207 [GPALSettings(Description = "Intercharacter typing delay ceiling [0ms (default), no pacing]")]
3208 public static IAllowGPALSettings WithTypingDelay(int delayInTicks)
3209 {
3210 GPALSettings.WithTypingDelay(delayInTicks);
3211 return (IAllowGPALSettings)GPALSettings;
3212 }
3213
3225 [GPALSettings(Description = "Image matching similarity percentage. [80 (default)]")]
3226 public static IAllowGPALSettings WithImageMatchingPercentage(int matchPercent)
3227 {
3228 GPALSettings.WithImageMatchingPercentage(matchPercent);
3229 return (IAllowGPALSettings)GPALSettings;
3230 }
3231
3252 [GPALSettings(Description = "Nodes a YAML payload may hold before aliases are dropped for shared references. A cyclic payload always keeps them. [5000 (default), 0 never drops]")]
3253 public static IAllowGPALSettings WithYamlAliasLimit(int nodeLimit)
3254 {
3255 GPALSettings.WithYamlAliasLimit(nodeLimit);
3256 return (IAllowGPALSettings)GPALSettings;
3257 }
3258
3271 [GPALSettings(Description = "Simulate moving the mouse for every action (implies .WithHardware). [false (default)]")]
3272 public static IAllowGPALSettings WithSimulateMouseMovement(bool trueFalse = true)
3273 {
3274 GPALSettings.SimulateMouse = trueFalse;
3275 return (IAllowGPALSettings)GPALSettings;
3276 }
3277
3294 [GPALSettings(Description = "Stop workflow if any selector is not found")]
3295 public static IAllowGPALSettings WithStopOnNotFound(bool trueFalse = true)
3296 {
3297 GPALSettings.StopOnNotFound = trueFalse;
3298 return (IAllowGPALSettings)GPALSettings;
3299 }
3300
3301 [GPALSettings(Description = "Set the default location to store temporary profiles if browser.WithProfileDataDirectory is not used. default [C:\\profiles]")]
3302 public static IAllowGPALSettings WithTempProfileParentDirectory(string tempProfileDirectory)
3303 {
3304 // expanded the same way the settings object expands it, so %TEMP%\gpal means the same thing from
3305 // either one
3306 GPALSettings.TempProfileDirectory = Environment.ExpandEnvironmentVariables(tempProfileDirectory);
3308 }
3309
3323 [GPALSettings(Description = "Handler to receive GPAL Exception events")]
3324 public static IAllowGPALSettings WithExceptionHandler(EventHandler<GPALEventArgs> eventHandler)
3325 {
3326 ExceptionEventHandler = eventHandler;
3327 Exception ex = new Exception("Welcome Exception :)");
3328 GPAL.PublishSimpleEvent(Enums.GPALEventType.EXCEPTION, $"Welcome to GPAL [{Version}] Exception Channel", null, Enums.GPALObjectType.None, ex);
3329 return (IAllowGPALSettings)GPALSettings;
3330 }
3331
3337 [GPALSettings(Description = "Print GPAL published information or exception messages to the output window in VisualStudio. [true (default)]")]
3338 public static IAllowGPALSettings WithPromptBeforeUnexpectedExit(bool trueFalse = true)
3339 {
3340 GPALSettings.PromptBeforeUnexpectedExit = trueFalse;
3341 return (IAllowGPALSettings)GPALSettings;
3342 }
3343
3355 [GPALSettings(Description = "Handler to receive GPAL Information events")]
3356 public static IAllowGPALSettings WithInformationHandler(EventHandler<GPALEventArgs> eventHandler)
3357 {
3358 InformationEventHandler = eventHandler;
3359 GPAL.PublishSimpleEvent(Enums.GPALEventType.INFO, $"Welcome to GPAL [{Version}] Information Channel");
3360 return (IAllowGPALSettings)GPALSettings;
3361 }
3362
3374 [GPALSettings(Description = "Global setting to wait for time in ticks for an element to show up")]
3375 public static IAllowGPALSettings WithWaitFor(int waitForInTicks)
3376 {
3377 GPALSettings.WaitForInMs = waitForInTicks;
3378 return (IAllowGPALSettings)GPALSettings;
3379 }
3380
3381 [GPALSettings(Description = "Take a screenshot with each published event")]
3382 public static IAllowGPALSettings WithTakeEventScreenshot(bool trueOrFalse)
3383 {
3384 GPALSettings.TakeEventScreenshot = trueOrFalse;
3386 }
3387
3388
3389 #endregion <GPAL Settings>
3390 #region <Private>
3391 // nmot sure why i coded this, may have been when i started to code the driver update logic but determined a better way to get the version...
3392 private static void CheckVersions()
3393 {
3394 if (string.Empty.Equals(tmpBrowser?.BrowserVersion))
3395 {
3396 BrowserType browserType = tmpBrowser.BrowserType;
3397
3398 if (BrowserType.Chrome == browserType)
3399 {
3400 DriverHelper.GetChromeVersion(out tmpBrowser.BrowserSettings.Version);
3401 }
3402 else if (BrowserType.Edge == browserType)
3403 {
3404 DriverHelper.GetEdgeVersion(out tmpBrowser.BrowserSettings.Version);
3405 }
3406 else if (BrowserType.FireFox == browserType)
3407 {
3408 DriverHelper.GetFirefoxVersion(out tmpBrowser.BrowserSettings.Version);
3409 }
3410
3411 //ICapabilities capabilities = ((RemoteWebDriver)tmpBrowser.BrowserDriver).Capabilities;
3412 //tmpBrowser.BrowserVersion = capabilities.GetCapability("browserVersion")?.ToString();
3413
3414 // Common WebDriver Capabilities:
3415 // "browserName" - Name of the browser (e.g., "chrome", "firefox", "edge")
3416 // "browserVersion" - Version of the browser (e.g., "120.0.6099.110")
3417 // "platformName" - OS platform ("windows", "linux", "macOS")
3418 // "acceptInsecureCerts" - Whether SSL cert warnings are ignored (true/false)
3419 // "setWindowRect" - Whether the WebDriver can resize the window (true/false)
3420 //
3421 // Chrome-Specific:
3422 // "goog:chromeOptions" - ChromeDriver options (e.g., args, binary path)
3423 // "goog:loggingPrefs" - Logging preferences (e.g., performance, console logs)
3424 //
3425 // Edge-Specific:
3426 // "ms:edgeOptions" - EdgeDriver options (e.g., args, headless mode)
3427 // "ms:edgeChromium" - Whether Edge is running on Chromium (true/false)
3428 //
3429 // Firefox-Specific:
3430 // "moz:firefoxOptions" - FirefoxDriver options
3431 // "moz:headless" - Whether running in headless mode (true/false)
3432 //
3433 // Remote/Grid Execution:
3434 // "webdriver.remote.sessionid" - Session ID for remote WebDriver
3435 // "timeouts" - Dictionary of timeouts (implicit, pageLoad, script)
3436 }
3437 }
3438 private static void SetCatchUncaughtExceptions()
3439 {
3440 if (false == UncaughtExceptionHandlerSet)
3441 {
3442 CheckVersions();
3443
3444 try
3445 {
3446 // Set the unhandled exception mode to force errors to go through our handler.
3447 System.Windows.Forms.Application.SetUnhandledExceptionMode(UnhandledExceptionMode.CatchException);
3448
3449 // Add the event handler for handling thread exceptions to the event.
3450 AppDomain.CurrentDomain.UnhandledException +=
3451 new UnhandledExceptionEventHandler(UncaughtExceptionHandler);
3452
3453 // Hook cleanup handlers
3454 Console.CancelKeyPress += OnCancelKeyPress;
3455 AppDomain.CurrentDomain.ProcessExit += OnProcessExit;
3456
3457 }
3458 catch (Exception ex)
3459 {
3460 PublishSimpleEvent(GPALEventType.EXCEPTION, $"Unable to set uncaught exception handler", null, Enums.GPALObjectType.None, ex);
3461 }
3462
3463 UncaughtExceptionHandlerSet = true;
3464 }
3465 }
3466
3467 private static bool handlingTermination = false;
3468 private static void UncaughtExceptionHandler(object sender, UnhandledExceptionEventArgs args)
3469 {
3470 Exception e = (Exception)args.ExceptionObject;
3471
3472 if (false == handlingTermination)
3473 {
3474 handlingTermination = true;
3475
3476 // the message is what the reader needs. the exception is not passed on, because PublishSimpleEvent
3477 // emits ex.StackTrace from it and that is noise on top of a message we already have.
3478 bool ours = e is GPALException;
3479 string reason = true == ours ? "GPAL exception" : "uncaught exception";
3480
3481 if (ConsoleEvents.HasFlag(GPALEventType.WARNING))
3482 {
3483 PublishSimpleEvent(GPALEventType.WARNING, $">>> Caught {reason}: [{e.Message}][{e.StackTrace}]", null, Enums.GPALObjectType.None);
3484 PublishSimpleEvent(GPALEventType.WARNING, $">>> [{reason}] Cleaning up before terminating.", null, GPALObjectType.None);
3485 }
3486 else if (ConsoleEvents.HasFlag(GPALEventType.CAUTION))
3487 {
3488 PublishSimpleEvent(GPALEventType.CAUTION, $">>> Caught {reason}: [{e.Message}][{e.StackTrace}]", null, Enums.GPALObjectType.None);
3489 PublishSimpleEvent(GPALEventType.CAUTION, $">>> [{reason}] Cleaning up before terminating.", null, GPALObjectType.None);
3490 }
3491
3492 if (false == PublishToConsole)
3493 GPAL.PublishSimpleEvent(GPALEventType.NOTICE, $"Terminating due to [{reason}]: [{e.Message}]");
3494
3495 if (false == PublishToDebug)
3496 Debug.WriteLine($"Terminating due to {reason}: [{e.Message}]");
3497
3498 CleanupAndExit(true == ours ? "GPAL Exception" : "Unhandled Exception");
3499 }
3500 }
3501
3502 private static void OnCancelKeyPress(object sender, ConsoleCancelEventArgs e)
3503 {
3504 //PublishSimpleEvent(GPALEventType.INFO, $">>> Detected Ctrl+C or Ctrl+Break.");
3505
3506 if (false == handlingTermination)
3507 {
3508 handlingTermination = true;
3509
3510 e.Cancel = true; // Prevents immediate termination
3511
3512 if (false == PublishToConsole)
3513 GPAL.PublishSimpleEvent(GPALEventType.NOTICE, $"Terminating due to Ctrl+C or Ctrl+Break.");
3514
3515 if (false == PublishToDebug)
3516 Debug.WriteLine($"Terminating due to Ctrl+C or Ctrl+Break.");
3517
3518 CleanupAndExit("Ctrl+C or Ctrl+Break");
3519 }
3520 }
3521
3522 private static void OnProcessExit(object sender, EventArgs e)
3523 {
3524 // PublishSimpleEvent(GPALEventType.INFO, $">>> Detected process exit or console X.");
3525
3526 if (false == handlingTermination)
3527 {
3528 handlingTermination = true;
3529
3530 if (false == PublishToConsole)
3531 GPAL.PublishSimpleEvent(GPALEventType.NOTICE, $"Terminating due to Process Exit or console X.");
3532
3533 if (false == PublishToDebug)
3534 Debug.WriteLine($"Terminating due to Process Exit or console X.");
3535
3536 CleanupAndExit("Process Exit or console X");
3537 }
3538 }
3539
3540 private static bool _cleanupDone = false;
3541
3542 internal static void CleanupAndExit(string reason)
3543 {
3544 if (false == _cleanupDone) // in case we get multiple kill signals somehow, only run this once..
3545 {
3546 _cleanupDone = true;
3547 try
3548 {
3549 if (ConsoleEvents.HasFlag(GPALEventType.INFO))
3550 PublishSimpleEvent(GPALEventType.INFO, $">>> [{reason}] Cleaning up before terminating.", null, GPALObjectType.None);
3551 else if (ConsoleEvents.HasFlag(GPALEventType.NOTICE))
3552 PublishSimpleEvent(GPALEventType.NOTICE, $">>> [{reason}] Cleaning up before terminating.", null, GPALObjectType.None);
3553
3554 if (false == PublishToConsole)
3555 GPAL.PublishSimpleEvent(GPALEventType.NOTICE, $"[{reason}] Cleaning up before terminating.");
3556
3557 if (false == PublishToDebug)
3558 Debug.WriteLine($"[{reason}] Cleaning up before terminating.");
3559
3560 BrowserHelper.KillAllRunningProcesses(true, null);
3561
3562 // the desktop itself lives until the last window on it is gone, so this only gives up our
3563 // handle rather than closing anything still running there
3564 GenerallyPositive.Browser.HiddenDesktop.CloseAll();
3565 }
3566 catch (Exception ex)
3567 {
3568 PublishSimpleEvent(GPALEventType.EXCEPTION, $"Cleanup error", null, GPALObjectType.None, ex);
3569 }
3570
3571 // Stop timer and publish end time + total runtime
3572 _appTimer.Stop();
3573 string endTime = DateTime.Now.ToString("yyyy-MM-dd HH:mm:ss");
3574 TimeSpan totalRuntime = _appTimer.Elapsed;
3575 string formattedRuntime = $"{totalRuntime.Hours:D2}:{totalRuntime.Minutes:D2}:{totalRuntime.Seconds:D2}.{totalRuntime.Milliseconds:D3}";
3576
3577 if (ConsoleEvents.HasFlag(GPALEventType.INFO))
3578 {
3579 PublishSimpleEvent(GPALEventType.INFO, $">>> Application End Time: [{endTime}] <<<", null, GPALObjectType.None);
3580 PublishSimpleEvent(GPALEventType.INFO, $">>> Total Runtime: [{formattedRuntime}] <<<", null, GPALObjectType.None);
3581 }
3582 else if (ConsoleEvents.HasFlag(GPALEventType.NOTICE))
3583 {
3584 PublishSimpleEvent(GPALEventType.NOTICE, $">>> Application End Time: [{endTime}] <<<", null, GPALObjectType.None);
3585 PublishSimpleEvent(GPALEventType.NOTICE, $">>> Total Runtime: [{formattedRuntime}] <<<", null, GPALObjectType.None);
3586 }
3587
3588 // published and nothing else, the same as the start time and the version are at startup. writing
3589 // these directly when console publishing was off said them anyway, which is the one thing turning
3590 // it off asked us not to do
3591
3592 if (true == GPALSettings.PromptBeforeUnexpectedExit)
3593 {
3594 GPAL.PublishSimpleEvent(GPALEventType.NOTICE, "Press any key to exit...");
3595 Debug.WriteLine("Press any key to exit...");
3596 Console.ReadKey();
3597 }
3598 }
3599
3600 // normal exit, return 0
3601 if (true == reason.Equals("Process Exit or console X"))
3602 Environment.Exit(0);
3603
3604 Environment.Exit(0xdead);
3605 }
3606 #endregion <Private>
3607 }
3608}
Application object that contains the fluent methods to create your Application workflow....
Browser object that contains the fluent methods to create your Browser workflow. Instatiated using G...
Definition Browser.cs:68
Represents a URL with optional pre-navigation storage cleanup / inspection actions....
Definition GPALUrl.cs:53
Fluent YouTube video uploader. Entry point: GPAL.YouTube. Upload phase: WithCredentials -> WithTitle ...
Definition YouTube.cs:181
File-side plumbing behind the fluent chain: writing a unit of work's data out in a delimited format,...
Definition FileHelper.cs:55
static string EnsureDirectoryEndsWithBackslash(string directoryPath)
Adds a trailing backslash to a directory path when it does not already have one, so the path can be c...
Used to pass objects of interest when publishing an Information or Exception Channel message NOTE: P...
Definition GPAL.cs:295
List< GPALElement > WebElements
List of WebElements returned for the given Browser Selector.
Definition GPAL.cs:337
IWebDriver BrowserDriver
The browser driver instantiated and used to connect to the browser.
Definition GPAL.cs:325
IGPALGrid< string > Tokens
GPALGrid [rows/columns] of tokens being used to fill input.
Definition GPAL.cs:354
string Message
The Message being published to the Information/Exception channel.
Definition GPAL.cs:313
GPALElement GPALElement
The current web element.
Definition GPAL.cs:333
SelectorSettings.SelectorPathEntry SelectorPathEntry
The SelectorPathEntry is the current path (css, xpath, text, image, etc) being evaluated/used to find...
Definition GPAL.cs:350
SqlCommand SqlCommand
SqlCommand which threw an exception.
Definition GPAL.cs:362
Exception ExceptionRaised
The Exception being published on the Exception channel.
Definition GPAL.cs:317
Image ScreenShot
Every message is accompanied by an automatic screenshot in case this additional information provides ...
Definition GPAL.cs:321
List< IGPALGrid< string > > TokenList
List of Grids associated with GPALFile.
Definition GPAL.cs:358
UnitOfWork CurrentUOW
The Current Unit of Work for the GPALObject supplied. GPALObject (depending upon context) might also...
Definition GPAL.cs:309
Selector Selector
The current Selector being used.
Definition GPAL.cs:329
DateTime DateTimeStamp
The date/time this event is published.
Definition GPAL.cs:370
AutomationElement AutomationElement
The current AutomationElement.
Definition GPAL.cs:341
List< GPALAutomationElement > AutomationElements
List of AutomationElements returned for the given Application Selector.
Definition GPAL.cs:345
Enums.GPALObjectType GPALObjectType
GPALObjectType enumeration: None, Application, Browser, GPALForm, UnitOfWork.
Definition GPAL.cs:304
dynamic Data
Any type of data that does not fite in the above, converter data, for example.
Definition GPAL.cs:366
dynamic GPALObject
Current context's Application, Browser, GPALForm, UnitOfWork object.
Definition GPAL.cs:300
The Windows clipboard. There is one per machine, so this is a standalone object rather than something...
Class to define database usage. Currently only used for input from a table, sql or stored procedure....
Provides fluent, file-based automation of Excel workbooks: opening one or more workbooks (including w...
Definition GPALExcel.cs:46
GPAL File object instantied with GPAL.File Used to load tokens into a GPALGrid [rows/columns].
Definition GPALFile.cs:36
List< string > Filenames
Get the list of filenames.
Definition GPALFile.cs:540
A single item shared by GPAL menu bars and toolbars. Because it derives from GPALControl it speaks th...
A GPAL button instantiated with GPAL.Button for use on GPAL forms. Callback EventHandler is invoked ...
A GPAL chart instantiated with GPAL.Chart for use on GPAL forms. Callback EventHandler is invoked on...
A GPAL checkbox instantiateds with GPAL,Checkbox for use on GPAL forms. Callback EventHandler is inv...
A dropdown list (ComboBox) for selecting one option from a predefined set of choices....
A grid control instantiated with GPAL.DataGridView for use on GPAL forms, displaying the contents of ...
A date/time picker control instantiated with GPAL.DateTimePicker for use on GPAL forms.
A file/folder selector control instantiated with GPAL.FileSelector for use on GPAL forms....
Form object that contains the fluent methods to create interactive forms. Instatiated using GPAL....
Definition GPALForm.cs:64
A titled box around a set of controls, instantiated with GPAL.GroupBox and laid out like any other co...
A GPAL single line input instantiated with GPAL.Input for use on GPAL forms. Callback EventHandler i...
A GPAL lable instantiated with GPAL.Label for use on GPAL forms. Callback EventHandler is invoked on...
A list view control capable of displaying data in Details, List, LargeIcon, SmallIcon,...
A menu bar docked to the top of a GPAL form. Open a top-level menu with WithMenu(string),...
A numeric up/down spinner control instantiated with GPAL.NumericUpDown for use on GPAL forms.
A progress indicator control that shows completion percentage or indeterminate activity....
A GPAL radiobutton instantiated with GPAL.RadioButton for use GPAL forms. Callback EventHandler is i...
A read-only display control for rendering simple Markdown - headings (#, ##, ###),...
A GPAL slider instantiated with GPAL.Slider, for picking a number from a range by dragging....
Marks where one pane of a GPALSplitter ends and the other begins, added to the splitter with GPAL....
Two panes with a bar between them, instantiated with GPAL.Splitter and laid out like any other contro...
A status bar (usually docked at the bottom of the form) that displays real-time messages,...
A GPAL tab instantiated with GPAL.Tab for use on GPAL forms. Callback EventHandler is invoked on the...
A GPAL multiline textare instantiated with GPAL.TextArea for use on GPAL forms. Callback EventHandle...
A toolbar docked below the menu bar, containing GPALBarItem buttons and optional separators.
A tree view control instantiated with GPAL.TreeView for use on GPAL forms.
static byte VK_ALT
Alt key virtual keycode (VK) for use with keybd_event. https://docs.microsoft.com/en-us/windows/win32...
Definition GPAL.cs:58
static IAllowProgressBarControlSettings ProgressBar
A progress bar control for GPAL forms. Displays task completion percentage or indeterminate (marquee...
Definition GPAL.cs:1910
static IAllowMenuBarControlSettings MenuBar
A menu bar docked to the top of a GPAL form. Open a top-level menu with WithMenu("TopMenu"),...
Definition GPAL.cs:2018
static IAllowGPALSettings WithNoFallbackActions(bool trueFalse=true)
GPAL has fallback alternatives for actions should they fail, generally applies to selenium....
Definition GPAL.cs:3088
static IAllowGPALSettings WithHandlerEvents(GPALEventType eventTypes)
Which event types reach the handlers given to WithInformationHandler and WithExceptionHandler....
Definition GPAL.cs:3023
static IAllowControlSettingsAndChecked Checkbox
Instantiates a new GPALCheckbox for use on a GPAL form. Callback EventHandler is invoked on the GPAL...
Definition GPAL.cs:1514
static byte VK_END
End key virtual keycode (VK) for use with keybd_event.
Definition GPAL.cs:78
static byte VK_F10
F10 key virtual keycode (VK) for use with keybd_event.
Definition GPAL.cs:66
static IAllowGPALSettings RemoveLogger(IGPALLogger logger)
Stops publishing events to a logger.
Definition GPAL.cs:3002
static Browser.IHiddenDesktops HiddenDesktop
Every hidden desktop this process has made, and the screen itself. Peek puts one on the screen and pu...
Definition GPAL.cs:2296
static IAllowRichTextBoxControlSettings RichTextBox
Instantiates a new GPALRichTextBox for use on a GPAL form. Renders Markdown (headings,...
Definition GPAL.cs:2204
static IAllowApplicationOpenOrParameters Application
Instantiates a new fluent Application object.
Definition GPAL.cs:597
static GPALSpliter SplitRight
Marks the end of a splitter's left pane. Everything added after it is the right pane.
Definition GPAL.cs:2143
static IAllowControlSettingsAndChartSettings Chart
Instantiates a new GPALChart for use on GPAL forms. Callback EventHandler is invoked on the GPALChar...
Definition GPAL.cs:1471
static IAllowLoggingSettings Logger
New GPAL Logger.
Definition GPAL.cs:545
static IAllowSelectorSettings Selector
Instantiates a new fluent GPAL Selector object used to locate an element. Selectors are for browsers...
Definition GPAL.cs:729
static IAllowGraphicSettings CaptureScreen(Browser.IBrowser browser)
Take a screenshot.
Definition GPAL.cs:2515
static IAllowGPALSettings WithPublishToDebug(GPALEventType eventType=GPALEventType.INFO|GPALEventType.WARNING|GPALEventType.ERROR|GPALEventType.EXCEPTION|GPALEventType.NOTICE|GPALEventType.CAUTION|GPALEventType.FAILURE)
Indicate which event types to print to the console. Default is none.
Definition GPAL.cs:2974
static IAllowConverterInput Converter
New GPAL Convertor.
Definition GPAL.cs:571
static byte VK_LMENU
Left Alt key virtual keycode (VK) for use with keybd_event.
Definition GPAL.cs:130
static IAllowGPALSettings WithInformationHandler(EventHandler< GPALEventArgs > eventHandler)
Set the handler to receive Information messages from GPAL which you might like to know about....
Definition GPAL.cs:3356
static void EmitTable< TRow, TCol, TValue >(string title, IDictionary<(TRow row, TCol col), TValue > data, string cornerLabel="", Func< TValue, string > formatCell=null, string emptyCell="-")
Emits an ASCII table for any 2-axis keyed dictionary as a single INFO event: row keys down the side,...
Definition GPAL.cs:2761
static GPALSpliter SplitBottom
Marks the end of a splitter's top pane. Everything added after it is the bottom pane.
Definition GPAL.cs:2146
static Browser.IAllowElementSetting ElementAssistant(Selector selector)
Instantiates a new Browser ElementAssistant object with fluent methods to help you manipulate WebElem...
Definition GPAL.cs:1300
static byte VK_BACK
Backspace key virtual keycode (VK) for use with keybd_event.
Definition GPAL.cs:146
static IAllowGPALSettings CallIfFound(Browser.Browser.CallIfDelegate callIfFound)
Global Browser CallIfFound handler called for every selector in every Unit of Work.
Definition GPAL.cs:2861
static IAllowPictureControlSettings Picture
A picture control for GPAL forms. Shows an image, from a file or from an Image already in hand....
Definition GPAL.cs:1849
static IAllowGPALSettings WithPublishStackTrace(bool trueFalse=true)
Print the stack trace along with the published GPAL message.
Definition GPAL.cs:3035
static IAllowToolbarControlSettings Toolbar
A toolbar docked below the menu bar, holding GPAL.BarItem buttons and optional separators.
Definition GPAL.cs:2071
static IAllowDateTimePickerControlSettings DateTimePicker
Instantiates a new GPALDateTimePicker for use on a GPAL form. Callback EventHandler is invoked on th...
Definition GPAL.cs:1610
static uint KEYEVENTF_EXTENDEDKEY
The extended-key flag indicates whether the keystroke message originated from one of the additional k...
Definition GPAL.cs:161
static IAllowControlSettingsPlaceholderAndPassword Input
Instantiates a new one-line GPALInput control for use on a GPAL form. Callback EventHandler is invok...
Definition GPAL.cs:1693
static byte VK_RWIN
Right Windows key virtual keycode (VK) for use with keybd_event.
Definition GPAL.cs:122
static Application.IAllowForSelector ElementAssistant(Application.Application application)
Instantiates a new Application ElementAssistant with fluent methods to help you manipulate Automation...
Definition GPAL.cs:1331
static IAllowSplitterControlSettings Splitter
Instantiates a new GPALSplitter for use on a GPAL form: two panes with a bar between them,...
Definition GPAL.cs:2134
static IAllowFormSettings Form
Instantiates a new GPALForm for creating simple interactive user interfaces.
Definition GPAL.cs:1369
static IAllowGridActions< string > Grid
New GPALGrid<string></string> (rows/columns).
Definition GPAL.cs:532
static IAllowControlSettingsAndChecked RadioButton
Instantiates a new GPALRadioButton for use on a GPAL form. Callback EventHandler is invoked on the G...
Definition GPAL.cs:1959
static IAllowComboBoxControlSettings ComboBox
Instantiates a new GPALComboBox for use on a GPAL form. Callback(s) set via WhenSelectedDo are invok...
Definition GPAL.cs:1553
static IAllowBarItemSettings BarItem
A single item shared by menu bars and toolbars: text, optional image, optional shortcut key,...
Definition GPAL.cs:2045
static string OAuthTokenUrl
Where an OAuth redirect lands and the token is read back from, e.g. http://localhost:3117/....
Definition GPAL.cs:2313
static byte VK_RETURN
Enter/Return key virtual keycode (VK) for use with keybd_event.
Definition GPAL.cs:98
static byte VK_SCROLL
Scroll Lock key virtual keycode (VK) for use with keybd_event.
Definition GPAL.cs:126
static IAllowControlSettingsAndDefault Button
Instantiates a new GPALButton for use on GPAL forms. Callback EventHandler is invoked on the GPALBut...
Definition GPAL.cs:1414
static byte VK_DOWN
Down Arrow key virtual keycode (VK) for use with keybd_event.
Definition GPAL.cs:86
static IAllowDataGridViewControlSettings DataGridView
Instantiates a new GPALDataGridView for use on a GPAL form. Callback EventHandler is invoked on the ...
Definition GPAL.cs:1581
static IAllowGPALSettings AppCallIfFound(Application.Application.CallIfDelegate callIfFound)
Global Browser CallIfFound handler called for every selector in every Unit of Work.
Definition GPAL.cs:2839
static IAllowRequestSettings Request
Start describing an API request for .Fetch to issue from inside the page, so it carries the session t...
Definition GPAL.cs:760
static IAllowGPALSettings WithPublishToControl(GPALTextArea control)
Writes GPAL events to a text area on a form, as WithPublishToConsole does to a console....
Definition GPAL.cs:2910
static IAllowRESTEndpoint RESTClient
Instantiate a new fluent RESTClient.
Definition GPAL.cs:950
static IAllowGPALSettings WithPublishToLogger(IGPALLogger logger)
Print the published information or exception messages to the supplied database. Uses Database Create ...
Definition GPAL.cs:2991
static IAllowGPALSettings CallOnFail(Browser.Browser.CallOnFailDelegate callOnFail)
Add a handler to be called when something fails that the workflow is in a position to decide about,...
Definition GPAL.cs:3050
static byte VK_CONTROL_LEFT
Left Control key virtual keycode (VK) for use with keybd_event.
Definition GPAL.cs:110
static byte VK_SHIFT_RIGHT
Right Shift key virtual keycode (VK) for use with keybd_event.
Definition GPAL.cs:102
static string MyMethodName()
Returns the calling methods name (used in Information/Exception messages). Instead of harcoding meth...
Definition GPAL.cs:2536
static IAllowControlSettingsAndUrl LinkLabel
Instantiates a new GPALLinkLabel for use on a GPAL form: a link that opens its address in the default...
Definition GPAL.cs:1718
static byte VK_SHIFT_LEFT
Left Shift key virtual keycode (VK) for use with keybd_event.
Definition GPAL.cs:106
static IAllowControlSettingsAndFontSettings Label
Instantiates a new GPALLabel for use on a GPAL form. Callback EventHandler is invoked on the GPALLab...
Definition GPAL.cs:1750
static Selector CssSelector(string css, string selectorName=null)
Helper for shorthand notation.
Definition GPAL.cs:1062
static IAllowListViewControlSettings ListView
Instantiates a new GPALListView for use on a GPAL form. Callback EventHandler is invoked on the GPAL...
Definition GPAL.cs:1790
static IAllowGPALSettings WithExceptionHandler(EventHandler< GPALEventArgs > eventHandler)
Set the handler to receive handled Exception messages from GPAL. GPAL also sets an unhandled excepti...
Definition GPAL.cs:3324
static IAllowGPALSettings WithWaitFor(int waitForInTicks)
Global setting to wait for time in ticks for an element to show up. Applied to every element in every...
Definition GPAL.cs:3375
static IAllowGPALSettings AppCallIfNotFound(Application.Application.CallIfDelegate callIfNotFound)
Global Browser CallIfNotFound handler called for every selector in every Unit of Work.
Definition GPAL.cs:2850
static IAllowGPALSettings CallIfNotFound(Browser.Browser.CallIfDelegate callIfNotFound)
Global Browser CallIfNotFound handler called for every selector in every Unit of Work.
Definition GPAL.cs:2872
static IAllowTreeViewControlSettings TreeView
Instantiates a new GPALTreeView for use on a GPAL form. Callback EventHandler is invoked on the GPAL...
Definition GPAL.cs:2236
static byte VK_APPS
Application (Menu) key virtual keycode (VK) for use with keybd_event.
Definition GPAL.cs:138
static byte VK_NEXT
Page Down key virtual keycode (VK) for use with keybd_event.
Definition GPAL.cs:74
static void EmitRuntimeTable(string title, IDictionary<(BrowserType bt, AutomationEngine ae), TimeSpan > runtimes)
Convenience wrapper over EmitTable<TRow,TCol,TValue> for test-program bt/ae[/headless] loops: browser...
Definition GPAL.cs:2822
static IAllowGPALSettings WithAutoUpdateWebDriver(bool trueFalse=true)
Automatically download and unzip the web driver that matches the version of the browser being run....
Definition GPAL.cs:3112
static IAllowGPALSettings WithControlEvents(GPALEventType eventTypes)
Which event types reach the control given to WithPublishToControl. Defaults to all....
Definition GPAL.cs:2928
static IAllowNumericUpDownControlSettings NumericUpDown
Instantiates a new GPALNumericUpDown for use on a GPAL form. Callback EventHandler is invoked on the...
Definition GPAL.cs:1821
static IAllowCredentialServiceType Credentials
Return a new credential manager.
Definition GPAL.cs:965
static IAllowGPALSettings WithAutomationEngine(AutomationEngine automationEngine)
Set the automation type to use for all browsers. NOTE: can be overridden at the selector level (to ch...
Definition GPAL.cs:3124
static IAllowSliderControlSettings Slider
A slider control for GPAL forms. Picks a number from a range by dragging, and exposes that range to ...
Definition GPAL.cs:1879
static IAllowEmailServerSettings Mail
New GPAL Mail handler.
Definition GPAL.cs:558
static IAllowGPALSettings WithStopOnNotFound(bool trueFalse=true)
Global setting instructing GPAL to terminate if any Selector cannot find any elements....
Definition GPAL.cs:3295
static IAllowGPALSettings WithPublishToConsole(GPALEventType eventType=GPALEventType.INFO|GPALEventType.WARNING|GPALEventType.ERROR|GPALEventType.EXCEPTION|GPALEventType.NOTICE|GPALEventType.CAUTION|GPALEventType.FAILURE)
Print the published information or exception messages to the console.
Definition GPAL.cs:2883
static IAllowGPALSettings WithImageMatchingPercentage(int matchPercent)
Sets the image matching percentage for image Selectors. .
Definition GPAL.cs:3226
static IAllowControlSettings Tab
Instantiates a new GPALTab for use on a GPAL form. Callback EventHandler is invoked on the GPALTab....
Definition GPAL.cs:2106
static IAllowGPALSettings WithExceptionHandlerEvents(GPALEventType eventTypes)
Which event types reach the handler given to WithExceptionHandler. Defaults to all.
Definition GPAL.cs:2962
static IAllowGPALSettings WithYamlAliasLimit(int nodeLimit)
How many nodes a YAML payload may hold before GPAL stops writing aliases for shared references....
Definition GPAL.cs:3253
static byte VK_LWIN
Left Windows key virtual keycode (VK) for use with keybd_event.
Definition GPAL.cs:118
static IAllowGPALSettings WithDefaultErrorPlaceholder(string message="##UNABLE TO GET##")
When GPAL is unable to retrieve data from an element, this message will be placed in the grid....
Definition GPAL.cs:3062
static IAllowGPALSettings WithDownloadTimeoutInSec(int seconds)
Set the default timeout time in seconds to wait for a download to start The default is 60 seconds.
Definition GPAL.cs:3074
static byte VK_UP
Up Arrow key virtual keycode (VK) for use with keybd_event.
Definition GPAL.cs:90
static IAllowAIProvider AI
Entry point for the GPAL AI subsystem, providing a fluent interface for AI tasks.
Definition GPAL.cs:461
static byte VK_SPACE
Space bar key virtual keycode (VK) for use with keybd_event.
Definition GPAL.cs:142
static void PublishSimpleEvent(GPALEventType gPALEventType, string msg, dynamic gPALObject=null, Enums.GPALObjectType gPALObjectType=GPALObjectType.None, Exception ex=null)
Publish a message to either the information channel or exception channel (if exception passed in) Pub...
Definition GPAL.cs:2568
static byte VK_DELETE
Delete key virtual keycode (VK) for use with keybd_event.
Definition GPAL.cs:62
static IAllowGPALSettings WithTempDirectory(string tempPath)
Where GPAL puts files it had to create but nobody asked to keep, such as a GPALFile given a url rathe...
Definition GPAL.cs:3151
static IAllowGPALSettings WithSimulateMouseMovement(bool trueFalse=true)
Global setting to simulate a human moving the mouse to the next point for all commands....
Definition GPAL.cs:3272
static IAllowStatusStripControlSettings StatusStrip
A status strip control (docked at the bottom) for GPAL forms. Displays real-time status messages,...
Definition GPAL.cs:1988
static IAllowDriveCredentials GoogleDrive
New GoogleDrive workflow.
Definition GPAL.cs:986
static IAllowGroupBoxControlSettings GroupBox
Instantiates a new GPALGroupBox for use on a GPAL form: a titled box around a set of controls,...
Definition GPAL.cs:2140
static byte VK_PRIOR
Page Up key virtual keycode (VK) for use with keybd_event.
Definition GPAL.cs:70
static IAllowClipboardInput Clipboard
Instantiates a new fluent Database SETTINGS object. This data object defines settings for use by ....
Definition GPAL.cs:687
static IAllowControlSettingsAndPlaceholder TextArea
Instantiates a new GPALTextArea for use on a GPAL form. Callback EventHandler is invoked on the GPAL...
Definition GPAL.cs:2170
static Browser.IAllowScheduledWorkflow Workflow
Runs whole workflows at once, each building and owning whatever it drives, so one can run Chrome on O...
Definition GPAL.cs:2272
static IAllowGPALSettings WithAllThatMatch(int matchCount=int.MaxValue)
Global value for selector matches so it's not set UOW at a time.
Definition GPAL.cs:3099
static byte VK_TAB
Tab key virtual keycode (VK) for use with keybd_event.
Definition GPAL.cs:94
static uint KEYEVENTF_KEYUP
Indicates a keyup keystroke is being sent. For use with keybd_event.
Definition GPAL.cs:171
static IAllowGPALSettings WithDriverLocation(string driverPath)
The location of the browser drivers to be used for each GPAL.Browser instance. This can be overridde...
Definition GPAL.cs:3137
static IAllowBrowserTypeOrGoto Browser
Instantiates a new fluent Browser object.
Definition GPAL.cs:628
static IAllowGPALSettings WithTypingDelay(int delayInTicks)
Sets the ceiling for the inter-character typing delay used by SendString across every automation engi...
Definition GPAL.cs:3208
static IAllowGPALSettings WithPromptBeforeUnexpectedExit(bool trueFalse=true)
Prompt 'Press any key...' before terminating due to an uncaught exception. GPAL tries to handle all e...
Definition GPAL.cs:3338
static IAllowGPALSettings WithInformationHandlerEvents(GPALEventType eventTypes)
Which event types reach the handler given to WithInformationHandler. Defaults to all.
Definition GPAL.cs:2945
static byte VK_HOME
Home key virtual keycode (VK) for use with keybd_event.
Definition GPAL.cs:82
static IAllowFileSelectorControlSettings FileSelector
Instantiates a new GPALFileSelector for use on a GPAL form. Opens a standard file or folder selectio...
Definition GPAL.cs:1662
static byte VK_RMENU
Right Alt key virtual keycode (VK) for use with keybd_event.
Definition GPAL.cs:134
static byte VK_CONTROL_RIGHT
Right Control key virtual keycode (VK) for use with keybd_event.
Definition GPAL.cs:114
static IAllowSpreadsheetSelection GoogleSheets
New Googlesheets workflow.
Definition GPAL.cs:978
static IAllowFileName File
Instantiates a new fluent File SETTINGS object. This data object defines settings for use by the ....
Definition GPAL.cs:519
Provides configurable logging to a file (in any format supported by GPALConverter) or to a GPALDataba...
Definition GPALLogger.cs:47
Provides a fluent API for configuring, sending, and receiving email via SMTP, IMAP,...
Definition GPALMail.cs:55
Describes an API request for .Fetch to issue from inside the page, so it carries the session the brow...
GPAL Selector used to locate Application and Browser elements. Instantiated with GPAL....
Definition Selector.cs:56
Browser.Browser Browser
Browser associated with this selector.
Definition Selector.cs:758
IAllowSelectorSettings WithXPath(string xPath)
Define XPath used to find elements Both.
Definition Selector.cs:180
An entry defining a selector locator, a selector path.
Everything revolves around the Unit of Work. A Unit of Work is defined as one or more selectors betw...
Definition UnitOfWork.cs:39
Starting interface - only allows setting the target URL.
Every hidden desktop this process has made, and the screen itself. Peek and Return move the screen,...
What a database is, not what to do with it: the connection, the command, and the definitions of the p...
Finds a picture on a capture of the screen or of a browser. The GPAL.Image package provides one and r...