From eab9f01ad6a6a9cbbd2952c9ba7ad09edbdb5025 Mon Sep 17 00:00:00 2001 From: Yash Saraf Date: Mon, 1 Jun 2026 22:47:23 +0530 Subject: [PATCH 1/4] 3.1.0: arm64 + proxy passthrough for binary download - GetBinaryName made public and adds Linux arm64 branch via RuntimeInformation.OSArchitecture. arm64 wins over alpine to match the Node SDK ordering. - BrowserStackTunnel.SetProxy(host, port) + proxyHost/proxyPort fields. - fetchSourceUrl builds HttpClientHandler with Proxy = WebProxy(host, port) when proxy is set. - downloadBinary sets WebClient.Proxy = WebProxy(host, port) when set. (Keeping WebClient; proxy plumbing on it is one line.) - Local.addArgs captures proxyHost/proxyPort on local fields in addition to appending to argumentString. Local.start calls tunnel.SetProxy(...) before DownloadVerifyAndRunBinary. - Test fixtures derive binaryName from BrowserStackTunnel.GetBinaryName() so arm64 CI hosts pick the right binary. Tracks LOC-6563. Co-Authored-By: Claude Opus 4.7 (1M context) --- .../BrowserStackTunnelTests.cs | 2 +- .../BrowserStackLocal.csproj | 6 ++-- .../BrowserStackLocal/BrowserStackTunnel.cs | 30 +++++++++++++++++-- BrowserStackLocal/BrowserStackLocal/Local.cs | 12 ++++++++ .../IntegrationTests.cs | 2 +- 5 files changed, 45 insertions(+), 7 deletions(-) diff --git a/BrowserStackLocal/BrowserStackLocal Unit Tests/BrowserStackTunnelTests.cs b/BrowserStackLocal/BrowserStackLocal Unit Tests/BrowserStackTunnelTests.cs index ad13e95..0630e7f 100644 --- a/BrowserStackLocal/BrowserStackLocal Unit Tests/BrowserStackTunnelTests.cs +++ b/BrowserStackLocal/BrowserStackLocal Unit Tests/BrowserStackTunnelTests.cs @@ -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() diff --git a/BrowserStackLocal/BrowserStackLocal/BrowserStackLocal.csproj b/BrowserStackLocal/BrowserStackLocal/BrowserStackLocal.csproj index 688943d..0ba4f0e 100644 --- a/BrowserStackLocal/BrowserStackLocal/BrowserStackLocal.csproj +++ b/BrowserStackLocal/BrowserStackLocal/BrowserStackLocal.csproj @@ -7,9 +7,9 @@ BrowserStackLocal BrowserStackLocal C# Bindings for BrowserStack Local - 3.0.0 - 3.0.0 - 3.0.0 + 3.1.0 + 3.1.0 + 3.1.0 BrowserStack BrowserStack Copyright © 2016 diff --git a/BrowserStackLocal/BrowserStackLocal/BrowserStackTunnel.cs b/BrowserStackLocal/BrowserStackLocal/BrowserStackTunnel.cs index 4134cc2..5430622 100644 --- a/BrowserStackLocal/BrowserStackLocal/BrowserStackTunnel.cs +++ b/BrowserStackLocal/BrowserStackLocal/BrowserStackTunnel.cs @@ -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 @@ -27,6 +28,8 @@ public class BrowserStackTunnel : IDisposable private string sourceUrl = null; private bool isFallbackEnabled = false; private Exception downloadFailureException = null; + private string proxyHost = null; + private int proxyPort = 0; static readonly string homepath = !IsWindows() ? Environment.GetFolderPath(Environment.SpecialFolder.Personal) : @@ -77,13 +80,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"; @@ -94,6 +98,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) @@ -169,7 +184,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 { @@ -216,6 +238,10 @@ public void downloadBinary() using (var client = new WebClient()) { + if (!string.IsNullOrEmpty(proxyHost) && proxyPort > 0) + { + client.Proxy = new WebProxy(proxyHost, proxyPort); + } client.DownloadFile(sourceUrl + "/" + binaryName, this.binaryAbsolute); } diff --git a/BrowserStackLocal/BrowserStackLocal/Local.cs b/BrowserStackLocal/BrowserStackLocal/Local.cs index 6c57251..c7fa168 100644 --- a/BrowserStackLocal/BrowserStackLocal/Local.cs +++ b/BrowserStackLocal/BrowserStackLocal/Local.cs @@ -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 emptyStringPair = new KeyValuePair(); @@ -75,6 +77,15 @@ private void addArgs(string key, string value) } else { + if (key.Equals("proxyHost")) + { + proxyHost = value; + } + else if (key.Equals("proxyPort")) + { + int.TryParse(value, out proxyPort); + } + result = valueCommands.Find(pair => pair.Key == key); if (!result.Equals(emptyStringPair)) { @@ -226,6 +237,7 @@ public void start(List> options) argumentString += "-logFile \"" + customLogPath + "\" "; argumentString += "--source \"c-sharp:" + bindingVersion + "\" "; tunnel.addBinaryArguments(argumentString); + tunnel.SetProxy(proxyHost, proxyPort); DownloadVerifyAndRunBinary(); } diff --git a/BrowserStackLocal/BrowserStackLocalIntegrationTests/IntegrationTests.cs b/BrowserStackLocal/BrowserStackLocalIntegrationTests/IntegrationTests.cs index c0b1e39..9f6bed6 100644 --- a/BrowserStackLocal/BrowserStackLocalIntegrationTests/IntegrationTests.cs +++ b/BrowserStackLocal/BrowserStackLocalIntegrationTests/IntegrationTests.cs @@ -12,7 +12,7 @@ namespace BrowserStackLocalIntegrationTests public class IntegrationTests { static readonly OperatingSystem os = Environment.OSVersion; - static readonly string binaryName = os.Platform.ToString() == "Unix" ? "BrowserStackLocal-darwin-x64" : "BrowserStackLocal"; + static readonly string binaryName = BrowserStackTunnel.GetBinaryName().Replace(".exe", ""); private static string username = Environment.GetEnvironmentVariable("BROWSERSTACK_USERNAME"); private static string accesskey = Environment.GetEnvironmentVariable("BROWSERSTACK_ACCESS_KEY"); From 1bade9bd42e56a76531eead57e66428495c7a9ae Mon Sep 17 00:00:00 2001 From: Yash Saraf Date: Mon, 1 Jun 2026 23:09:38 +0530 Subject: [PATCH 2/4] Code review feedback: error log + doc comment + unit tests - Local.cs addArgs: int.TryParse failure now logs to stderr instead of silently dropping the proxy on download with no signal to the user. - BrowserStackTunnel.cs: doc comment on proxyHost/proxyPort fields documents the required ordering (SetProxy before addBinaryPath). - BrowserStackTunnelTests.cs: 2 new unit tests covering GetBinaryName return-set and SetProxy no-throw contract. Adds the missing TunnelClass constructor that broke compile pre-this-PR. - LocalTests.cs: 3 new unit tests covering proxy-options-to-tunnel plumbing (with proxy, without proxy, with invalid port). Fixes pre-existing CS0854 errors on Mock.Verify expressions with optional args, and pre-existing missing-ctor errors on Mock(). Project now compiles for the first time in years; 5 new tests pass, 11 pre-existing tests still fail with pre-existing assertion bugs (unchanged behavior; CI does not run this project anyway). Code review findings #3, #4, #5 from LOC-6563 review. Co-Authored-By: Claude Opus 4.7 (1M context) --- .../BrowserStackTunnelTests.cs | 24 ++++++ .../LocalTests.cs | 81 +++++++++++++++---- .../BrowserStackLocal/BrowserStackTunnel.cs | 4 + BrowserStackLocal/BrowserStackLocal/Local.cs | 5 +- 4 files changed, 97 insertions(+), 17 deletions(-) diff --git a/BrowserStackLocal/BrowserStackLocal Unit Tests/BrowserStackTunnelTests.cs b/BrowserStackLocal/BrowserStackLocal Unit Tests/BrowserStackTunnelTests.cs index 0630e7f..8c83b0a 100644 --- a/BrowserStackLocal/BrowserStackLocal Unit Tests/BrowserStackTunnelTests.cs +++ b/BrowserStackLocal/BrowserStackLocal Unit Tests/BrowserStackTunnelTests.cs @@ -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; diff --git a/BrowserStackLocal/BrowserStackLocal Unit Tests/LocalTests.cs b/BrowserStackLocal/BrowserStackLocal Unit Tests/LocalTests.cs index 8be3eb6..918afa9 100644 --- a/BrowserStackLocal/BrowserStackLocal Unit Tests/LocalTests.cs +++ b/BrowserStackLocal/BrowserStackLocal Unit Tests/LocalTests.cs @@ -37,7 +37,7 @@ public void TestThrowsWithNoAccessKey() options.Add(new KeyValuePair("key", "")); local = new LocalClass(); - Mock tunnelMock = new Mock(); + Mock tunnelMock = new Mock("test-user-agent"); tunnelMock.Setup(mock => mock.Run("", "", logAbsolute, "start")); local.setTunnel(tunnelMock.Object); @@ -53,7 +53,7 @@ public void TestWorksWithAccessKeyInOptions() options = new List>(); options.Add(new KeyValuePair("key", "dummyKey")); local = new LocalClass(); - Mock tunnelMock = new Mock(); + Mock tunnelMock = new Mock("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."); @@ -68,7 +68,7 @@ public void TestWorksWithAccessKeyNotInOptions() Environment.SetEnvironmentVariable("BROWSERSTACK_ACCESS_KEY", "envDummyKey"); options = new List>(); local = new LocalClass(); - Mock tunnelMock = new Mock(); + Mock tunnelMock = new Mock("test-user-agent"); tunnelMock.Setup(mock => mock.Run("envDummyKey", "", logAbsolute, "start")); local.setTunnel(tunnelMock.Object); Assert.DoesNotThrow(new TestDelegate(startWithOptions), @@ -86,7 +86,7 @@ public void TestWorksForFolderTesting() options.Add(new KeyValuePair("f", "dummyFolderPath")); local = new LocalClass(); - Mock tunnelMock = new Mock(); + Mock tunnelMock = new Mock("test-user-agent"); tunnelMock.Setup(mock => mock.Run("dummyKey", "dummyFolderPath", logAbsolute, "start")); local.setTunnel(tunnelMock.Object); local.start(options); @@ -103,11 +103,11 @@ public void TestWorksForBinaryPath() options.Add(new KeyValuePair("binarypath", "dummyPath")); local = new LocalClass(); - Mock tunnelMock = new Mock(); + Mock tunnelMock = new Mock("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(), It.IsAny()), 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(); @@ -125,11 +125,11 @@ public void TestWorksWithBooleanOptions() options.Add(new KeyValuePair("onlyAutomate", "true")); local = new LocalClass(); - Mock tunnelMock = new Mock(); + Mock tunnelMock = new Mock("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(), It.IsAny()), 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(); @@ -148,11 +148,11 @@ public void TestWorksWithValueOptions() options.Add(new KeyValuePair("proxyPass", "dummyPass")); local = new LocalClass(); - Mock tunnelMock = new Mock(); + Mock tunnelMock = new Mock("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(), It.IsAny()), Times.Once); tunnelMock.Verify(mock => mock.addBinaryArguments( It.IsRegex("-localIdentifier.*dummyIdentifier.*dummyHost.*-proxyHost.*dummyHost.*-proxyPort.*dummyPort.*-proxyUser.*dummyUser.*-proxyPass.*dummyPass.*") ), Times.Once()); @@ -171,11 +171,11 @@ public void TestWorksWithCustomOptions() options.Add(new KeyValuePair("customKey2", "customValue2")); local = new LocalClass(); - Mock tunnelMock = new Mock(); + Mock tunnelMock = new Mock("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(), It.IsAny()), Times.Once); tunnelMock.Verify(mock => mock.addBinaryArguments( It.IsRegex("-customBoolKey1.*-customBoolKey2.*-customKey1.*customValue1.*-customKey2.*customValue2.*") ), Times.Once()); @@ -191,7 +191,7 @@ public void TestCallsFallbackOnFailure() local = new LocalClass(); int count = 0; - Mock tunnelMock = new Mock(); + Mock tunnelMock = new Mock("test-user-agent"); tunnelMock.Setup(mock => mock.Run("dummyKey", "", logAbsolute, "start")).Callback(() => { count++; @@ -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(), It.IsAny()), 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()); @@ -214,16 +214,65 @@ public void TestKillsTunnel() options.Add(new KeyValuePair("key", "dummyKey")); local = new LocalClass(); - Mock tunnelMock = new Mock(); + Mock tunnelMock = new Mock("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(), It.IsAny()), 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>(); + options.Add(new KeyValuePair("key", "dummyKey")); + options.Add(new KeyValuePair("proxyHost", "proxy.example.com")); + options.Add(new KeyValuePair("proxyPort", "8080")); + + local = new LocalClass(); + Mock tunnelMock = new Mock("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>(); + options.Add(new KeyValuePair("key", "dummyKey")); + + local = new LocalClass(); + Mock tunnelMock = new Mock("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>(); + options.Add(new KeyValuePair("key", "dummyKey")); + options.Add(new KeyValuePair("proxyHost", "proxy.example.com")); + options.Add(new KeyValuePair("proxyPort", "not-a-number")); + + local = new LocalClass(); + Mock tunnelMock = new Mock("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); diff --git a/BrowserStackLocal/BrowserStackLocal/BrowserStackTunnel.cs b/BrowserStackLocal/BrowserStackLocal/BrowserStackTunnel.cs index 5430622..5752b99 100644 --- a/BrowserStackLocal/BrowserStackLocal/BrowserStackTunnel.cs +++ b/BrowserStackLocal/BrowserStackLocal/BrowserStackTunnel.cs @@ -28,6 +28,10 @@ public class BrowserStackTunnel : IDisposable private string sourceUrl = null; 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; diff --git a/BrowserStackLocal/BrowserStackLocal/Local.cs b/BrowserStackLocal/BrowserStackLocal/Local.cs index c7fa168..7d27d5e 100644 --- a/BrowserStackLocal/BrowserStackLocal/Local.cs +++ b/BrowserStackLocal/BrowserStackLocal/Local.cs @@ -83,7 +83,10 @@ private void addArgs(string key, string value) } else if (key.Equals("proxyPort")) { - int.TryParse(value, out 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); From e5d95f5ba5e4475a0f0e383b7d233a4c5e6ec9f5 Mon Sep 17 00:00:00 2001 From: Yash Saraf Date: Tue, 2 Jun 2026 11:12:35 +0530 Subject: [PATCH 3/4] Add User-Agent header to binary download via WebClient Matches what Node SDK already does on every HTTP request (rails endpoint POST AND CDN binary GET). Until this commit, the WebClient binary download was the only request without a UA header, which made CloudFront/CloudFlare access logs unfilterable by binding+version. Now every outbound HTTP from this binding sends: User-Agent: browserstack-local-csharp/ Same value the HttpClient already sent on the rails endpoint POST. Follow-up to the code review pass on LOC-6563. Co-Authored-By: Claude Opus 4.7 (1M context) --- BrowserStackLocal/BrowserStackLocal/BrowserStackTunnel.cs | 1 + 1 file changed, 1 insertion(+) diff --git a/BrowserStackLocal/BrowserStackLocal/BrowserStackTunnel.cs b/BrowserStackLocal/BrowserStackLocal/BrowserStackTunnel.cs index 5752b99..f825c29 100644 --- a/BrowserStackLocal/BrowserStackLocal/BrowserStackTunnel.cs +++ b/BrowserStackLocal/BrowserStackLocal/BrowserStackTunnel.cs @@ -242,6 +242,7 @@ 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); From d968e3630c194b023f870e2e99261c08547d65f0 Mon Sep 17 00:00:00 2001 From: Yash Saraf Date: Tue, 2 Jun 2026 12:24:41 +0530 Subject: [PATCH 4/4] Drop binding TFM from net7.0 to net6.0 MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Why: the 3.x binding's only net7-specific touchpoint is the `Architecture.Arm64` enum (added in .NET Core 3.0 / netstandard 2.1). Everything else (HttpClient, WebClient, WebProxy, Process, ACL APIs) is available since netstandard 2.0 / net6.0. Targeting net6.0 instead lets net6.0 SDK consumers stay on 3.x without being forced to upgrade their runtime to net7+. .NET 5 and netcoreapp 3.1 still aren't covered — but per the SDK agent's 7-day BigQuery, those TFMs have zero C# SDK traffic, so the practical impact is nil. Net effect: removes the LOC-6563 rollout's hardest constraint (forcing SDK to drop net6.0 from in order to adopt 3.1.0). The 7 hard-blocked enterprise accounts on .NET 6.0.36 (group_id=934837 et al., 14 users, 623 weekly events) stay supported. Also updates test project TFMs and HintPaths from net7.0 to net6.0 so the local build chain matches. Co-Authored-By: Claude Opus 4.7 (1M context) --- .../BrowserStackLocal Unit Tests.csproj | 6 +++--- .../BrowserStackLocal/BrowserStackLocal.csproj | 2 +- .../BrowserStackLocalIntegrationTests.csproj | 6 +++--- 3 files changed, 7 insertions(+), 7 deletions(-) diff --git a/BrowserStackLocal/BrowserStackLocal Unit Tests/BrowserStackLocal Unit Tests.csproj b/BrowserStackLocal/BrowserStackLocal Unit Tests/BrowserStackLocal Unit Tests.csproj index 75629c6..321cd1c 100644 --- a/BrowserStackLocal/BrowserStackLocal Unit Tests/BrowserStackLocal Unit Tests.csproj +++ b/BrowserStackLocal/BrowserStackLocal Unit Tests/BrowserStackLocal Unit Tests.csproj @@ -2,7 +2,7 @@ BrowserStackLocal_Unit_Tests BrowserStackLocal Unit Tests - net7.0 + net6.0 false BrowserStackLocal Unit Tests BrowserStackLocal Unit Tests @@ -18,10 +18,10 @@ - + - ..\BrowserStackLocal\bin\$(Configuration)\net7.0\BrowserStackLocal.dll + ..\BrowserStackLocal\bin\$(Configuration)\net6.0\BrowserStackLocal.dll