Skip to content

Commit 192d433

Browse files
authored
[DevTools][Emulation] Handling of DevTools: emulate GeoLocation, Device, Media, UserAgent (#214) +semver: feature
* [DevTools] Implement DevToolsHandling - Emulation domain * Fix HasActiveDevToolsSession behavior * Move GeoLocation override methods to a separate DevToolsEmulationExtensions class * DevTools Emulation extensions to override device metrics and CanEmulate * Implement SetUserAgentOverride and SetScriptExecutionDisabled DevTools emulation methods. * Implement additional extensions: SetTouchEmulationEnabled, SetEmulatedMedia,DisableEmulatedMediaOverride, SetDefaultBackgroundColorOverride, ClearDefaultBackgroundColorOverride
1 parent a676cdc commit 192d433

File tree

17 files changed

+1029
-12
lines changed

17 files changed

+1029
-12
lines changed

Aquality.Selenium/src/Aquality.Selenium/Aquality.Selenium.xml

Lines changed: 229 additions & 5 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

Aquality.Selenium/src/Aquality.Selenium/Browsers/Browser.cs

Lines changed: 27 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -9,6 +9,8 @@
99
using Aquality.Selenium.Core.Waitings;
1010
using System.Collections.ObjectModel;
1111

12+
using IDevTools = OpenQA.Selenium.DevTools.IDevTools;
13+
1214
namespace Aquality.Selenium.Browsers
1315
{
1416
/// <summary>
@@ -18,6 +20,7 @@ public class Browser : IApplication
1820
{
1921
private TimeSpan implicitWaitTimeout;
2022
private TimeSpan pageLoadTimeout;
23+
private DevToolsHandling devTools;
2124

2225
private readonly IBrowserProfile browserProfile;
2326
private readonly IConditionalWait conditionalWait;
@@ -127,10 +130,29 @@ public string CurrentUrl
127130
}
128131

129132
/// <summary>
130-
/// Checkes whether current SessionId is null or not.
133+
/// Checks whether current SessionId is null or not.
131134
/// </summary>
132135
public bool IsStarted => Driver?.SessionId != null;
133136

137+
/// <summary>
138+
/// Provides interface to handle DevTools for Chromium-based and Firefox drivers.
139+
/// </summary>
140+
/// <returns>An instance of <see cref="DevToolsHandling"/>.</returns>
141+
public DevToolsHandling DevTools
142+
{
143+
get
144+
{
145+
if (Driver is IDevTools driver)
146+
{
147+
return devTools ?? (devTools = new DevToolsHandling(driver));
148+
}
149+
else
150+
{
151+
throw new NotSupportedException("DevTools protocol is not supported for current browser.");
152+
}
153+
}
154+
}
155+
134156
/// <summary>
135157
/// Quit web browser.
136158
/// </summary>
@@ -141,7 +163,7 @@ public void Quit()
141163
}
142164

143165
/// <summary>
144-
/// Navigates to desired url.
166+
/// Navigates to desired URL.
145167
/// </summary>
146168
/// <param name="url">String representation of URL.</param>
147169
public void GoTo(string url)
@@ -189,9 +211,9 @@ private INavigation Navigate()
189211
}
190212

191213
/// <summary>
192-
/// Provide interface to manage of browser tabs.
214+
/// Provides interface to manage of browser tabs.
193215
/// </summary>
194-
/// <returns>instance of IBrowserTabNavigation.</returns>
216+
/// <returns>Instance of IBrowserTabNavigation.</returns>
195217
public IBrowserTabNavigation Tabs()
196218
{
197219
return new BrowserTabNavigation(Driver);
@@ -231,7 +253,7 @@ public void HandleAlert(AlertAction alertAction, string text = null)
231253
}
232254

233255
/// <summary>
234-
/// Maximises web page.
256+
/// Maximizes web page.
235257
/// </summary>
236258
public void Maximize()
237259
{
Lines changed: 265 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,265 @@
1+
using Aquality.Selenium.Core.Utilities;
2+
using OpenQA.Selenium.DevTools;
3+
using OpenQA.Selenium.DevTools.V85.DOM;
4+
using OpenQA.Selenium.DevTools.V85.Emulation;
5+
using System;
6+
using System.Collections.Generic;
7+
using System.Linq;
8+
using System.Threading.Tasks;
9+
10+
namespace Aquality.Selenium.Browsers
11+
{
12+
/// <summary>
13+
/// Implementation of version-independent emulation DevTools commands as extensions for <see cref="DevToolsHandling"/>.
14+
/// Currently only non-experimental extensions are implemented.
15+
/// For more information, see <see href="https://chromedevtools.github.io/devtools-protocol/tot/Emulation/"/>.
16+
/// </summary>
17+
public static class DevToolsEmulationExtensions
18+
{
19+
/// <summary>
20+
/// Tells whether emulation is supported.
21+
/// </summary>
22+
/// <param name="devTools">Current instance of <see cref="DevToolsHandling"/>.</param>
23+
/// <returns>A task for asynchronous command with boolean result.</returns>
24+
public static async Task<bool> CanEmulate(this DevToolsHandling devTools)
25+
{
26+
var response = await devTools.SendCommand(new CanEmulateCommandSettings());
27+
return response["result"]?.ToString().Equals(bool.TrueString, StringComparison.OrdinalIgnoreCase) == true;
28+
}
29+
30+
/// <summary>
31+
/// Overrides the GeoLocation Position or Error. Omitting any of the parameters emulates position unavailable.
32+
/// </summary>
33+
/// <param name="devTools">Current instance of <see cref="DevToolsHandling"/>.</param>
34+
/// <param name="latitude">Latitude of GeoLocation.</param>
35+
/// <param name="longitude">Longitude of location.</param>
36+
/// <param name="accuracy">Accuracy of the geoLocation. By default is set to 1 meaning 100% accuracy.</param>
37+
public static async Task SetGeoLocationOverride(this DevToolsHandling devTools,
38+
double? latitude, double? longitude, double? accuracy = 1)
39+
{
40+
var settings = new SetGeolocationOverrideCommandSettings
41+
{
42+
Latitude = latitude,
43+
Longitude = longitude,
44+
Accuracy = accuracy
45+
};
46+
await devTools.SendCommand(settings);
47+
}
48+
49+
/// <summary>
50+
/// Clears the overridden GeoLocation Position and Error.
51+
/// </summary>
52+
/// <param name="devTools">Current instance of <see cref="DevToolsHandling"/>.</param>
53+
/// <returns>A task for asynchronous command.</returns>
54+
public static async Task ClearGeolocationOverride(this DevToolsHandling devTools)
55+
{
56+
await devTools.SendCommand(new ClearGeolocationOverrideCommandSettings());
57+
}
58+
59+
/// <summary>
60+
/// Overrides the values of device screen dimensions.
61+
/// </summary>
62+
/// <param name="devTools">Current instance of <see cref="DevToolsHandling"/>.</param>
63+
/// <param name="commandParameters">Version-specific set of parameters.
64+
/// For example, take a look at <see cref="SetDeviceMetricsOverrideCommandSettings"/>.</param>
65+
/// <returns>A task for asynchronous command.</returns>
66+
public static async Task SetDeviceMetricsOverride(this DevToolsHandling devTools, ICommand commandParameters)
67+
{
68+
GuardCommandParameters<SetDeviceMetricsOverrideCommandSettings>(commandParameters);
69+
await devTools.SendCommand(commandParameters);
70+
}
71+
72+
/// <summary>
73+
/// Overrides the values of device screen dimensions.
74+
/// </summary>
75+
/// <param name="devTools">Current instance of <see cref="DevToolsHandling"/>.</param>
76+
/// <param name="width">Value to override window.screen.width.</param>
77+
/// <param name="height">Value to override window.screen.height.</param>
78+
/// <param name="mobile">Whether to emulate mobile device. This includes viewport meta tag, overlay scrollbars, text autosizing and more.</param>
79+
/// <param name="deviceScaleFactor">Overriding device scale factor value. 0 disables the override.</param>
80+
/// <param name="screenOrientationType">Orientation type.
81+
/// Allowed Values (in any case): portraitPrimary, portraitSecondary, landscapePrimary, landscapeSecondary.</param>
82+
/// <param name="screenOrientationAngle">Orientation angle. Set only if orientation type was set.</param>
83+
/// <returns>A task for asynchronous command.</returns>
84+
public static async Task SetDeviceMetricsOverride(this DevToolsHandling devTools,
85+
long width, long height, bool mobile,
86+
double deviceScaleFactor = 0, string screenOrientationType = null, int? screenOrientationAngle = null)
87+
{
88+
var deviceModeSetting = new SetDeviceMetricsOverrideCommandSettings()
89+
{
90+
Width = width,
91+
Height = height,
92+
Mobile = mobile,
93+
DeviceScaleFactor = deviceScaleFactor
94+
};
95+
if (!string.IsNullOrEmpty(screenOrientationType))
96+
{
97+
var screenOrientation = new ScreenOrientation
98+
{
99+
Type = screenOrientationType.ToEnum<ScreenOrientationTypeValues>()
100+
};
101+
if (screenOrientationAngle != null)
102+
{
103+
screenOrientation.Angle = screenOrientationAngle.Value;
104+
}
105+
deviceModeSetting.ScreenOrientation = screenOrientation;
106+
}
107+
await SetDeviceMetricsOverride(devTools, deviceModeSetting);
108+
}
109+
110+
/// <summary>
111+
/// Clears the overridden device metrics.
112+
/// </summary>
113+
/// <param name="devTools">Current instance of <see cref="DevToolsHandling"/>.</param>
114+
/// <returns>A task for asynchronous command.</returns>
115+
public static async Task ClearDeviceMetricsOverride(this DevToolsHandling devTools)
116+
{
117+
await devTools.SendCommand(new ClearDeviceMetricsOverrideCommandSettings());
118+
}
119+
120+
/// <summary>
121+
/// Overrides the values of user agent.
122+
/// </summary>
123+
/// <param name="devTools">Current instance of <see cref="DevToolsHandling"/>.</param>
124+
/// <param name="commandParameters">Version-specific set of parameters.
125+
/// For example, take a look at <see cref="SetUserAgentOverrideCommandSettings"/>.</param>
126+
/// <returns>A task for asynchronous command.</returns>
127+
public static async Task SetUserAgentOverride(this DevToolsHandling devTools, ICommand commandParameters)
128+
{
129+
GuardCommandParameters<SetUserAgentOverrideCommandSettings>(commandParameters);
130+
await devTools.SendCommand(commandParameters);
131+
}
132+
133+
/// <summary>
134+
/// Overrides the values of user agent.
135+
/// </summary>
136+
/// <param name="devTools">Current instance of <see cref="DevToolsHandling"/>.</param>
137+
/// <param name="userAgent">User agent to use.</param>
138+
/// <param name="acceptLanguage">Browser language to emulate.</param>
139+
/// <param name="platform">The platform navigator.platform should return.</param>
140+
/// <returns>A task for asynchronous command.</returns>
141+
public static async Task SetUserAgentOverride(this DevToolsHandling devTools,
142+
string userAgent, string acceptLanguage = null, string platform = null)
143+
{
144+
var settings = new SetUserAgentOverrideCommandSettings()
145+
{
146+
UserAgent = userAgent,
147+
AcceptLanguage = acceptLanguage,
148+
Platform = platform
149+
};
150+
await SetUserAgentOverride(devTools, settings);
151+
}
152+
153+
/// <summary>
154+
/// Switches script execution in the page.
155+
/// </summary>
156+
/// <param name="devTools">Current instance of <see cref="DevToolsHandling"/>.</param>
157+
/// <param name="value">Whether script execution should be disabled in the page.</param>
158+
/// <returns>A task for asynchronous command.</returns>
159+
public static async Task SetScriptExecutionDisabled(this DevToolsHandling devTools, bool value = true)
160+
{
161+
await devTools.SendCommand(new SetScriptExecutionDisabledCommandSettings { Value = value });
162+
}
163+
164+
/// <summary>
165+
/// Enables touch on platforms which do not support them.
166+
/// </summary>
167+
/// <param name="devTools">Current instance of <see cref="DevToolsHandling"/>.</param>
168+
/// <param name="enabled">Whether the touch event emulation should be enabled.</param>
169+
/// <param name="maxTouchPoints">Maximum touch points supported. Defaults to one.</param>
170+
/// <returns>A task for asynchronous command.</returns>
171+
public static async Task SetTouchEmulationEnabled(this DevToolsHandling devTools, bool enabled = true, long? maxTouchPoints = null)
172+
{
173+
await devTools.SendCommand(new SetTouchEmulationEnabledCommandSettings { Enabled = enabled, MaxTouchPoints = maxTouchPoints });
174+
}
175+
176+
/// <summary>
177+
/// Enables touch on platforms which do not support them.
178+
/// </summary>
179+
/// <param name="devTools">Current instance of <see cref="DevToolsHandling"/>.</param>
180+
/// <param name="commandParameters">Version-specific set of parameters.
181+
/// For example, take a look at <see cref="SetTouchEmulationEnabledCommandSettings"/>.</param>
182+
/// <returns>A task for asynchronous command.</returns>
183+
public static async Task SetTouchEmulationEnabled(this DevToolsHandling devTools, ICommand commandParameters)
184+
{
185+
GuardCommandParameters<SetTouchEmulationEnabledCommandSettings>(commandParameters);
186+
await devTools.SendCommand(commandParameters);
187+
}
188+
189+
/// <summary>
190+
/// Emulates the given media type or media feature for CSS media queries.
191+
/// </summary>
192+
/// <param name="devTools">Current instance of <see cref="DevToolsHandling"/>.</param>
193+
/// <param name="media">Media type to emulate. Empty string disables the override.
194+
/// Possible values: braille, embossed, handheld, print, projection, screen, speech, tty, tv.</param>
195+
/// <param name="mediaFeatures">Media features to emulate.</param>
196+
/// <returns>A task for asynchronous command.</returns>
197+
public static async Task SetEmulatedMedia(this DevToolsHandling devTools, string media, IDictionary<string, string> mediaFeatures = null)
198+
{
199+
var settings = new SetEmulatedMediaCommandSettings { Media = media };
200+
if (mediaFeatures != null)
201+
{
202+
settings.Features = mediaFeatures.Keys.Select(key => new MediaFeature { Name = key, Value = mediaFeatures[key] }).ToArray();
203+
}
204+
await devTools.SendCommand(settings);
205+
}
206+
207+
/// <summary>
208+
/// Emulates the given media type or media feature for CSS media queries.
209+
/// </summary>
210+
/// <param name="devTools">Current instance of <see cref="DevToolsHandling"/>.</param>
211+
/// <param name="commandParameters">Version-specific set of parameters.
212+
/// For example, take a look at <see cref="SetEmulatedMediaCommandSettings"/>.</param>
213+
/// <returns>A task for asynchronous command.</returns>
214+
public static async Task SetEmulatedMedia(this DevToolsHandling devTools, ICommand commandParameters)
215+
{
216+
GuardCommandParameters<SetEmulatedMediaCommandSettings>(commandParameters);
217+
await devTools.SendCommand(commandParameters);
218+
}
219+
220+
/// <summary>
221+
/// Disables emulated media override.
222+
/// </summary>
223+
/// <param name="devTools">Current instance of <see cref="DevToolsHandling"/>.</param>
224+
/// <returns>A task for asynchronous command.</returns>
225+
public static async Task DisableEmulatedMediaOverride(this DevToolsHandling devTools)
226+
{
227+
var settings = new SetEmulatedMediaCommandSettings { Media = string.Empty };
228+
await SetEmulatedMedia(devTools, settings);
229+
}
230+
231+
/// <summary>
232+
/// Sets an override of the default background color of the frame. This override is used if the content does not specify one.
233+
/// </summary>
234+
/// <param name="devTools">Current instance of <see cref="DevToolsHandling"/>.</param>
235+
/// <param name="red">The red component, in the [0-255] range.</param>
236+
/// <param name="green">The green component, in the [0-255] range.</param>
237+
/// <param name="blue">The blue component, in the [0-255] range.</param>
238+
/// <param name="alpha">The alpha component, in the [0-1] range (default: 1).</param>
239+
/// <returns>A task for asynchronous command.</returns>
240+
public static async Task SetDefaultBackgroundColorOverride(this DevToolsHandling devTools,
241+
long red, long green, long blue, double? alpha = null)
242+
{
243+
var settings = new SetDefaultBackgroundColorOverrideCommandSettings { Color = new RGBA { R = red, G = green, B = blue, A = alpha } };
244+
await devTools.SendCommand(settings);
245+
}
246+
247+
/// <summary>
248+
/// Clears an override of the default background color of the frame.
249+
/// </summary>
250+
/// <param name="devTools">Current instance of <see cref="DevToolsHandling"/>.</param>
251+
/// <returns>A task for asynchronous command.</returns>
252+
public static async Task ClearDefaultBackgroundColorOverride(this DevToolsHandling devTools)
253+
{
254+
await devTools.SendCommand(new SetDefaultBackgroundColorOverrideCommandSettings());
255+
}
256+
257+
private static void GuardCommandParameters<T>(ICommand commandParameters) where T : ICommand, new()
258+
{
259+
if (commandParameters == null || commandParameters.CommandName != new T().CommandName)
260+
{
261+
throw new ArgumentException("Command parameters are null or does not match to command name.", nameof(commandParameters));
262+
}
263+
}
264+
}
265+
}

0 commit comments

Comments
 (0)