Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Add selenium grid support #93

Merged
merged 1 commit into from
Sep 28, 2015
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
3 changes: 3 additions & 0 deletions Winium/Winium.StoreApps.Driver/CommandLineOptions.cs
Original file line number Diff line number Diff line change
Expand Up @@ -30,6 +30,9 @@ internal class CommandLineOptions
[Option("bound-device-name", Required = false, HelpText = "strict name of emulator to bind with driver. Driver will be able to start sessions only on this device, if session will specify deviceName that is not a substring of bound device name, then an error will occur. Use this option to run tests in parallel on differen driver-emulator pairs connected to Selenium Grid on same host.")]
public string BoundDeviceName { get; set; }

[Option("nodeconfig", Required = false, HelpText = "configuration JSON file to register driver with selenium grid")]
public string NodeConfig { get; set; }

#endregion

#region Public Methods and Operators
Expand Down
62 changes: 33 additions & 29 deletions Winium/Winium.StoreApps.Driver/Listener.cs
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,6 @@
#region

using System;
using System.Globalization;
using System.IO;
using System.Net;
using System.Net.Sockets;
Expand All @@ -14,13 +13,11 @@

public class Listener
{
#region Static Fields

private static string urnPrefix;
#region Fields

#endregion
private readonly Uri baseAddress;

#region Fields
private readonly NodeRegistrar nodeRegistrar;

private UriDispatchTables dispatcher;

Expand All @@ -32,35 +29,33 @@ public class Listener

#region Constructors and Destructors

public Listener(int listenerPort)
public Listener(int listenerPort, string urlBase, string nodeConfigFile)
{
urlBase = NormalizePrefix(urlBase);
this.Port = listenerPort;
}

#endregion

#region Public Properties

public static string UrnPrefix
{
get
if (!string.IsNullOrWhiteSpace(nodeConfigFile))
{
return urnPrefix;
}

set
{
if (!string.IsNullOrEmpty(value))
if (!urlBase.Equals("wd/hub"))
{
// Normalize prefix
urnPrefix = "/" + value.Trim('/');
Logger.Warn(
"--url-base '{0}' will be overriden and set to 'wd/hub' because --nodeconfig option was specified",
urlBase);
}

urlBase = "wd/hub";

this.nodeRegistrar = new NodeRegistrar(nodeConfigFile, "localhost", this.Port);
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

this.Port с большой буквы зачесалась

}

this.baseAddress = new UriBuilder("http", "localhost", this.Port, urlBase).Uri;
}

public int Port { get; private set; }
#endregion

public Uri Prefix { get; private set; }
#region Public Properties

public int Port { get; private set; }

#endregion

Expand All @@ -71,13 +66,17 @@ public void StartListening()
try
{
this.listener = new TcpListener(IPAddress.Any, this.Port);

this.Prefix = new Uri(string.Format(CultureInfo.InvariantCulture, "http://localhost:{0}", this.Port));
this.dispatcher = new UriDispatchTables(new Uri(this.Prefix, UrnPrefix));
this.dispatcher = new UriDispatchTables(this.baseAddress);
this.executorDispatcher = new CommandExecutorDispatchTable();

// Start listening for client requests.
this.listener.Start();
Logger.Info("RemoteWebDriver instances should connect to: {0}", this.baseAddress);

if (this.nodeRegistrar != null)
{
this.nodeRegistrar.Register();
}

// Enter the listening loop
while (true)
Expand Down Expand Up @@ -141,13 +140,18 @@ public void StopListening()

#region Methods

private static string NormalizePrefix(string prefix)
{
return string.IsNullOrWhiteSpace(prefix) ? string.Empty : prefix.Trim('/');
}

private string HandleRequest(HttpRequest acceptedRequest)
{
var firstHeaderTokens = acceptedRequest.StartingLine.Split(' ');
var method = firstHeaderTokens[0];
var resourcePath = firstHeaderTokens[1];

var uriToMatch = new Uri(this.Prefix, resourcePath);
var uriToMatch = new Uri(this.baseAddress, resourcePath);
var matched = this.dispatcher.Match(method, uriToMatch);

if (matched == null)
Expand Down
243 changes: 243 additions & 0 deletions Winium/Winium.StoreApps.Driver/NodeRegistrar.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,243 @@
namespace Winium.StoreApps.Driver
{
#region

using System;
using System.IO;
using System.Net;
using System.Threading;
using System.Timers;

using Newtonsoft.Json;
using Newtonsoft.Json.Linq;

using Timer = System.Timers.Timer;

#endregion

public class NodeRegistrar
{
#region Fields

private readonly string configFilePath;

private readonly string defaultHost;

private readonly int defaultPort;

private Timer autoRegisterTimer;

private string data;

private NodeRegistrarConfiguration registrarConfiguration;

#endregion

#region Constructors and Destructors

public NodeRegistrar(string configFilePath, string defaultHost, int defaultPort)
{
this.configFilePath = configFilePath;
this.defaultHost = defaultHost;
this.defaultPort = defaultPort;
}

#endregion

#region Public Methods and Operators

public void Register()
{
new Thread(
() =>
{
Thread.CurrentThread.IsBackground = true;
this.RegisterNode();
}).Start();
}

#endregion

#region Methods

private void AutoRegisterEvent(object source, ElapsedEventArgs e)
{
if (!this.IsAlreadyRegistered())
{
this.PostData();
}
}

private bool IsAlreadyRegistered()
{
try
{
using (var client = new WebClient())
{
client.Headers[HttpRequestHeader.ContentType] = "application/json";
var uri = new Uri(this.registrarConfiguration.HubUri, "/grid/api/proxy");
client.QueryString.Add("id", this.registrarConfiguration.Id);
dynamic jo = JObject.Parse(client.DownloadString(uri));
return jo.success;
}
}
catch (WebException webException)
{
Logger.Error("Selenium Grid hub down or not responding: {0}.", webException.Message);
}

return false;
}

private bool LoadConfiguration()
{
try
{
dynamic jo = JObject.Parse(File.ReadAllText(this.configFilePath));
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

я бы наверно персонально отловил возможное FileNotFoundException

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Кажется у него и так получается нормальное сообщение об ошибке в отличие от парсинга json, например:

Error reading node configuration: Файл 'D:\Projects\windows-universal-app-driver\Winium\Winium.StoreApps.Driver\bin\x86\Release\123' не найден.

А вот ошибка в синтаксе, если её ловить как exception, тут не совсем ясно, что за проперти и т.п.

Error reading node configuration: Invalid character after parsing property name. Expected ':' but got: ". Path 'configuration.registerCycle', line 22, position 5.


if (jo.configuration.host == null || jo.configuration.port == null || jo.configuration.url == null)
{
jo.configuration.host = this.defaultHost;
jo.configuration.port = this.defaultPort;
jo.configuration.url = new UriBuilder("http", this.defaultHost, this.defaultPort).ToString();
Logger.Warn(
"Some of required node options (host, port or url) are not set, Winium set them to: host={0}, port={1}, url={2}."
+ " Note that this will not work if your node and grid aren't in the same place.",
jo.configuration.host,
jo.configuration.port,
jo.configuration.url);
}

this.registrarConfiguration = jo.configuration.ToObject<NodeRegistrarConfiguration>();

this.data = JsonConvert.SerializeObject(jo, Formatting.Indented);

return true;
}
catch (JsonReaderException ex)
{
Logger.Error("Syntax error in node configuration file: {0}", ex.Message);
return false;
}
catch (Exception ex)
{
Logger.Error("Error reading node configuration: {0}", ex.Message);
return false;
}
}

private void PostData()
{
try
{
using (var client = new WebClient())
{
client.Headers[HttpRequestHeader.ContentType] = "application/json";
var uri = new Uri(this.registrarConfiguration.HubUri, "grid/register/");
var response = client.UploadString(uri, this.data);

Logger.Debug(
"Winium successfully registered with the grid on {0}, grid responded with: '{1}'.",
this.registrarConfiguration.HubUri,
response);
}
}
catch (WebException webException)
{
var webResponse = webException.Response as HttpWebResponse;
if (webResponse != null)
{
Logger.Error("Selenium Grid refused to register hub. {0}", webResponse.StatusDescription);
}
else
{
Logger.Error("Selenium Grid hub down or not responding: {0}.", webException.Message);
}
}
}

private void RegisterNode()
{
if (!this.LoadConfiguration())
{
return;
}

if (!this.registrarConfiguration.Register)
{
Logger.Debug("Node registration data was not send to Selenium Grid.");
return;
}

this.PostData();

if (!(this.registrarConfiguration.RegisterCycle > 0))
{
return;
}

Logger.Debug(
"Starting auto register for grid. Will try to register every {0} ms",
this.registrarConfiguration.RegisterCycle);
this.autoRegisterTimer = new Timer();
this.autoRegisterTimer.Elapsed += this.AutoRegisterEvent;
this.autoRegisterTimer.Interval = this.registrarConfiguration.RegisterCycle;
this.autoRegisterTimer.Enabled = true;
}

#endregion

private class NodeRegistrarConfiguration
{
#region Fields

private Uri hubUri;

private string id;

#endregion

#region Public Properties

public Uri HubUri
{
get
{
return this.hubUri ?? (this.hubUri = new UriBuilder("http", this.HubHost, this.HubPort).Uri);
}
}

public string Id
{
get
{
return this.id ?? (this.id = new UriBuilder("http", this.Host, this.Port).ToString());
}
}

[JsonProperty("register")]
public bool Register { get; set; }

[JsonProperty("registerCycle")]
public double RegisterCycle { get; set; }

#endregion

#region Properties

[JsonProperty("host")]
private string Host { get; set; }

[JsonProperty("hubHost")]
private string HubHost { get; set; }

[JsonProperty("hubPort")]
private int HubPort { get; set; }

[JsonProperty("port")]
private int Port { get; set; }

#endregion
}
}
}
8 changes: 3 additions & 5 deletions Winium/Winium.StoreApps.Driver/Program.cs
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,6 @@

using System;
using System.Collections.Generic;
using System.IO;

using Winium.StoreApps.Driver.Automator;
using Winium.StoreApps.Driver.CommandHelpers;
Expand Down Expand Up @@ -59,12 +58,11 @@ private static void Main(string[] args)
Logger.Warn("Colud not set OnExit cleanup handlers.");
}

var listeningPort = options.Port;
AppLifetimeDisposables.Add(EmulatorFactory.Instance);
listener = new Listener(listeningPort);
Listener.UrnPrefix = options.UrlBase;

Console.WriteLine("Starting {0} on port {1}\n", appName, listeningPort);
listener = new Listener(options.Port, options.UrlBase, options.NodeConfig);

Console.WriteLine("Starting {0} on port {1}\n", appName, listener.Port);

listener.StartListening();
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

вот что заметил, так случайно будет не более правильно?

listener = new Listener(options.Port, options.UrlBase, options.NodeConfig);
listener.StartListening();
Console.WriteLine("Starting {0} on port {1}\n", appName, listener.Port);

}
Expand Down
Loading