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, "", "", false, true);
}
}
///
/// 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;
}
}
}
}