Skip to content

Release v3.0.0 #55

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

Merged
merged 15 commits into from
Jun 20, 2025
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
4 changes: 2 additions & 2 deletions .github/workflows/cd.yml
Original file line number Diff line number Diff line change
Expand Up @@ -23,9 +23,9 @@ jobs:
msbuild BrowserStackLocalIntegrationTests -t:restore -p:Configuration=Release
msbuild BrowserStackLocalIntegrationTests -t:build -p:Configuration=Release
- name: Setup .NET Core
uses: actions/setup-dotnet@v1
uses: actions/setup-dotnet@v3
with:
dotnet-version: 3.1.301
dotnet-version: 7.0.410
- name: Run Integration Tests
env:
BROWSERSTACK_USERNAME: ${{ secrets.BROWSERSTACK_USERNAME }}
Expand Down
4 changes: 2 additions & 2 deletions .github/workflows/ci.yml
Original file line number Diff line number Diff line change
Expand Up @@ -23,9 +23,9 @@ jobs:
msbuild BrowserStackLocalIntegrationTests -t:restore -p:Configuration=Release
msbuild BrowserStackLocalIntegrationTests -t:build -p:Configuration=Release
- name: Setup .NET Core
uses: actions/setup-dotnet@v1
uses: actions/setup-dotnet@v3
with:
dotnet-version: 3.1.301
dotnet-version: 7.0.410
- name: Run Integration Tests
env:
BROWSERSTACK_USERNAME: ${{ secrets.BROWSERSTACK_USERNAME }}
Expand Down
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>net45;netcoreapp3.1</TargetFrameworks>
<TargetFrameworks>net7.0</TargetFrameworks>
<GenerateAssemblyInfo>false</GenerateAssemblyInfo>
<Title>BrowserStackLocal Unit Tests</Title>
<Product>BrowserStackLocal Unit Tests</Product>
Expand All @@ -18,15 +18,10 @@
<PackageReference Include="NUnit3TestAdapter" Version="3.15.1" />
<PackageReference Include="Microsoft.NET.Test.Sdk" Version="16.4.0" />
</ItemGroup>
<ItemGroup Condition=" '$(TargetFramework)' == 'net45' ">
<ItemGroup Condition=" '$(TargetFramework)' == 'net7.0' ">
<PackageReference Include="System.Threading.Tasks.Extensions" Version="4.5.3" />
<Reference Include="BrowserStackLocal">
<HintPath>..\BrowserStackLocal\bin\Debug\net20\BrowserStackLocal.dll</HintPath>
</Reference>
</ItemGroup>
<ItemGroup Condition=" '$(TargetFramework)' == 'netcoreapp3.1' ">
<Reference Include="BrowserStackLocal">
<HintPath>..\BrowserStackLocal\bin\Debug\netstandard2.0\BrowserStackLocal.dll</HintPath>
<HintPath>..\BrowserStackLocal\bin\$(Configuration)\net7.0\BrowserStackLocal.dll</HintPath>
</Reference>
</ItemGroup>
<!-- To modify your build process, add your task inside one of the targets below and uncomment it.
Expand All @@ -36,4 +31,4 @@
<Target Name="AfterBuild">
</Target>
-->
</Project>
</Project>
Original file line number Diff line number Diff line change
Expand Up @@ -30,14 +30,14 @@ public void TestInitialState()
public void TestBinaryPathIsSet()
{
tunnel = new TunnelClass();
tunnel.addBinaryPath("dummyPath");
tunnel.addBinaryPath("dummyPath", "");
Assert.AreEqual(tunnel.getBinaryAbsolute(), "dummyPath");
}
[TestMethod]
public void TestBinaryPathOnNull()
{
tunnel = new TunnelClass();
tunnel.addBinaryPath(null);
tunnel.addBinaryPath(null, "");
string expectedPath = Path.Combine(homepath, ".browserstack");
expectedPath = Path.Combine(expectedPath, binaryName);
Assert.AreEqual(tunnel.getBinaryAbsolute(), expectedPath);
Expand All @@ -46,7 +46,7 @@ public void TestBinaryPathOnNull()
public void TestBinaryPathOnEmpty()
{
tunnel = new TunnelClass();
tunnel.addBinaryPath("");
tunnel.addBinaryPath("", "");
string expectedPath = Path.Combine(homepath, ".browserstack");
expectedPath = Path.Combine(expectedPath, binaryName);
Assert.AreEqual(tunnel.getBinaryAbsolute(), expectedPath);
Expand All @@ -56,7 +56,7 @@ public void TestBinaryPathOnFallback()
{
string expectedPath = "dummyPath";
tunnel = new TunnelClass();
tunnel.addBinaryPath("dummyPath");
tunnel.addBinaryPath("dummyPath", "");
Assert.AreEqual(tunnel.getBinaryAbsolute(), expectedPath);

tunnel.fallbackPaths();
Expand All @@ -78,7 +78,7 @@ public void TestBinaryPathOnFallback()
public void TestBinaryPathOnNoMoreFallback()
{
tunnel = new TunnelClass();
tunnel.addBinaryPath("dummyPath");
tunnel.addBinaryPath("dummyPath", "");
tunnel.fallbackPaths();
tunnel.fallbackPaths();
tunnel.fallbackPaths();
Expand Down
12 changes: 6 additions & 6 deletions BrowserStackLocal/BrowserStackLocal Unit Tests/LocalTests.cs
Original file line number Diff line number Diff line change
Expand Up @@ -107,7 +107,7 @@ public void TestWorksForBinaryPath()
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", ""), 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 @@ -129,7 +129,7 @@ public void TestWorksWithBooleanOptions()
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("", ""), 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 @@ -152,7 +152,7 @@ public void TestWorksWithValueOptions()
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("", ""), Times.Once);
tunnelMock.Verify(mock => mock.addBinaryArguments(
It.IsRegex("-localIdentifier.*dummyIdentifier.*dummyHost.*-proxyHost.*dummyHost.*-proxyPort.*dummyPort.*-proxyUser.*dummyUser.*-proxyPass.*dummyPass.*")
), Times.Once());
Expand All @@ -175,7 +175,7 @@ public void TestWorksWithCustomOptions()
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("", ""), Times.Once);
tunnelMock.Verify(mock => mock.addBinaryArguments(
It.IsRegex("-customBoolKey1.*-customBoolKey2.*-customKey1.*customValue1.*-customKey2.*customValue2.*")
), Times.Once());
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("", ""), 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 @@ -219,7 +219,7 @@ public void TestKillsTunnel()
local.setTunnel(tunnelMock.Object);
local.start(options);
local.stop();
tunnelMock.Verify(mock => mock.addBinaryPath(""), Times.Once);
tunnelMock.Verify(mock => mock.addBinaryPath("", ""), Times.Once);
tunnelMock.Verify(mock => mock.addBinaryArguments(It.IsRegex("-logFile \"" + logAbsolute + "\" .*")), Times.Once());
tunnelMock.Verify(mock => mock.Run("dummyKey", "", logAbsolute, "start"), Times.Once());
}
Expand Down
11 changes: 4 additions & 7 deletions BrowserStackLocal/BrowserStackLocal/BrowserStackLocal.csproj
Original file line number Diff line number Diff line change
Expand Up @@ -2,14 +2,14 @@
<PropertyGroup>
<RootNamespace>BrowserStack</RootNamespace>
<AssemblyName>BrowserStackLocal</AssemblyName>
<TargetFrameworks>net20;netstandard2.0</TargetFrameworks>
<TargetFramework>net7.0</TargetFramework>
<GenerateAssemblyInfo>false</GenerateAssemblyInfo>
<Title>BrowserStackLocal</Title>
<Product>BrowserStackLocal</Product>
<Description>C# Bindings for BrowserStack Local</Description>
<Version>2.4.0</Version>
<AssemblyVersion>2.4.0</AssemblyVersion>
<FileVersion>2.4.0</FileVersion>
<Version>3.0.0</Version>
<AssemblyVersion>3.0.0</AssemblyVersion>
<FileVersion>3.0.0</FileVersion>
<Authors>BrowserStack</Authors>
<Company>BrowserStack</Company>
<Copyright>Copyright © 2016</Copyright>
Expand All @@ -18,9 +18,6 @@
<ItemGroup>
<PackageReference Include="Newtonsoft.Json" Version="13.0.2" />
</ItemGroup>
<ItemGroup Condition=" '$(TargetFramework)' == 'netstandard2.0' ">
<PackageReference Include="System.IO.FileSystem.AccessControl" Version="4.7.0" />
</ItemGroup>
<ItemGroup>
<None Include="MIT-LICENSE.txt" Pack="true" PackagePath="" />
</ItemGroup>
Expand Down
78 changes: 71 additions & 7 deletions BrowserStackLocal/BrowserStackLocal/BrowserStackTunnel.cs
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,12 @@
using System.Security.Principal;
using Newtonsoft.Json.Linq;


using System.Collections.Generic;
using System.Net.Http;
using System.Threading.Tasks;
using Newtonsoft.Json;

namespace BrowserStack
{
public enum LocalState { Idle, Connecting, Connected, Error, Disconnected };
Expand All @@ -16,7 +22,11 @@
{
static readonly string uname = Util.GetUName();
static readonly string binaryName = GetBinaryName();
static readonly string downloadURL = "https://www.browserstack.com/local-testing/downloads/binaries/" + binaryName;

private static string userAgent = "";
private string sourceUrl = null;
private bool isFallbackEnabled = false;
private Exception downloadFailureException = null;

static readonly string homepath = !IsWindows() ?
Environment.GetFolderPath(Environment.SpecialFolder.Personal) :
Expand All @@ -41,7 +51,7 @@
{
return osName.Contains("darwin");
}


static bool IsWindows()
{
Expand Down Expand Up @@ -84,8 +94,15 @@
return "BrowserStackLocal.exe";
}

public virtual void addBinaryPath(string binaryAbsolute)
public virtual void addBinaryPath(string binaryAbsolute, string accessKey, bool fallbackEnabled = false, Exception failureException = null)
{
if (basePathsIndex == -1)
{
/* Called at most twice (primary & a fallback) */
isFallbackEnabled = fallbackEnabled;
downloadFailureException = failureException;
fetchSourceUrl(accessKey);
}
if (binaryAbsolute == null || binaryAbsolute.Trim().Length == 0)
{
binaryAbsolute = Path.Combine(basePaths[++basePathsIndex], binaryName);
Expand All @@ -102,14 +119,19 @@
this.binaryArguments = binaryArguments;
}

public BrowserStackTunnel()
public BrowserStackTunnel(string userAgentParam)
{
userAgent = userAgentParam;
localState = LocalState.Idle;
output = new StringBuilder();
}

public virtual void fallbackPaths()
{
if (File.Exists(binaryAbsolute))
{
File.Delete(binaryAbsolute);
}
if (basePathsIndex >= basePaths.Length - 1)
{
throw new Exception("Binary not found or failed to launch. Make sure that BrowserStackLocal is not already running.");
Expand All @@ -121,7 +143,7 @@
public void modifyBinaryPermission()
{
if (!IsWindows())
{
{
try
{
using (Process proc = Process.Start("/bin/bash", $"-c \"chmod 0755 {this.binaryAbsolute}\""))
Expand All @@ -138,21 +160,63 @@
{
DirectoryInfo dInfo = new DirectoryInfo(binaryAbsolute);
DirectorySecurity dSecurity = dInfo.GetAccessControl();
dSecurity.AddAccessRule(new FileSystemAccessRule(new SecurityIdentifier(WellKnownSidType.WorldSid, null), FileSystemRights.FullControl, InheritanceFlags.ObjectInherit | InheritanceFlags.ContainerInherit, PropagationFlags.NoPropagateInherit, AccessControlType.Allow));

Check warning on line 163 in BrowserStackLocal/BrowserStackLocal/BrowserStackTunnel.cs

View workflow job for this annotation

GitHub Actions / build

This call site is reachable on all platforms. 'PropagationFlags.NoPropagateInherit' is only supported on: 'windows'. (https://learn.microsoft.com/dotnet/fundamentals/code-analysis/quality-rules/ca1416)

Check warning on line 163 in BrowserStackLocal/BrowserStackLocal/BrowserStackTunnel.cs

View workflow job for this annotation

GitHub Actions / build

This call site is reachable on all platforms. 'AccessControlType.Allow' is only supported on: 'windows'. (https://learn.microsoft.com/dotnet/fundamentals/code-analysis/quality-rules/ca1416)

Check warning on line 163 in BrowserStackLocal/BrowserStackLocal/BrowserStackTunnel.cs

View workflow job for this annotation

GitHub Actions / build

This call site is reachable on all platforms. 'FileSystemSecurity.AddAccessRule(FileSystemAccessRule)' is only supported on: 'windows'. (https://learn.microsoft.com/dotnet/fundamentals/code-analysis/quality-rules/ca1416)

Check warning on line 163 in BrowserStackLocal/BrowserStackLocal/BrowserStackTunnel.cs

View workflow job for this annotation

GitHub Actions / build

This call site is reachable on all platforms. 'InheritanceFlags.ObjectInherit' is only supported on: 'windows'. (https://learn.microsoft.com/dotnet/fundamentals/code-analysis/quality-rules/ca1416)

Check warning on line 163 in BrowserStackLocal/BrowserStackLocal/BrowserStackTunnel.cs

View workflow job for this annotation

GitHub Actions / build

This call site is reachable on all platforms. 'InheritanceFlags.ContainerInherit' is only supported on: 'windows'. (https://learn.microsoft.com/dotnet/fundamentals/code-analysis/quality-rules/ca1416)

Check warning on line 163 in BrowserStackLocal/BrowserStackLocal/BrowserStackTunnel.cs

View workflow job for this annotation

GitHub Actions / build

This call site is reachable on all platforms. 'WellKnownSidType.WorldSid' is only supported on: 'windows'. (https://learn.microsoft.com/dotnet/fundamentals/code-analysis/quality-rules/ca1416)

Check warning on line 163 in BrowserStackLocal/BrowserStackLocal/BrowserStackTunnel.cs

View workflow job for this annotation

GitHub Actions / build

This call site is reachable on all platforms. 'FileSystemRights.FullControl' is only supported on: 'windows'. (https://learn.microsoft.com/dotnet/fundamentals/code-analysis/quality-rules/ca1416)
dInfo.SetAccessControl(dSecurity);
}
}

private string fetchSourceUrl(string accessKey)
{
var url = "https://local.browserstack.com/binary/api/v1/endpoint";

using (var client = new HttpClient())
{
var data = new Dictionary<string, object>
{
{ "auth_token", accessKey }
};

if (isFallbackEnabled)
{
data["error_message"] = downloadFailureException.Message;
}

var jsonData = JsonConvert.SerializeObject(data);
var content = new StringContent(jsonData, Encoding.UTF8, "application/json");

client.DefaultRequestHeaders.Add("User-Agent", userAgent);
if (isFallbackEnabled)
{
client.DefaultRequestHeaders.Add("X-Local-Fallback-Cloudflare", "true");
}

var response = client.PostAsync(url, content).Result;

response.EnsureSuccessStatusCode();

var responseString = response.Content.ReadAsStringAsync().Result;

var jsonResponse = JObject.Parse(responseString);

if (jsonResponse["error"] != null)
{
throw new Exception((string)jsonResponse["error"]);
}

sourceUrl = jsonResponse["data"]?["endpoint"]?.ToString();
return sourceUrl;
}
}

public void downloadBinary()
{
string binaryDirectory = Path.Combine(this.binaryAbsolute, "..");
//string binaryAbsolute = Path.Combine(binaryDirectory, binaryName);

Directory.CreateDirectory(binaryDirectory);

using (var client = new WebClient())

Check warning on line 217 in BrowserStackLocal/BrowserStackLocal/BrowserStackTunnel.cs

View workflow job for this annotation

GitHub Actions / build

'WebClient.WebClient()' is obsolete: 'WebRequest, HttpWebRequest, ServicePoint, and WebClient are obsolete. Use HttpClient instead.' (https://aka.ms/dotnet-warnings/SYSLIB0014)
{
client.DownloadFile(downloadURL, this.binaryAbsolute);
client.DownloadFile(sourceUrl + "/" + binaryName, this.binaryAbsolute);
}

if (!File.Exists(binaryAbsolute))
Expand Down
Loading
Loading