Skip to content

Instantly share code, notes, and snippets.

@jimevans
Created September 9, 2019 17:34
Show Gist options
  • Save jimevans/9a23c1bcfd516f776c80b91564010d15 to your computer and use it in GitHub Desktop.
Save jimevans/9a23c1bcfd516f776c80b91564010d15 to your computer and use it in GitHub Desktop.
SauceLabs type-safe options creator
SauceLabsSessionOptions sauceOptions = new SauceLabsSessionOptions();
sauceOptions.UserName = "username";
sauceOptions.AccessKey = "accessKey";
ChromeOptions options = new ChromeOptions();
options.AddAdditionalOption(SauceLabsSessionOptions.SauceOptionsOptionName, sauceOptions.ToDictionary());
IWebDriver driver = new RemoteWebDriver(new Uri("https://ondemand.saucelabs.com/wd/hub"), options);
namespace SauceLabs
{
/// <summary>
/// Represents the visibility levels of SauceLabs jobs.
/// </summary>
public enum SauceLabsJobVisibility
{
/// <summary>
/// Job visibility is unspecified.
/// </summary>
Default,
/// <summary>
/// Job is accessible to everyone, and may be listed on public web pages and indexed by search engines.
/// </summary>
Public,
/// <summary>
/// Job video and result page are accessible to everyone, but logs are kept private.
/// </summary>
PublicRestricted,
/// <summary>
/// Job is only accessible to people having valid link and it is not listed on publicly available pages
/// or indexed by search engines.
/// </summary>
Share,
/// <summary>
/// Job is only accessible to people under the same root account as the job .
/// </summary>
Team,
/// <summary>
/// Job is only accessible to the job owner.
/// </summary>
Private,
}
}
using System;
using System.Collections.Generic;
namespace SauceLabs
{
/// <summary>
/// This class defines the options that can be set for an executable to be run
/// before a test starts in a SauceLabs session.
/// </summary>
public class SauceLabsPreRunExecutableOptions
{
private List<object> arguments = new List<object>();
/// <summary>
/// Initializes a new instance of the <see cref="SauceLabsPreRunExecutableOptions"/> class.
/// </summary>
/// <param name="urlToExecutable">The URL to an executable file to be downloaded and executed before the test starts.</param>
public SauceLabsPreRunExecutableOptions(string urlToExecutable)
{
if (string.IsNullOrEmpty(urlToExecutable))
{
throw new ArgumentNullException("urlToExecutable", "Executable path must not be null or the empty string");
}
this.Executable = urlToExecutable;
}
/// <summary>
/// Gets the URL of the executable to be downloaded and executed before the test starts.
/// </summary>
public string Executable { get; }
/// <summary>
/// Gets or sets a value indicating whether SauceLabs should wait for this executable
/// to finish before the browser session starts.
/// </summary>
public bool RunInBackground { get; set; }
/// <summary>
/// Gets or sets the number of seconds SauceLabs will wait for the executable
/// to finish before the browser session starts.
/// </summary>
public int Timeout { get; set; }
/// <summary>
/// Adds an argument to the command line of the executable.
/// </summary>
/// <param name="arg">The argument to add to the command line.</param>
public void AddArgument(string arg)
{
if (string.IsNullOrEmpty(arg))
{
throw new ArgumentNullException("arg", "Argument cannot be null or the empty string");
}
this.AddArguments(arg);
}
/// <summary>
/// Adds a set of arguments to the command line of the executable.
/// </summary>
/// <param name="args">The arguments to add to the command line.</param>
public void AddArguments(params string[] args)
{
this.AddArguments(args);
}
/// <summary>
/// Adds a set of arguments to the command line of the executable.
/// </summary>
/// <param name="args">The arguments to add to the command line.</param>
public void AddArguments(IEnumerable<string> args)
{
foreach (string arg in args)
{
if (string.IsNullOrEmpty(arg))
{
throw new ArgumentNullException("Argument to be added cannot be null or the empty string");
}
this.arguments.Add(arg);
}
}
/// <summary>
/// Converts this <see cref="SauceLabsPreRunExecutableOptions"/> instance to a dictionary to be added
/// to a set of options for creating a session on SauceLabs.
/// </summary>
/// <returns>
/// A <see cref="Dictionary{String, Object}"/> containing the settings for the executable
///to be downloaded and run before a test starts.
/// </returns>
public Dictionary<string, object> ToDictionary()
{
Dictionary<string, object> toReturn = new Dictionary<string, object>();
toReturn["executable"] = this.Executable;
if (this.arguments.Count > 0)
{
toReturn["args"] = this.arguments.ToArray();
}
if (this.RunInBackground)
{
toReturn["background"] = true;
}
if (this.Timeout > 0)
{
toReturn["timeout"] = this.Timeout;
}
return toReturn;
}
}
}
using System;
using System.Collections.Generic;
namespace SauceLabs
{
/// <summary>
/// This class defines the options that can be set for a SauceLabs session.
/// </summary>
public class SauceLabsSessionOptions
{
/// <summary>
/// The option name to be used in setting the SauceLabs options when creating a new session.
/// </summary>
public static readonly string SauceOptionsOptionName = "sauce:options";
private static readonly string UserNameOptionName = "username";
private static readonly string AccessKeyOptionName = "accessKey";
private static readonly string AppiumVersionOptionName = "appiumVersion";
private static readonly string SeleniumVersionOptionName = "seleniumVersion";
private static readonly string ChromeDriverVersionOptionName = "chromedriverVersion";
private static readonly string IEDriverVersionOptionName = "iedriverVersion";
private static readonly string ScreenResolutionOptionName = "screenResolution";
private static readonly string RecordVideoOptionName = "recordVideo";
private static readonly string RecordScreenshotsOptionName = "recordScrenshots";
private static readonly string RecordLogsOptionName = "recordLogs";
private static readonly string VideoUploadOnPassOptionName = "videoUploadOnPass";
private static readonly string TestNameOptionName = "name";
private static readonly string BuildOptionName = "build";
private static readonly string TagsOptionName = "tags";
private static readonly string PassedOptionName = "passed";
private static readonly string MaximumTestDurationOptionName = "maxDuration";
private static readonly string CommandTimeoutOptionName = "commandTimeout";
private static readonly string IdleTestTimeoutOptionName = "idleTimeout";
private static readonly string TunnelIdentifierOptionName = "tunnelIdentifier";
private static readonly string ParentTunnelOptionName = "parentTunnel";
private static readonly string TimeZoneOptionName = "timeZone";
private static readonly string AvoidProxyOptionName = "avoidProxy";
private static readonly string JobVisibilityOptionName = "public";
private static readonly string PriorityOptionName = "priority";
private static readonly string ExtendedDebuggingOptionName = "extendedDebugging";
private static readonly string CustomDataOptionName = "custom-data";
private static readonly string PrerunOptionName = "prerun";
private SauceLabsJobVisibility jobVisibility = SauceLabsJobVisibility.Default;
private bool enableVideoRecording = true;
private bool enableScreenshots = true;
private bool enableVideoUploadOnPass = true;
private bool enableLogs = true;
private int priority = -1;
private List<object> tags = new List<object>();
private Dictionary<string, object> customData;
/// <summary>
/// Gets or sets the SauceLabs user name used to create the session.
/// </summary>
public string UserName { get; set; }
/// <summary>
/// Gets or sets the SauceLabs access key used to create the session.
/// </summary>
public string AccessKey { get; set; }
/// <summary>
/// Gets or sets the version of Appium to be used during the session.
/// </summary>
public string AppiumVersion { get; set; }
/// <summary>
/// Gets or sets the version of Selenium to be used during the session.
/// </summary>
public string SeleniumVersion { get; set; }
/// <summary>
/// Gets or sets the version of chromedriver.exe to be used during the session.
/// </summary>
public string ChromeDriverVersion { get; set; }
/// <summary>
/// Gets or sets the version of IEDriverServer.exe to be used during the session.
/// </summary>
public string IEDriverVersion { get; set; }
/// <summary>
/// Gets or sets the screen resolution to be used during the session.
/// </summary>
public string ScreenResolution { get; set; }
/// <summary>
/// Gets or sets a value indicating whether to record video during the session.
/// </summary>
public bool RecordVideo
{
get { return this.enableVideoRecording; }
set { this.enableVideoRecording = value; }
}
/// <summary>
/// Gets or sets a value indicating whether to record screenshots during the session.
/// </summary>
public bool RecordScreenshots
{
get { return this.enableScreenshots; }
set { this.enableScreenshots = value; }
}
/// <summary>
/// Gets or sets a value indicating whether to record logs during the session.
/// </summary>
public bool RecordLogs
{
get { return this.enableLogs; }
set { this.enableLogs = value; }
}
/// <summary>
/// Gets or sets a value indicating whether to upload a video of the session if the test passes.
/// </summary>
public bool VideoUploadOnPass
{
get { return this.enableVideoUploadOnPass; }
set { this.enableVideoUploadOnPass = value; }
}
/// <summary>
/// Gets or sets a value indicating whether to record HAR files for some
/// browsers as well as console.json logs during the session.
/// </summary>
public bool ExtendedDebugging { get; set; }
/// <summary>
/// Gets or sets a user-defined name for tests.
/// </summary>
public string TestName { get; set; }
/// <summary>
/// Gets or sets a user-defined identifier for the build against which the session is executed.
/// </summary>
public string Build { get; set; }
/// <summary>
/// Gets or sets a value indicating whether to record the pass/fail status of the test on SauceLabs.
/// </summary>
public bool? IsPassed { get; set; }
/// <summary>
/// Gets or sets the maximum test duration, in seconds.
/// </summary>
public int MaximumTestDuration { get; set; }
/// <summary>
/// Gets or sets the command timeout, in seconds.
/// </summary>
public int CommandTimeout { get; set; }
/// <summary>
/// Gets or sets the amount of time, in seconds, between commands that indicate an idle session
/// that should be terminated.
/// </summary>
public int IdleTestTimeout { get; set; }
/// <summary>
/// Gets or sets the identifier for the SauceConnect tunnel to use during the session.
/// </summary>
public string TunnelIdentifier { get; set; }
/// <summary>
/// Gets or sets the identifier of a user name for a shared SauceConnect tunnel to use during the session.
/// </summary>
public string ParentTunnel { get; set; }
/// <summary>
/// Gets or sets the time zone to set in the SauceLabs virtual machine during the session.
/// </summary>
public string TimeZone { get; set; }
/// <summary>
/// Gets or sets a value indicating whether to avoid routing traffic through the Selenium HTTP proxy.
/// </summary>
public bool? AvoidProxy { get; set; }
/// <summary>
/// Gets or sets the priority of the session.
/// </summary>
public int Priority
{
get { return this.priority; }
set
{
if (value < 0)
{
throw new ArgumentException("Priority must be greater than or equal to zero");
}
this.priority = value;
}
}
/// <summary>
/// Gets or sets the visibility of the results of the session.
/// </summary>
public SauceLabsJobVisibility JobVisibility
{
get { return this.jobVisibility; }
set { this.jobVisibility = value; }
}
/// <summary>
/// Gets or sets the information about the executable to run before the test starts on SauceLabs.
/// </summary>
public SauceLabsPreRunExecutableOptions PreRunExecutable { get; set; }
/// <summary>
/// Adds a tag to the SauceLabs job.
/// </summary>
/// <param name="tagToAdd">The tag to add to the job.</param>
public void AddTag(string tagToAdd)
{
if (string.IsNullOrEmpty(tagToAdd))
{
throw new ArgumentNullException("tagToAdd", "tagToAdd cannot be null or the empty string");
}
this.AddTags(tagToAdd);
}
/// <summary>
/// Adds a set of tags to the SauceLabs job.
/// </summary>
/// <param name="tagsToAdd">The tags to add to the job.</param>
public void AddTags(params string[] tagsToAdd)
{
this.AddTags(tagsToAdd);
}
/// <summary>
/// Adds a set of tags to the SauceLabs job.
/// </summary>
/// <param name="tagsToAdd">The tags to add to the job.</param>
public void AddTags(IEnumerable<string> tagsToAdd)
{
foreach(string tag in tagsToAdd)
{
if (string.IsNullOrEmpty(tag))
{
throw new ArgumentNullException("Tag to be added cannot be null or the empty string");
}
this.tags.Add(tag);
}
}
/// <summary>
/// Sets user-defined custom data to be used in the session.
/// </summary>
/// <param name="customData"></param>
public void SetCustomData(Dictionary<string, object> customData)
{
if (customData == null)
{
throw new ArgumentNullException("customData", "Custom data cannot be null");
}
this.customData = customData;
}
/// <summary>
/// Converts this <see cref="SauceLabsSessionOptions"/> instance to a dictionary to be added
/// to a set of options for creating a session on SauceLabs.
/// </summary>
/// <returns>
/// A <see cref="Dictionary{String, Object}"/> containing the SauceLabs-specific
/// settings for a session.
/// </returns>
public virtual Dictionary<string, object> ToDictionary()
{
Dictionary<string, object> options = new Dictionary<string, object>();
if (!string.IsNullOrEmpty(this.UserName))
{
options[UserNameOptionName] = this.UserName;
}
if (!string.IsNullOrEmpty(this.AccessKey))
{
options[AccessKeyOptionName] = this.AccessKey;
}
if (!string.IsNullOrEmpty(this.AppiumVersion))
{
options[AppiumVersionOptionName] = this.AppiumVersion;
}
if (!string.IsNullOrEmpty(this.SeleniumVersion))
{
options[SeleniumVersionOptionName] = this.SeleniumVersion;
}
if (!string.IsNullOrEmpty(this.ChromeDriverVersion))
{
options[ChromeDriverVersionOptionName] = this.ChromeDriverVersion;
}
if (!string.IsNullOrEmpty(this.IEDriverVersion))
{
options[IEDriverVersionOptionName] = this.IEDriverVersion;
}
if (!string.IsNullOrEmpty(this.ScreenResolution))
{
options[ScreenResolutionOptionName] = this.ScreenResolution;
}
if (!this.enableVideoRecording)
{
options[RecordVideoOptionName] = false;
}
if (!this.enableScreenshots)
{
options[RecordScreenshotsOptionName] = false;
}
if (!this.enableVideoUploadOnPass)
{
options[VideoUploadOnPassOptionName] = false;
}
if (!this.enableLogs)
{
options[RecordLogsOptionName] = false;
}
if (!string.IsNullOrEmpty(this.TestName))
{
options[TestNameOptionName] = this.TestName;
}
if (!string.IsNullOrEmpty(this.Build))
{
options[BuildOptionName] = this.Build;
}
if (this.tags.Count > 0)
{
options[TagsOptionName] = this.tags.ToArray();
}
if (this.IsPassed.HasValue)
{
options[PassedOptionName] = this.IsPassed.Value;
}
if (this.MaximumTestDuration > 0)
{
options[MaximumTestDurationOptionName] = this.MaximumTestDuration;
}
if (this.CommandTimeout > 0)
{
options[CommandTimeoutOptionName] = this.CommandTimeout;
}
if (this.IdleTestTimeout > 0)
{
options[IdleTestTimeoutOptionName] = this.IdleTestTimeout;
}
if (!string.IsNullOrEmpty(this.TunnelIdentifier))
{
options[TunnelIdentifierOptionName] = this.TunnelIdentifier;
}
if (!string.IsNullOrEmpty(this.ParentTunnel))
{
if (string.IsNullOrEmpty(this.TunnelIdentifier))
{
throw new InvalidOperationException("When specifying a parent tunnel, a tunnel identifier must also be specified.");
}
options[ParentTunnelOptionName] = this.ParentTunnel;
}
if (!string.IsNullOrEmpty(this.TimeZone))
{
options[TimeZoneOptionName] = this.TimeZone;
}
if (this.AvoidProxy.HasValue)
{
options[AvoidProxyOptionName] = this.AvoidProxy.Value;
}
if (this.jobVisibility != SauceLabsJobVisibility.Default)
{
if (this.jobVisibility == SauceLabsJobVisibility.PublicRestricted)
{
options[JobVisibilityOptionName] = "public restricted";
}
else
{
options[JobVisibilityOptionName] = this.jobVisibility.ToString().ToLowerInvariant();
}
}
if (this.priority >= 0)
{
options[PriorityOptionName] = this.priority;
}
if (this.ExtendedDebugging)
{
options[ExtendedDebuggingOptionName] = this.ExtendedDebugging;
}
if (this.customData != null)
{
options[CustomDataOptionName] = this.customData;
}
if (this.PreRunExecutable != null)
{
options[PrerunOptionName] = this.PreRunExecutable.ToDictionary();
}
return options;
}
}
}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment