Skip to content

Commit fad97d7

Browse files
committed
add support for remote web driver with raw capabilities
1 parent d9d3a22 commit fad97d7

File tree

6 files changed

+174
-8
lines changed

6 files changed

+174
-8
lines changed

src/Gravity.Abstraction.Tests/Base/TestSuite.cs

Lines changed: 0 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -3,7 +3,6 @@
33

44
using Microsoft.VisualStudio.TestTools.UnitTesting;
55

6-
using OpenQA.Selenium;
76
using OpenQA.Selenium.Remote;
87

98
using System.Net.Http;
Lines changed: 15 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,15 @@
1+
using System.Collections.Generic;
2+
3+
namespace Gravity.Abstraction.Contracts
4+
{
5+
public class CapabilitiesModel
6+
{
7+
public IDictionary<string, object> DesiredCapabilities { get; set; }
8+
public W3Capabilites Capabilities { get; set; }
9+
10+
public class W3Capabilites
11+
{
12+
public IEnumerable<IDictionary<string, object>> FirstMatch { get; set; }
13+
}
14+
}
15+
}
Lines changed: 52 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,52 @@
1+
using OpenQA.Selenium.Remote;
2+
using OpenQA.Selenium;
3+
4+
using System;
5+
using System.Reflection;
6+
7+
namespace Gravity.Abstraction.Extensions
8+
{
9+
internal static class WebDriverExtension
10+
{
11+
public static string GetSession(this IWebDriver driver)
12+
{
13+
return driver is IHasSessionId id ? $"{id.SessionId}" : $"{Guid.NewGuid()}";
14+
}
15+
16+
public static Uri GetEndpoint(this IWebDriver driver)
17+
{
18+
// setup
19+
const BindingFlags Flags = BindingFlags.Instance | BindingFlags.NonPublic;
20+
21+
// get RemoteWebDriver type
22+
var remoteWebDriver = GetRemoteWebDriver(driver.GetType());
23+
24+
// get this instance executor > get this instance internalExecutor
25+
var executor = remoteWebDriver.GetField("executor", Flags).GetValue(driver) as ICommandExecutor;
26+
27+
// get URL
28+
var endpoint = executor.GetType().GetField("service", Flags).GetValue(executor) as DriverService;
29+
30+
// result
31+
return endpoint.ServiceUrl;
32+
}
33+
34+
private static Type GetRemoteWebDriver(Type type)
35+
{
36+
// if not a remote web driver, return the type used for the call
37+
if (!typeof(RemoteWebDriver).IsAssignableFrom(type))
38+
{
39+
return type;
40+
}
41+
42+
// iterate until gets the RemoteWebDriver type
43+
while (type != typeof(RemoteWebDriver))
44+
{
45+
type = type.BaseType;
46+
}
47+
48+
// gets RemoteWebDriver to use for extracting internal information
49+
return type;
50+
}
51+
}
52+
}

src/Gravity.Abstraction/WebDriver/DriverFactory.cs

Lines changed: 61 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -28,6 +28,8 @@
2828
using System.Diagnostics.CodeAnalysis;
2929
using OpenQA.Selenium.Uia;
3030
using Gravity.Abstraction.Uia;
31+
using System.Net.Http;
32+
using System.Text;
3133

3234
namespace Gravity.Abstraction.WebDriver
3335
{
@@ -94,9 +96,9 @@ public IWebDriver Create()
9496
}
9597
catch (Exception e)
9698
{
97-
99+
Trace.TraceError($"{e.GetBaseException()}");
98100
throw;
99-
}
101+
}
100102
}
101103
#endregion
102104

@@ -210,7 +212,7 @@ private IWebDriver GetRemoteAndroid(string driverBinaries)
210212
[SuppressMessage("CodeQuality", "IDE0051:Remove unused private members", Justification = "Used by reflection.")]
211213
private IWebDriver GetRemoteIos(string driverBinaries)
212214
{
213-
return GetMobile<AppiumOptions, AppiumOptionsParams, IOSDriver<IWebElement>>(driverBinaries, "iOS");
215+
return GetMobile<AppiumOptions, AppiumOptionsParams, IOSDriver<IWebElement>>(driverBinaries, string.Empty);
214216
}
215217

216218
[DriverMethod(Driver = Driver.Safari, RemoteDriver = true)]
@@ -229,13 +231,65 @@ private IWebDriver GetRemoteDriver(string driverBinaries)
229231
var capabilities = GetCapabilities(options, _capabilities);
230232
options.BrowserVersion = string.IsNullOrEmpty(options.BrowserVersion) ? "1.0" : options.BrowserVersion;
231233

232-
233234
// factor web driver
234235
var executor = _commandTimeout == 0
235236
? new UiaCommandExecutor(new Uri(driverBinaries), TimeSpan.FromSeconds(60))
236237
: new UiaCommandExecutor(new Uri(driverBinaries), TimeSpan.FromSeconds(_commandTimeout));
237238
return new UiaDriver(executor, capabilities);
238239
}
240+
241+
[DriverMethod(Driver = "RemoteWebDriver", RemoteDriver = true)]
242+
[SuppressMessage("CodeQuality", "IDE0051:Remove unused private members", Justification = "Used by reflection.")]
243+
private IWebDriver GetRemoteUiaDriver(string driverBinaries)
244+
{
245+
// setup
246+
var capabilities = new CapabilitiesModel
247+
{
248+
DesiredCapabilities = _capabilities,
249+
Capabilities = new()
250+
{
251+
FirstMatch = new[]
252+
{
253+
_capabilities
254+
}
255+
}
256+
};
257+
258+
// build
259+
using var client = new HttpClient();
260+
var requestBody = JsonSerializer.Serialize(capabilities, new JsonSerializerOptions
261+
{
262+
PropertyNamingPolicy = JsonNamingPolicy.CamelCase
263+
});
264+
var content = new StringContent(requestBody, Encoding.UTF8, "application/json");
265+
var request = new HttpRequestMessage(HttpMethod.Post, $"{driverBinaries}/session")
266+
{
267+
Content = content
268+
};
269+
270+
// invoke
271+
var response = client.SendAsync(request).Result;
272+
273+
// internal server error
274+
if (!response.IsSuccessStatusCode)
275+
{
276+
throw new WebDriverException(response.ReasonPhrase);
277+
}
278+
279+
// extract id
280+
var responseContent = response.Content.ReadAsStringAsync().Result;
281+
var responseObject = JsonSerializer.Deserialize<IDictionary<string, object>>(responseContent);
282+
var sessionId = ((JsonElement)responseObject["value"]).GetProperty("sessionId").GetString();
283+
284+
// setup: get information to initialize driver two
285+
var addressOfRemoteServer = new Uri(driverBinaries);
286+
var timeout = TimeSpan.FromSeconds(60);
287+
var commandExecutor = new LocalExecutor(sessionId, addressOfRemoteServer, timeout);
288+
var localCapabilities = new Dictionary<string, object>(_capabilities);
289+
290+
// get
291+
return new RemoteWebDriver(commandExecutor, new DesiredCapabilities(localCapabilities));
292+
}
239293
#endregion
240294

241295
// TODO: add default constructor options when there are no binaries to load
@@ -291,14 +345,14 @@ private DriverOptions GetOptions<TOptions, TParams>(string platformName)
291345
where TParams : DriverOptionsParams, IOptionable<TOptions>
292346
{
293347
// parse token
294-
var isOptions = this._options.Keys.Count > 0;
348+
var isOptions = _options.Keys.Count > 0;
295349

296350
// null validation
297351
if (!isOptions)
298352
{
299353
return new TOptions
300354
{
301-
PlatformName = platformName
355+
PlatformName = string.IsNullOrEmpty(platformName) ? null : platformName
302356
};
303357
}
304358

@@ -309,7 +363,7 @@ private DriverOptions GetOptions<TOptions, TParams>(string platformName)
309363
var options = paramsObj.ToDriverOptions();
310364
if (!string.IsNullOrEmpty(platformName))
311365
{
312-
options.PlatformName = platformName;
366+
options.PlatformName = string.IsNullOrEmpty(platformName) ? null : platformName;
313367
}
314368
return options;
315369
}
Lines changed: 45 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,45 @@
1+
using OpenQA.Selenium.Remote;
2+
using OpenQA.Selenium;
3+
4+
using System;
5+
6+
namespace Gravity.Abstraction.WebDriver
7+
{
8+
public class LocalExecutor : HttpCommandExecutor
9+
{
10+
public LocalExecutor(string sessionId, Uri addressOfRemoteServer, TimeSpan timeout)
11+
: this(sessionId, addressOfRemoteServer, timeout, false)
12+
{ }
13+
14+
public LocalExecutor(string sessionId, Uri addressOfRemoteServer, TimeSpan timeout, bool enableKeepAlive)
15+
: this(addressOfRemoteServer, timeout, enableKeepAlive)
16+
{
17+
SessionId = sessionId;
18+
}
19+
20+
public LocalExecutor(Uri addressOfRemoteServer, TimeSpan timeout, bool enableKeepAlive)
21+
: base(addressOfRemoteServer, timeout, enableKeepAlive)
22+
{ }
23+
24+
// expose the "Session ID" used to build this executor
25+
public string SessionId { get; }
26+
27+
public override Response Execute(Command commandToExecute)
28+
{
29+
// invoke selenium command using the original driver executor
30+
if (!commandToExecute.Name.Equals("newSession"))
31+
{
32+
return base.Execute(commandToExecute);
33+
}
34+
35+
// returns a success result, if the command is "newSession"
36+
// this prevents a shadow browser from opening
37+
return new Response
38+
{
39+
Status = WebDriverResult.Success,
40+
SessionId = SessionId,
41+
Value = null
42+
};
43+
}
44+
}
45+
}

src/dotnet-client

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1 @@
1+
Subproject commit a0489643ec4bde504b17eca4c8eee49634bb7cd6

0 commit comments

Comments
 (0)