using OpenQA.Selenium; using RuriLib.LS; using System; using System.Collections.Generic; using System.Collections.ObjectModel; using System.Drawing; using System.IO; using System.Threading; using System.Windows.Media; namespace RuriLib { /// /// The available ways to address an element. /// public enum ElementLocator { /// The id of the element. Id, /// The class of the element. Class, /// The name of the element. Name, /// The tag of the element. Tag, /// The CSS selector of the element. Selector, /// The xpath of the element. XPath } /// /// The actions that can be performed on an element. /// public enum ElementAction { /// Clears the text of an input element. Clear, /// Sends keystrokes to an input element. SendKeys, /// Types keystrokes into an input element with random delays between each keystroke, like a human would. SendKeysHuman, /// Clicks an element. Click, /// Submits a form element. Submit, /// Gets the text inside an element. GetText, /// Gets a given attribute of an element. GetAttribute, /// Checks if the element is currently displayed on the page. IsDisplayed, /// Checks if the element is enabled on the page. IsEnabled, /// Checks if the element is selected. IsSelected, /// Retrieves the X coordinate of the top-left corner of the element. LocationX, /// Retrieves the Y coordinate of the top-left corner of the element. LocationY, /// Retrieves the width of the element. SizeX, /// Retrieves the height of the element. SizeY, /// Takes a screenshot of the element. Screenshot, /// Switches to the iframe element. SwitchToFrame, /// Waits until the element appears in the DOM (up to a specified timeout). WaitForElement } /// /// A block that can perform an action on an element inside an HTML page. /// public class SBlockElementAction : BlockBase { private ElementLocator locator = ElementLocator.Id; /// The element locator. public ElementLocator Locator { get { return locator; } set { locator = value; OnPropertyChanged(); } } private string elementString = ""; /// The value of the locator. public string ElementString { get { return elementString; } set { elementString = value; OnPropertyChanged(); } } private int elementIndex = 0; /// The index of the element in case the locator matches more than one. public int ElementIndex { get { return elementIndex; } set { elementIndex = value; OnPropertyChanged(); } } private ElementAction action = ElementAction.Clear; /// The action to be performed on the element. public ElementAction Action { get { return action; } set { action = value; OnPropertyChanged(); } } private string input = ""; /// The input data. public string Input { get { return input; } set { input = value; OnPropertyChanged(); } } private string outputVariable = ""; /// The name of the output variable. public string OutputVariable { get { return outputVariable; } set { outputVariable = value; OnPropertyChanged(); } } private bool isCapture = false; /// Whether the output variable should be marked for Capture. public bool IsCapture { get { return isCapture; } set { isCapture = value; OnPropertyChanged(); } } private bool recursive = false; /// Whether the GetText and GetAttribute actions should be executed on all the elements matched by the locator and return a list of values. public bool Recursive { get { return recursive; } set { recursive = value; OnPropertyChanged(); } } /// /// Creates an element action block. /// public SBlockElementAction() { Label = "ELEMENT ACTION"; } /// public override BlockBase FromLS(string line) { // Trim the line var input = line.Trim(); // Parse the label if (input.StartsWith("#")) Label = LineParser.ParseLabel(ref input); /* * Syntax: * ELEMENTACTION ID "id" 0 RECURSIVE? ACTION ["INPUT"] [-> VAR/CAP "OUTPUT"] * */ Locator = (ElementLocator)LineParser.ParseEnum(ref input, "LOCATOR", typeof(ElementLocator)); ElementString = LineParser.ParseLiteral(ref input, "STRING"); if (LineParser.Lookahead(ref input) == TokenType.Boolean) LineParser.SetBool(ref input, this); else if (LineParser.Lookahead(ref input) == TokenType.Integer) ElementIndex = LineParser.ParseInt(ref input, "INDEX"); Action = (ElementAction)LineParser.ParseEnum(ref input, "ACTION", typeof(ElementAction)); if (input == "") return this; if (LineParser.Lookahead(ref input) == TokenType.Literal) Input = LineParser.ParseLiteral(ref input, "INPUT"); // Try to parse the arrow, otherwise just return the block as is with default var name and var / cap choice if (LineParser.ParseToken(ref input, TokenType.Arrow, false) == "") return this; // Parse the VAR / CAP try { var varType = LineParser.ParseToken(ref input, TokenType.Parameter, true); if (varType.ToUpper() == "VAR" || varType.ToUpper() == "CAP") IsCapture = varType.ToUpper() == "CAP"; } catch { throw new ArgumentException("Invalid or missing variable type"); } // Parse the variable/capture name try { OutputVariable = LineParser.ParseToken(ref input, TokenType.Literal, true); } catch { throw new ArgumentException("Variable name not specified"); } return this; } /// public override string ToLS(bool indent = true) { var writer = new BlockWriter(GetType(), indent, Disabled); writer .Label(Label) .Token("ELEMENTACTION") .Token(Locator) .Literal(ElementString); if (Recursive) writer.Boolean(Recursive, "Recursive"); else if (ElementIndex != 0) writer.Integer(ElementIndex, "ElementIndex"); writer .Indent() .Token(Action) .Literal(Input, "Input"); if (!writer.CheckDefault(OutputVariable, "OutputVariable")) { writer .Arrow() .Token(IsCapture ? "CAP" : "VAR") .Literal(OutputVariable); } return writer.ToString(); } /// public override void Process(BotData data) { base.Process(data); if (data.Driver == null) { data.Log(new LogEntry("Open a browser first!", Colors.White)); throw new Exception("Browser not open"); } // Find the element IWebElement element = null; ReadOnlyCollection elements = null; try { if(action != ElementAction.WaitForElement) { elements = FindElements(data); element = elements[elementIndex]; } } catch { data.Log(new LogEntry("Cannot find element on the page", Colors.White)); } List outputs = new List(); try { switch (action) { case ElementAction.Clear: element.Clear(); break; case ElementAction.SendKeys: element.SendKeys(ReplaceValues(input, data)); break; case ElementAction.Click: element.Click(); UpdateSeleniumData(data); break; case ElementAction.Submit: element.Submit(); UpdateSeleniumData(data); break; case ElementAction.GetText: if (recursive) foreach (var elem in elements) outputs.Add(elem.Text); else outputs.Add(element.Text); break; case ElementAction.GetAttribute: if (recursive) foreach (var elem in elements) outputs.Add(elem.GetAttribute(ReplaceValues(input, data))); else outputs.Add(element.GetAttribute(ReplaceValues(input, data))); break; case ElementAction.IsDisplayed: outputs.Add(element.Displayed.ToString()); break; case ElementAction.IsEnabled: outputs.Add(element.Enabled.ToString()); break; case ElementAction.IsSelected: outputs.Add(element.Selected.ToString()); break; case ElementAction.LocationX: outputs.Add(element.Location.X.ToString()); break; case ElementAction.LocationY: outputs.Add(element.Location.Y.ToString()); break; case ElementAction.SizeX: outputs.Add(element.Size.Width.ToString()); break; case ElementAction.SizeY: outputs.Add(element.Size.Height.ToString()); break; case ElementAction.Screenshot: var image = GetElementScreenShot(data.Driver, element); SaveScreenshot(image, data); break; case ElementAction.SwitchToFrame: data.Driver.SwitchTo().Frame(element); break; case ElementAction.WaitForElement: var ms = 0; // Currently waited milliseconds var max = 10000; try { max = int.Parse(input) * 1000; } catch { }// Max ms to wait var found = false; while(ms < max) { try { elements = FindElements(data); element = elements[0]; found = true; break; } catch { ms += 200; Thread.Sleep(200); } } if (!found) { data.Log(new LogEntry("Timeout while waiting for element", Colors.White)); } break; case ElementAction.SendKeysHuman: var toSend = ReplaceValues(input, data); var rand = new Random(); foreach(char c in toSend) { element.SendKeys(c.ToString()); Thread.Sleep(rand.Next(100, 300)); } break; } } catch { data.Log(new LogEntry("Cannot execute action on the element", Colors.White)); } data.Log(new LogEntry(string.Format("Executed action {0} on the element with input {1}", action, ReplaceValues(input, data)), Colors.White)); if (outputs.Count != 0) { InsertVariables(data, isCapture, recursive, outputs, outputVariable, "", ""); } } /// /// Screenshots an element on the page. /// /// The selenium driver /// The element to screenshot /// The bitmap screenshot of the element public static Bitmap GetElementScreenShot(IWebDriver driver, IWebElement element) { Screenshot sc = ((ITakesScreenshot)driver).GetScreenshot(); var img = Image.FromStream(new MemoryStream(sc.AsByteArray)) as Bitmap; return img.Clone(new Rectangle(element.Location, element.Size), img.PixelFormat); } private ReadOnlyCollection FindElements(BotData data) { switch (locator) { case ElementLocator.Id: return data.Driver.FindElements(By.Id(ReplaceValues(elementString, data))); case ElementLocator.Class: return data.Driver.FindElements(By.ClassName(ReplaceValues(elementString, data))); case ElementLocator.Name: return data.Driver.FindElements(By.Name(ReplaceValues(elementString, data))); case ElementLocator.Tag: return data.Driver.FindElements(By.TagName(ReplaceValues(elementString, data))); case ElementLocator.Selector: return data.Driver.FindElements(By.CssSelector(ReplaceValues(elementString, data))); case ElementLocator.XPath: return data.Driver.FindElements(By.XPath(ReplaceValues(elementString, data))); default: return null; } } } }