Skip to content
Open
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
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@
<PropertyGroup>
<RootNamespace>BrowserStackLocal_Unit_Tests</RootNamespace>
<AssemblyName>BrowserStackLocal Unit Tests</AssemblyName>
<TargetFrameworks>net7.0</TargetFrameworks>
<TargetFrameworks>net6.0</TargetFrameworks>
<GenerateAssemblyInfo>false</GenerateAssemblyInfo>
<Title>BrowserStackLocal Unit Tests</Title>
<Product>BrowserStackLocal Unit Tests</Product>
Expand All @@ -18,10 +18,10 @@
<PackageReference Include="NUnit3TestAdapter" Version="3.15.1" />
<PackageReference Include="Microsoft.NET.Test.Sdk" Version="16.4.0" />
</ItemGroup>
<ItemGroup Condition=" '$(TargetFramework)' == 'net7.0' ">
<ItemGroup Condition=" '$(TargetFramework)' == 'net6.0' ">
<PackageReference Include="System.Threading.Tasks.Extensions" Version="4.5.3" />
<Reference Include="BrowserStackLocal">
<HintPath>..\BrowserStackLocal\bin\$(Configuration)\net7.0\BrowserStackLocal.dll</HintPath>
<HintPath>..\BrowserStackLocal\bin\$(Configuration)\net6.0\BrowserStackLocal.dll</HintPath>
</Reference>
</ItemGroup>
<!-- To modify your build process, add your task inside one of the targets below and uncomment it.
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -17,7 +17,7 @@ public class BrowserStackTunnelTests
static readonly string homepath = os.Platform.ToString() == "Unix" ?
Environment.GetFolderPath(Environment.SpecialFolder.Personal) :
Environment.ExpandEnvironmentVariables("%HOMEDRIVE%%HOMEPATH%");
static readonly string binaryName = os.Platform.ToString() == "Unix" ? "BrowserStackLocal-darwin-x64" : "BrowserStackLocal.exe";
static readonly string binaryName = BrowserStackTunnel.GetBinaryName();
private TunnelClass tunnel;
[TestMethod]
public void TestInitialState()
Expand Down Expand Up @@ -103,12 +103,36 @@ public void TestBinaryArgumentsAreEmptyOnNull()
}


[TestMethod]
public void TestGetBinaryNameReturnsKnownPlatformBinary()
{
string result = BrowserStackTunnel.GetBinaryName();
string[] knownBinaries = new[] {
"BrowserStackLocal.exe",
"BrowserStackLocal-darwin-x64",
"BrowserStackLocal-linux-x64",
"BrowserStackLocal-linux-ia32",
"BrowserStackLocal-linux-arm64",
"BrowserStackLocal-alpine"
};
Assert.Contains(result, knownBinaries);
}

[TestMethod]
public void TestSetProxyAcceptsHostAndPort()
{
tunnel = new TunnelClass();
Assert.DoesNotThrow(() => tunnel.SetProxy("proxy.example.com", 8080));
Assert.DoesNotThrow(() => tunnel.SetProxy(null, 0));
}

public void testFallbackException()
{
tunnel.fallbackPaths();
}
public class TunnelClass : BrowserStackTunnel
{
public TunnelClass() : base("test-user-agent") {}
public StringBuilder getOutputBuilder()
{
return output;
Expand Down
81 changes: 65 additions & 16 deletions BrowserStackLocal/BrowserStackLocal Unit Tests/LocalTests.cs
Original file line number Diff line number Diff line change
Expand Up @@ -37,7 +37,7 @@ public void TestThrowsWithNoAccessKey()
options.Add(new KeyValuePair<string, string>("key", ""));
local = new LocalClass();

Mock<BrowserStackTunnel> tunnelMock = new Mock<BrowserStackTunnel>();
Mock<BrowserStackTunnel> tunnelMock = new Mock<BrowserStackTunnel>("test-user-agent");
tunnelMock.Setup(mock => mock.Run("", "", logAbsolute, "start"));
local.setTunnel(tunnelMock.Object);

Expand All @@ -53,7 +53,7 @@ public void TestWorksWithAccessKeyInOptions()
options = new List<KeyValuePair<string, string>>();
options.Add(new KeyValuePair<string, string>("key", "dummyKey"));
local = new LocalClass();
Mock<BrowserStackTunnel> tunnelMock = new Mock<BrowserStackTunnel>();
Mock<BrowserStackTunnel> tunnelMock = new Mock<BrowserStackTunnel>("test-user-agent");
local.setTunnel(tunnelMock.Object);
Assert.DoesNotThrow(new TestDelegate(startWithOptions),
"BROWSERSTACK_ACCESS_KEY cannot be empty. Specify one by adding key to options or adding to the environment variable BROWSERSTACK_ACCESS_KEY.");
Expand All @@ -68,7 +68,7 @@ public void TestWorksWithAccessKeyNotInOptions()
Environment.SetEnvironmentVariable("BROWSERSTACK_ACCESS_KEY", "envDummyKey");
options = new List<KeyValuePair<string, string>>();
local = new LocalClass();
Mock<BrowserStackTunnel> tunnelMock = new Mock<BrowserStackTunnel>();
Mock<BrowserStackTunnel> tunnelMock = new Mock<BrowserStackTunnel>("test-user-agent");
tunnelMock.Setup(mock => mock.Run("envDummyKey", "", logAbsolute, "start"));
local.setTunnel(tunnelMock.Object);
Assert.DoesNotThrow(new TestDelegate(startWithOptions),
Expand All @@ -86,7 +86,7 @@ public void TestWorksForFolderTesting()
options.Add(new KeyValuePair<string, string>("f", "dummyFolderPath"));

local = new LocalClass();
Mock<BrowserStackTunnel> tunnelMock = new Mock<BrowserStackTunnel>();
Mock<BrowserStackTunnel> tunnelMock = new Mock<BrowserStackTunnel>("test-user-agent");
tunnelMock.Setup(mock => mock.Run("dummyKey", "dummyFolderPath", logAbsolute, "start"));
local.setTunnel(tunnelMock.Object);
local.start(options);
Expand All @@ -103,11 +103,11 @@ public void TestWorksForBinaryPath()
options.Add(new KeyValuePair<string, string>("binarypath", "dummyPath"));

local = new LocalClass();
Mock<BrowserStackTunnel> tunnelMock = new Mock<BrowserStackTunnel>();
Mock<BrowserStackTunnel> tunnelMock = new Mock<BrowserStackTunnel>("test-user-agent");
tunnelMock.Setup(mock => mock.Run("dummyKey", "", logAbsolute, "start"));
local.setTunnel(tunnelMock.Object);
local.start(options);
tunnelMock.Verify(mock => mock.addBinaryPath("dummyPath", ""), Times.Once);
tunnelMock.Verify(mock => mock.addBinaryPath("dummyPath", "", It.IsAny<bool>(), It.IsAny<Exception>()), Times.Once);
tunnelMock.Verify(mock => mock.addBinaryArguments(It.IsRegex("-logFile \"" + logAbsolute + "\" .*")), Times.Once());
tunnelMock.Verify(mock => mock.Run("dummyKey", "", logAbsolute, "start"), Times.Once());
local.stop();
Expand All @@ -125,11 +125,11 @@ public void TestWorksWithBooleanOptions()
options.Add(new KeyValuePair<string, string>("onlyAutomate", "true"));

local = new LocalClass();
Mock<BrowserStackTunnel> tunnelMock = new Mock<BrowserStackTunnel>();
Mock<BrowserStackTunnel> tunnelMock = new Mock<BrowserStackTunnel>("test-user-agent");
tunnelMock.Setup(mock => mock.Run("dummyKey", "", logAbsolute, "start"));
local.setTunnel(tunnelMock.Object);
local.start(options);
tunnelMock.Verify(mock => mock.addBinaryPath("", ""), Times.Once);
tunnelMock.Verify(mock => mock.addBinaryPath("", "", It.IsAny<bool>(), It.IsAny<Exception>()), Times.Once);
tunnelMock.Verify(mock => mock.addBinaryArguments(It.IsRegex("-vvv.*-force.*-forcelocal.*-forceproxy.*-onlyAutomate.*")), Times.Once());
tunnelMock.Verify(mock => mock.Run("dummyKey", "", logAbsolute, "start"), Times.Once());
local.stop();
Expand All @@ -148,11 +148,11 @@ public void TestWorksWithValueOptions()
options.Add(new KeyValuePair<string, string>("proxyPass", "dummyPass"));

local = new LocalClass();
Mock<BrowserStackTunnel> tunnelMock = new Mock<BrowserStackTunnel>();
Mock<BrowserStackTunnel> tunnelMock = new Mock<BrowserStackTunnel>("test-user-agent");
tunnelMock.Setup(mock =>mock.Run("dummyKey", "", logAbsolute, "start"));
local.setTunnel(tunnelMock.Object);
local.start(options);
tunnelMock.Verify(mock => mock.addBinaryPath("", ""), Times.Once);
tunnelMock.Verify(mock => mock.addBinaryPath("", "", It.IsAny<bool>(), It.IsAny<Exception>()), Times.Once);
tunnelMock.Verify(mock => mock.addBinaryArguments(
It.IsRegex("-localIdentifier.*dummyIdentifier.*dummyHost.*-proxyHost.*dummyHost.*-proxyPort.*dummyPort.*-proxyUser.*dummyUser.*-proxyPass.*dummyPass.*")
), Times.Once());
Expand All @@ -171,11 +171,11 @@ public void TestWorksWithCustomOptions()
options.Add(new KeyValuePair<string, string>("customKey2", "customValue2"));

local = new LocalClass();
Mock<BrowserStackTunnel> tunnelMock = new Mock<BrowserStackTunnel>();
Mock<BrowserStackTunnel> tunnelMock = new Mock<BrowserStackTunnel>("test-user-agent");
tunnelMock.Setup(mock => mock.Run("dummyKey", "", logAbsolute, "start"));
local.setTunnel(tunnelMock.Object);
local.start(options);
tunnelMock.Verify(mock => mock.addBinaryPath("", ""), Times.Once);
tunnelMock.Verify(mock => mock.addBinaryPath("", "", It.IsAny<bool>(), It.IsAny<Exception>()), Times.Once);
tunnelMock.Verify(mock => mock.addBinaryArguments(
It.IsRegex("-customBoolKey1.*-customBoolKey2.*-customKey1.*customValue1.*-customKey2.*customValue2.*")
), Times.Once());
Expand All @@ -191,7 +191,7 @@ public void TestCallsFallbackOnFailure()

local = new LocalClass();
int count = 0;
Mock<BrowserStackTunnel> tunnelMock = new Mock<BrowserStackTunnel>();
Mock<BrowserStackTunnel> tunnelMock = new Mock<BrowserStackTunnel>("test-user-agent");
tunnelMock.Setup(mock => mock.Run("dummyKey", "", logAbsolute, "start")).Callback(() =>
{
count++;
Expand All @@ -200,7 +200,7 @@ public void TestCallsFallbackOnFailure()
});
local.setTunnel(tunnelMock.Object);
local.start(options);
tunnelMock.Verify(mock => mock.addBinaryPath("", ""), Times.Once);
tunnelMock.Verify(mock => mock.addBinaryPath("", "", It.IsAny<bool>(), It.IsAny<Exception>()), Times.Once);
tunnelMock.Verify(mock => mock.addBinaryArguments(It.IsRegex("-logFile \"" + logAbsolute + "\" .*")), Times.Once());
tunnelMock.Verify(mock => mock.Run("dummyKey", "", logAbsolute, "start"), Times.Exactly(2));
tunnelMock.Verify(mock => mock.fallbackPaths(), Times.Once());
Expand All @@ -214,16 +214,65 @@ public void TestKillsTunnel()
options.Add(new KeyValuePair<string, string>("key", "dummyKey"));

local = new LocalClass();
Mock<BrowserStackTunnel> tunnelMock = new Mock<BrowserStackTunnel>();
Mock<BrowserStackTunnel> tunnelMock = new Mock<BrowserStackTunnel>("test-user-agent");
tunnelMock.Setup(mock => mock.Run("dummyKey", "", logAbsolute, "start"));
local.setTunnel(tunnelMock.Object);
local.start(options);
local.stop();
tunnelMock.Verify(mock => mock.addBinaryPath("", ""), Times.Once);
tunnelMock.Verify(mock => mock.addBinaryPath("", "", It.IsAny<bool>(), It.IsAny<Exception>()), Times.Once);
tunnelMock.Verify(mock => mock.addBinaryArguments(It.IsRegex("-logFile \"" + logAbsolute + "\" .*")), Times.Once());
tunnelMock.Verify(mock => mock.Run("dummyKey", "", logAbsolute, "start"), Times.Once());
}

[TestMethod]
public void TestSetProxyCalledWithProxyOptions()
{
options = new List<KeyValuePair<string, string>>();
options.Add(new KeyValuePair<string, string>("key", "dummyKey"));
options.Add(new KeyValuePair<string, string>("proxyHost", "proxy.example.com"));
options.Add(new KeyValuePair<string, string>("proxyPort", "8080"));

local = new LocalClass();
Mock<BrowserStackTunnel> tunnelMock = new Mock<BrowserStackTunnel>("test-user-agent");
tunnelMock.Setup(mock => mock.Run("dummyKey", "", logAbsolute, "start"));
local.setTunnel(tunnelMock.Object);
local.start(options);
tunnelMock.Verify(mock => mock.SetProxy("proxy.example.com", 8080), Times.Once);
local.stop();
}

[TestMethod]
public void TestSetProxyCalledWithDefaultsWhenAbsent()
{
options = new List<KeyValuePair<string, string>>();
options.Add(new KeyValuePair<string, string>("key", "dummyKey"));

local = new LocalClass();
Mock<BrowserStackTunnel> tunnelMock = new Mock<BrowserStackTunnel>("test-user-agent");
tunnelMock.Setup(mock => mock.Run("dummyKey", "", logAbsolute, "start"));
local.setTunnel(tunnelMock.Object);
local.start(options);
tunnelMock.Verify(mock => mock.SetProxy(null, 0), Times.Once);
local.stop();
}

[TestMethod]
public void TestSetProxyIgnoresInvalidPort()
{
options = new List<KeyValuePair<string, string>>();
options.Add(new KeyValuePair<string, string>("key", "dummyKey"));
options.Add(new KeyValuePair<string, string>("proxyHost", "proxy.example.com"));
options.Add(new KeyValuePair<string, string>("proxyPort", "not-a-number"));

local = new LocalClass();
Mock<BrowserStackTunnel> tunnelMock = new Mock<BrowserStackTunnel>("test-user-agent");
tunnelMock.Setup(mock => mock.Run("dummyKey", "", logAbsolute, "start"));
local.setTunnel(tunnelMock.Object);
local.start(options);
tunnelMock.Verify(mock => mock.SetProxy("proxy.example.com", 0), Times.Once);
local.stop();
}

public void startWithOptions()
{
local.start(options);
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -2,14 +2,14 @@
<PropertyGroup>
<RootNamespace>BrowserStack</RootNamespace>
<AssemblyName>BrowserStackLocal</AssemblyName>
<TargetFramework>net7.0</TargetFramework>
<TargetFramework>net6.0</TargetFramework>
<GenerateAssemblyInfo>false</GenerateAssemblyInfo>
<Title>BrowserStackLocal</Title>
<Product>BrowserStackLocal</Product>
<Description>C# Bindings for BrowserStack Local</Description>
<Version>3.0.0</Version>
<AssemblyVersion>3.0.0</AssemblyVersion>
<FileVersion>3.0.0</FileVersion>
<Version>3.1.0</Version>
<AssemblyVersion>3.1.0</AssemblyVersion>
<FileVersion>3.1.0</FileVersion>
<Authors>BrowserStack</Authors>
<Company>BrowserStack</Company>
<Copyright>Copyright © 2016</Copyright>
Expand Down
35 changes: 33 additions & 2 deletions BrowserStackLocal/BrowserStackLocal/BrowserStackTunnel.cs
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,7 @@
using System.Collections.Generic;
using System.Net.Http;
using System.Threading.Tasks;
using System.Runtime.InteropServices;
using Newtonsoft.Json;

namespace BrowserStack
Expand All @@ -28,6 +29,12 @@ public class BrowserStackTunnel : IDisposable
private bool isFallbackEnabled = false;
private Exception downloadFailureException = null;

// Set via SetProxy(...) before fetchSourceUrl / downloadBinary; otherwise the
// binary download bypasses the user's proxy even when -proxyHost is passed
// through to the running binary via argumentString.
private string proxyHost = null;
private int proxyPort = 0;

static readonly string homepath = !IsWindows() ?
Environment.GetFolderPath(Environment.SpecialFolder.Personal) :
Environment.ExpandEnvironmentVariables("%HOMEDRIVE%%HOMEPATH%");
Expand Down Expand Up @@ -77,13 +84,14 @@ static bool IsAlpine()
return false;
}

static string GetBinaryName()
public static string GetBinaryName()
{
if (IsWindows()) return "BrowserStackLocal.exe";
if (IsDarwin(uname)) return "BrowserStackLocal-darwin-x64";

if (IsLinux(uname))
{
if (IsArm64()) return "BrowserStackLocal-linux-arm64";
if (Util.Is64BitOS())
{
return IsAlpine() ? "BrowserStackLocal-alpine" : "BrowserStackLocal-linux-x64";
Expand All @@ -94,6 +102,17 @@ static string GetBinaryName()
return "BrowserStackLocal.exe";
}

static bool IsArm64()
{
return RuntimeInformation.OSArchitecture == Architecture.Arm64;
}

public virtual void SetProxy(string host, int port)
{
proxyHost = host;
proxyPort = port;
}

public virtual void addBinaryPath(string binaryAbsolute, string accessKey, bool fallbackEnabled = false, Exception failureException = null)
{
if (basePathsIndex == -1)
Expand Down Expand Up @@ -169,7 +188,14 @@ private string fetchSourceUrl(string accessKey)
{
var url = "https://local.browserstack.com/binary/api/v1/endpoint";

using (var client = new HttpClient())
HttpClientHandler handler = new HttpClientHandler();
if (!string.IsNullOrEmpty(proxyHost) && proxyPort > 0)
{
handler.Proxy = new WebProxy(proxyHost, proxyPort);
handler.UseProxy = true;
}

using (var client = new HttpClient(handler))
{
var data = new Dictionary<string, object>
{
Expand Down Expand Up @@ -216,6 +242,11 @@ public void downloadBinary()

using (var client = new WebClient())
{
client.Headers.Add(HttpRequestHeader.UserAgent, userAgent);
if (!string.IsNullOrEmpty(proxyHost) && proxyPort > 0)
{
client.Proxy = new WebProxy(proxyHost, proxyPort);
}
client.DownloadFile(sourceUrl + "/" + binaryName, this.binaryAbsolute);
}

Expand Down
15 changes: 15 additions & 0 deletions BrowserStackLocal/BrowserStackLocal/Local.cs
Original file line number Diff line number Diff line change
Expand Up @@ -17,6 +17,8 @@ public class Local
private string userAgent = "browserstack-local-csharp";
private bool isFallbackEnabled = false;
private Exception downloadFailureException = null;
private string proxyHost = null;
private int proxyPort = 0;

protected BrowserStackTunnel tunnel = null;
private static KeyValuePair<string, string> emptyStringPair = new KeyValuePair<string, string>();
Expand Down Expand Up @@ -75,6 +77,18 @@ private void addArgs(string key, string value)
}
else
{
if (key.Equals("proxyHost"))
{
proxyHost = value;
}
else if (key.Equals("proxyPort"))
{
if (!int.TryParse(value, out proxyPort))
{
Console.Error.WriteLine($"Invalid proxyPort '{value}'; ignoring proxy for binary download");
}
}

result = valueCommands.Find(pair => pair.Key == key);
if (!result.Equals(emptyStringPair))
{
Expand Down Expand Up @@ -226,6 +240,7 @@ public void start(List<KeyValuePair<string, string>> options)
argumentString += "-logFile \"" + customLogPath + "\" ";
argumentString += "--source \"c-sharp:" + bindingVersion + "\" ";
tunnel.addBinaryArguments(argumentString);
tunnel.SetProxy(proxyHost, proxyPort);

DownloadVerifyAndRunBinary();
}
Expand Down
Loading
Loading