diff --git a/.github/workflows/Semgrep.yml b/.github/workflows/Semgrep.yml new file mode 100644 index 0000000..0347afd --- /dev/null +++ b/.github/workflows/Semgrep.yml @@ -0,0 +1,48 @@ +# Name of this GitHub Actions workflow. +name: Semgrep + +on: + # Scan changed files in PRs (diff-aware scanning): + # The branches below must be a subset of the branches above + pull_request: + branches: ["master", "main"] + push: + branches: ["master", "main"] + schedule: + - cron: '0 6 * * *' + + +permissions: + contents: read + +jobs: + semgrep: + # User definable name of this GitHub Actions job. + permissions: + contents: read # for actions/checkout to fetch code + security-events: write # for github/codeql-action/upload-sarif to upload SARIF results + name: semgrep/ci + # If you are self-hosting, change the following `runs-on` value: + runs-on: ubuntu-latest + + container: + # A Docker image with Semgrep installed. Do not change this. + image: returntocorp/semgrep + + # Skip any PR created by dependabot to avoid permission issues: + if: (github.actor != 'dependabot[bot]') + + steps: + # Fetch project source with GitHub Actions Checkout. + - uses: actions/checkout@c85c95e3d7251135ab7dc9ce3241c5835cc595a9 # v3.5.3 + # Run the "semgrep ci" command on the command line of the docker image. + - run: semgrep ci --sarif --output=semgrep.sarif + env: + # Add the rules that Semgrep uses by setting the SEMGREP_RULES environment variable. + SEMGREP_RULES: p/default # more at semgrep.dev/explore + + - name: Upload SARIF file for GitHub Advanced Security Dashboard + uses: github/codeql-action/upload-sarif@6c089f53dd51dc3fc7e599c3cb5356453a52ca9e # v2.20.0 + with: + sarif_file: semgrep.sarif + if: always() \ No newline at end of file diff --git a/.github/workflows/cd.yml b/.github/workflows/cd.yml new file mode 100644 index 0000000..8486139 --- /dev/null +++ b/.github/workflows/cd.yml @@ -0,0 +1,59 @@ +name: .NET package CD + +on: [workflow_dispatch] + +defaults: + run: + working-directory: BrowserStackLocal + +jobs: + build: + runs-on: windows-latest + + steps: + - uses: actions/checkout@v2 + - name: Add msbuild to PATH + uses: microsoft/setup-msbuild@v1.0.2 + - name: Build BrowserStackLocal + run: | + msbuild BrowserStackLocal -t:restore -p:Configuration=Release + msbuild BrowserStackLocal -t:build -p:Configuration=Release + - name: Build Test project + run: | + msbuild BrowserStackLocalIntegrationTests -t:restore -p:Configuration=Release + msbuild BrowserStackLocalIntegrationTests -t:build -p:Configuration=Release + - name: Setup .NET Core + uses: actions/setup-dotnet@v3 + with: + dotnet-version: 7.0.410 + - name: Run Integration Tests + env: + BROWSERSTACK_USERNAME: ${{ secrets.BROWSERSTACK_USERNAME }} + BROWSERSTACK_ACCESS_KEY: ${{ secrets.BROWSERSTACK_ACCESS_KEY }} + run: dotnet test BrowserStackLocalIntegrationTests --no-build -p:Configuration=Release + - name: Pack NuGet Package + run: msbuild BrowserStackLocal -t:pack -p:Configuration=Release + - name: Setup nuget + uses: nuget/setup-nuget@v1 + with: + nuget-api-key: ${{ secrets.NUGET_API_KEY }} + nuget-version: '5.x' + - name: Create PFX certificate + id: createPfx + shell: pwsh + env: + PFX_CONTENT: ${{ secrets.BASE64_PFX_CONTENT }} + run: | + $pfxPath = Join-Path -Path $env:RUNNER_TEMP -ChildPath "cert.pfx"; + $encodedBytes = [System.Convert]::FromBase64String($env:PFX_CONTENT); + Set-Content $pfxPath -Value $encodedBytes -AsByteStream; + Write-Output "::set-output name=PFX_PATH::$pfxPath"; + - name: Sign Nuget Package + run: nuget sign .\BrowserStackLocal\bin\Release\*.nupkg -certificatePath "${{ steps.createPfx.outputs.PFX_PATH }}" -certificatePassword "${{secrets.CERT_PASSWORD}}" -Timestamper "http://timestamp.comodoca.com" + - name: Save artifact + uses: actions/upload-artifact@v4 + with: + name: BrowserStackLocal.nupkg + path: .\BrowserStackLocal\BrowserStackLocal\bin\Release\*.nupkg + - name: Push package to Nuget Repository + run: nuget push **\*.nupkg -Source 'https://api.nuget.org/v3/index.json' -ApiKey ${{secrets.NUGET_API_KEY}} diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml new file mode 100644 index 0000000..c7f79c6 --- /dev/null +++ b/.github/workflows/ci.yml @@ -0,0 +1,40 @@ +name: .NET package CI + +on: [workflow_dispatch] + +defaults: + run: + working-directory: BrowserStackLocal + +jobs: + build: + runs-on: windows-latest + + steps: + - uses: actions/checkout@v2 + - name: Add msbuild to PATH + uses: microsoft/setup-msbuild@v1.0.2 + - name: Build BrowserStackLocal + run: | + msbuild BrowserStackLocal -t:restore -p:Configuration=Release + msbuild BrowserStackLocal -t:build -p:Configuration=Release + - name: Build Test project + run: | + msbuild BrowserStackLocalIntegrationTests -t:restore -p:Configuration=Release + msbuild BrowserStackLocalIntegrationTests -t:build -p:Configuration=Release + - name: Setup .NET Core + uses: actions/setup-dotnet@v3 + with: + dotnet-version: 7.0.410 + - name: Run Integration Tests + env: + BROWSERSTACK_USERNAME: ${{ secrets.BROWSERSTACK_USERNAME }} + BROWSERSTACK_ACCESS_KEY: ${{ secrets.BROWSERSTACK_ACCESS_KEY }} + run: dotnet test BrowserStackLocalIntegrationTests --no-build -p:Configuration=Release + - name: Pack NuGet Package + run: msbuild BrowserStackLocal -t:pack -p:Configuration=Release + - name: Save artifact + uses: actions/upload-artifact@v4 + with: + name: BrowserStackLocal.nupkg + path: .\BrowserStackLocal\BrowserStackLocal\bin\Release\*.nupkg diff --git a/.gitignore b/.gitignore index 269fa7e..8672b80 100644 --- a/.gitignore +++ b/.gitignore @@ -1,6 +1,12 @@ BrowserStackLocal/BrowserStackLocal Unit Tests/bin BrowserStackLocal/BrowserStackLocal Unit Tests/obj +BrowserStackLocal/BrowserStackLocalIntegrationTests/bin +BrowserStackLocal/BrowserStackLocalIntegrationTests/obj BrowserStackLocal/BrowserStackLocal/bin BrowserStackLocal/BrowserStackLocal/obj BrowserStackLocal/packages -BrowserStackLocal/.vs \ No newline at end of file +BrowserStackLocal/.vs +BrowserStackLocalExample/BrowserStackExample/bin +BrowserStackLocalExample/BrowserStackExample/obj +BrowserStackLocalExample/packages +BrowserStackLocalExample/.vs diff --git a/BrowserStackLocal/Backup/BrowserStackLocal.sln b/BrowserStackLocal/Backup/BrowserStackLocal.sln new file mode 100644 index 0000000..ead4ad6 --- /dev/null +++ b/BrowserStackLocal/Backup/BrowserStackLocal.sln @@ -0,0 +1,34 @@ + +Microsoft Visual Studio Solution File, Format Version 12.00 +# Visual Studio 14 +VisualStudioVersion = 14.0.23107.0 +MinimumVisualStudioVersion = 10.0.40219.1 +Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "BrowserStackLocal", "BrowserStackLocal\BrowserStackLocal.csproj", "{F58E1C9A-5910-4DA8-B531-9F4A6B0AE8C8}" +EndProject +Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "BrowserStackLocal Unit Tests", "BrowserStackLocal Unit Tests\BrowserStackLocal Unit Tests.csproj", "{FF1ABB6F-4A2C-48F4-B4DB-47DAD566D2F9}" +EndProject +Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "BrowserStackLocalIntegrationTests", "BrowserStackLocalIntegrationTests\BrowserStackLocalIntegrationTests.csproj", "{01FFB287-C79A-4476-AEDB-EE8DE80EB3B3}" +EndProject +Global + GlobalSection(SolutionConfigurationPlatforms) = preSolution + Debug|Any CPU = Debug|Any CPU + Release|Any CPU = Release|Any CPU + EndGlobalSection + GlobalSection(ProjectConfigurationPlatforms) = postSolution + {F58E1C9A-5910-4DA8-B531-9F4A6B0AE8C8}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {F58E1C9A-5910-4DA8-B531-9F4A6B0AE8C8}.Debug|Any CPU.Build.0 = Debug|Any CPU + {F58E1C9A-5910-4DA8-B531-9F4A6B0AE8C8}.Release|Any CPU.ActiveCfg = Release|Any CPU + {F58E1C9A-5910-4DA8-B531-9F4A6B0AE8C8}.Release|Any CPU.Build.0 = Release|Any CPU + {FF1ABB6F-4A2C-48F4-B4DB-47DAD566D2F9}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {FF1ABB6F-4A2C-48F4-B4DB-47DAD566D2F9}.Debug|Any CPU.Build.0 = Debug|Any CPU + {FF1ABB6F-4A2C-48F4-B4DB-47DAD566D2F9}.Release|Any CPU.ActiveCfg = Release|Any CPU + {FF1ABB6F-4A2C-48F4-B4DB-47DAD566D2F9}.Release|Any CPU.Build.0 = Release|Any CPU + {01FFB287-C79A-4476-AEDB-EE8DE80EB3B3}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {01FFB287-C79A-4476-AEDB-EE8DE80EB3B3}.Debug|Any CPU.Build.0 = Debug|Any CPU + {01FFB287-C79A-4476-AEDB-EE8DE80EB3B3}.Release|Any CPU.ActiveCfg = Release|Any CPU + {01FFB287-C79A-4476-AEDB-EE8DE80EB3B3}.Release|Any CPU.Build.0 = Release|Any CPU + EndGlobalSection + GlobalSection(SolutionProperties) = preSolution + HideSolutionNode = FALSE + EndGlobalSection +EndGlobal diff --git a/BrowserStackLocal/BrowserStackLocal Unit Tests/BrowserStackLocal Unit Tests.csproj b/BrowserStackLocal/BrowserStackLocal Unit Tests/BrowserStackLocal Unit Tests.csproj index 8434c68..75629c6 100644 --- a/BrowserStackLocal/BrowserStackLocal Unit Tests/BrowserStackLocal Unit Tests.csproj +++ b/BrowserStackLocal/BrowserStackLocal Unit Tests/BrowserStackLocal Unit Tests.csproj @@ -1,116 +1,29 @@ - - + - Debug - AnyCPU - {FF1ABB6F-4A2C-48F4-B4DB-47DAD566D2F9} - Library - Properties BrowserStackLocal_Unit_Tests BrowserStackLocal Unit Tests - v4.5 - 512 - {3AC096D0-A1C2-E12C-1390-A8335801FDAB};{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC} - 10.0 - $(MSBuildExtensionsPath32)\Microsoft\VisualStudio\v$(VisualStudioVersion) - $(ProgramFiles)\Common Files\microsoft shared\VSTT\$(VisualStudioVersion)\UITestExtensionPackages - False - UnitTest + net7.0 + false + BrowserStackLocal Unit Tests + BrowserStackLocal Unit Tests + Unit tests for BrowserStack Local binding + BrowserStack + BrowserStack + Copyright © 2016 + false - - true - full - false - bin\Debug\ - DEBUG;TRACE - prompt - 4 - - - pdbonly - true - bin\Release\ - TRACE - prompt - 4 - - - - ..\packages\Moq.4.2.1510.2205\lib\net40\Moq.dll - True - - - ..\packages\NUnitTestAdapter.2.0.0\lib\nunit.core.dll - True - - - ..\packages\NUnitTestAdapter.2.0.0\lib\nunit.core.interfaces.dll - True - - - ..\packages\NUnit.2.6.0.12054\lib\nunit.framework.dll - True - - - ..\packages\NUnitTestAdapter.2.0.0\lib\nunit.util.dll - True - - - ..\packages\NUnitTestAdapter.2.0.0\lib\NUnit.VisualStudio.TestAdapter.dll - True - - - - - - - - - - - - - False - - - - - - - + + + + - - - {f58e1c9a-5910-4da8-b531-9f4a6b0ae8c8} - BrowserStackLocal - - - - - Designer - + + + + ..\BrowserStackLocal\bin\$(Configuration)\net7.0\BrowserStackLocal.dll + - - - - - False - - - False - - - False - - - False - - - - - - - \ No newline at end of file + diff --git a/BrowserStackLocal/BrowserStackLocal Unit Tests/BrowserStackTunnelTests.cs b/BrowserStackLocal/BrowserStackLocal Unit Tests/BrowserStackTunnelTests.cs index dd88bb6..ad13e95 100644 --- a/BrowserStackLocal/BrowserStackLocal Unit Tests/BrowserStackTunnelTests.cs +++ b/BrowserStackLocal/BrowserStackLocal Unit Tests/BrowserStackTunnelTests.cs @@ -13,6 +13,11 @@ namespace BrowserStack_Unit_Tests [TestClass] public class BrowserStackTunnelTests { + static readonly OperatingSystem os = Environment.OSVersion; + 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"; private TunnelClass tunnel; [TestMethod] public void TestInitialState() @@ -25,25 +30,25 @@ 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); - string expectedPath = Path.Combine(Environment.ExpandEnvironmentVariables("%HOMEDRIVE%%HOMEPATH%"), ".browserstack"); - expectedPath = Path.Combine(expectedPath, "BrowserStackLocal.exe"); + tunnel.addBinaryPath(null, ""); + string expectedPath = Path.Combine(homepath, ".browserstack"); + expectedPath = Path.Combine(expectedPath, binaryName); Assert.AreEqual(tunnel.getBinaryAbsolute(), expectedPath); } [TestMethod] public void TestBinaryPathOnEmpty() { tunnel = new TunnelClass(); - tunnel.addBinaryPath(""); - string expectedPath = Path.Combine(Environment.ExpandEnvironmentVariables("%HOMEDRIVE%%HOMEPATH%"), ".browserstack"); - expectedPath = Path.Combine(expectedPath, "BrowserStackLocal.exe"); + tunnel.addBinaryPath("", ""); + string expectedPath = Path.Combine(homepath, ".browserstack"); + expectedPath = Path.Combine(expectedPath, binaryName); Assert.AreEqual(tunnel.getBinaryAbsolute(), expectedPath); } [TestMethod] @@ -51,35 +56,35 @@ public void TestBinaryPathOnFallback() { string expectedPath = "dummyPath"; tunnel = new TunnelClass(); - tunnel.addBinaryPath("dummyPath"); + tunnel.addBinaryPath("dummyPath", ""); Assert.AreEqual(tunnel.getBinaryAbsolute(), expectedPath); tunnel.fallbackPaths(); - expectedPath = Path.Combine(Environment.ExpandEnvironmentVariables("%HOMEDRIVE%%HOMEPATH%"), ".browserstack"); - expectedPath = Path.Combine(expectedPath, "BrowserStackLocal.exe"); + expectedPath = Path.Combine(homepath, ".browserstack"); + expectedPath = Path.Combine(expectedPath, binaryName); Assert.AreEqual(tunnel.getBinaryAbsolute(), expectedPath); tunnel.fallbackPaths(); expectedPath = Directory.GetCurrentDirectory(); - expectedPath = Path.Combine(expectedPath, "BrowserStackLocal.exe"); + expectedPath = Path.Combine(expectedPath, binaryName); Assert.AreEqual(tunnel.getBinaryAbsolute(), expectedPath); tunnel.fallbackPaths(); expectedPath = Path.GetTempPath(); - expectedPath = Path.Combine(expectedPath, "BrowserStackLocal.exe"); + expectedPath = Path.Combine(expectedPath, binaryName); Assert.AreEqual(tunnel.getBinaryAbsolute(), expectedPath); } [TestMethod] public void TestBinaryPathOnNoMoreFallback() { tunnel = new TunnelClass(); - tunnel.addBinaryPath("dummyPath"); + tunnel.addBinaryPath("dummyPath", ""); tunnel.fallbackPaths(); tunnel.fallbackPaths(); tunnel.fallbackPaths(); Assert.Throws(typeof(Exception), new TestDelegate(testFallbackException), - "Binary not found or failed to launch. Make sure that BrowserStackLocal.exe is not already running." + "Binary not found or failed to launch. Make sure that BrowserStackLocal is not already running." ); } [TestMethod] diff --git a/BrowserStackLocal/BrowserStackLocal Unit Tests/LocalTests.cs b/BrowserStackLocal/BrowserStackLocal Unit Tests/LocalTests.cs index d8ce38d..8be3eb6 100644 --- a/BrowserStackLocal/BrowserStackLocal Unit Tests/LocalTests.cs +++ b/BrowserStackLocal/BrowserStackLocal Unit Tests/LocalTests.cs @@ -57,7 +57,7 @@ public void TestWorksWithAccessKeyInOptions() 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."); - tunnelMock.Verify(mock => mock.addBinaryArguments("-logFile \"" + logAbsolute + "\" "), Times.Once()); + tunnelMock.Verify(mock => mock.addBinaryArguments(It.IsRegex("-logFile \"" + logAbsolute + "\" " + "--source \"c-sharp:.*")), Times.Once()); tunnelMock.Verify(mock => mock.Run("dummyKey", "", logAbsolute, "start"), Times.Once()); local.stop(); } @@ -73,7 +73,7 @@ public void TestWorksWithAccessKeyNotInOptions() 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."); - tunnelMock.Verify(mock => mock.addBinaryArguments("-logFile \"" + logAbsolute + "\" "), Times.Once()); + tunnelMock.Verify(mock => mock.addBinaryArguments(It.IsRegex("-logFile \"" + logAbsolute + "\" .*")), Times.Once()); tunnelMock.Verify(mock => mock.Run("envDummyKey", "", logAbsolute, "start"), Times.Once()); local.stop(); } @@ -90,7 +90,7 @@ public void TestWorksForFolderTesting() tunnelMock.Setup(mock => mock.Run("dummyKey", "dummyFolderPath", logAbsolute, "start")); local.setTunnel(tunnelMock.Object); local.start(options); - tunnelMock.Verify(mock => mock.addBinaryArguments("-logFile \"" + logAbsolute + "\" "), Times.Once()); + tunnelMock.Verify(mock => mock.addBinaryArguments(It.IsRegex("-logFile \"" + logAbsolute + "\" .*")), Times.Once()); tunnelMock.Verify(mock => mock.Run("dummyKey", "dummyFolderPath", logAbsolute, "start"), Times.Once()); local.stop(); } @@ -107,8 +107,8 @@ 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.addBinaryArguments("-logFile \"" + logAbsolute + "\" "), 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(); } @@ -129,8 +129,8 @@ 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.addBinaryArguments(It.IsRegex("-vvv.*-force.*-forcelocal.*-forceproxy.*-onlyAutomate")), 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(); } @@ -152,9 +152,9 @@ 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") + It.IsRegex("-localIdentifier.*dummyIdentifier.*dummyHost.*-proxyHost.*dummyHost.*-proxyPort.*dummyPort.*-proxyUser.*dummyUser.*-proxyPass.*dummyPass.*") ), Times.Once()); tunnelMock.Verify(mock => mock.Run("dummyKey", "", logAbsolute, "start"), Times.Once()); local.stop(); @@ -175,9 +175,9 @@ 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") + It.IsRegex("-customBoolKey1.*-customBoolKey2.*-customKey1.*customValue1.*-customKey2.*customValue2.*") ), Times.Once()); tunnelMock.Verify(mock => mock.Run("dummyKey", "", logAbsolute, "start"), Times.Once()); local.stop(); @@ -200,8 +200,8 @@ public void TestCallsFallbackOnFailure() }); local.setTunnel(tunnelMock.Object); local.start(options); - tunnelMock.Verify(mock => mock.addBinaryPath(""), Times.Once); - tunnelMock.Verify(mock => mock.addBinaryArguments("-logFile \"" + logAbsolute + "\" "), 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()); local.stop(); @@ -219,8 +219,8 @@ public void TestKillsTunnel() local.setTunnel(tunnelMock.Object); local.start(options); local.stop(); - tunnelMock.Verify(mock => mock.addBinaryPath(""), Times.Once); - tunnelMock.Verify(mock => mock.addBinaryArguments("-logFile \"" + logAbsolute + "\" "), 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()); } diff --git a/BrowserStackLocal/BrowserStackLocal Unit Tests/Properties/AssemblyInfo.cs b/BrowserStackLocal/BrowserStackLocal Unit Tests/Properties/AssemblyInfo.cs deleted file mode 100644 index deddef4..0000000 --- a/BrowserStackLocal/BrowserStackLocal Unit Tests/Properties/AssemblyInfo.cs +++ /dev/null @@ -1,36 +0,0 @@ -using System.Reflection; -using System.Runtime.CompilerServices; -using System.Runtime.InteropServices; - -// General Information about an assembly is controlled through the following -// set of attributes. Change these attribute values to modify the information -// associated with an assembly. -[assembly: AssemblyTitle("BrowserStackLocal Unit Tests")] -[assembly: AssemblyDescription("")] -[assembly: AssemblyConfiguration("")] -[assembly: AssemblyCompany("")] -[assembly: AssemblyProduct("BrowserStackLocal Unit Tests")] -[assembly: AssemblyCopyright("Copyright © 2016")] -[assembly: AssemblyTrademark("")] -[assembly: AssemblyCulture("")] - -// Setting ComVisible to false makes the types in this assembly not visible -// to COM components. If you need to access a type in this assembly from -// COM, set the ComVisible attribute to true on that type. -[assembly: ComVisible(false)] - -// The following GUID is for the ID of the typelib if this project is exposed to COM -[assembly: Guid("ff1abb6f-4a2c-48f4-b4db-47dad566d2f9")] - -// Version information for an assembly consists of the following four values: -// -// Major Version -// Minor Version -// Build Number -// Revision -// -// You can specify all the values or you can default the Build and Revision Numbers -// by using the '*' as shown below: -// [assembly: AssemblyVersion("1.0.*")] -[assembly: AssemblyVersion("0.1.0")] -[assembly: AssemblyFileVersion("0.1.0")] diff --git a/BrowserStackLocal/BrowserStackLocal Unit Tests/packages.config b/BrowserStackLocal/BrowserStackLocal Unit Tests/packages.config deleted file mode 100644 index 86dc5ed..0000000 --- a/BrowserStackLocal/BrowserStackLocal Unit Tests/packages.config +++ /dev/null @@ -1,6 +0,0 @@ - - - - - - \ No newline at end of file diff --git a/BrowserStackLocal/BrowserStackLocal.sln b/BrowserStackLocal/BrowserStackLocal.sln index 4a8b9b1..64dba3f 100644 --- a/BrowserStackLocal/BrowserStackLocal.sln +++ b/BrowserStackLocal/BrowserStackLocal.sln @@ -1,11 +1,16 @@  Microsoft Visual Studio Solution File, Format Version 12.00 -# Visual Studio 14 -VisualStudioVersion = 14.0.23107.0 +# Visual Studio Version 17 +VisualStudioVersion = 17.1.32414.318 MinimumVisualStudioVersion = 10.0.40219.1 -Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "BrowserStackLocal", "BrowserStackLocal\BrowserStackLocal.csproj", "{F58E1C9A-5910-4DA8-B531-9F4A6B0AE8C8}" +Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "BrowserStackLocal", "BrowserStackLocal\BrowserStackLocal.csproj", "{F58E1C9A-5910-4DA8-B531-9F4A6B0AE8C8}" EndProject -Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "BrowserStackLocal Unit Tests", "BrowserStackLocal Unit Tests\BrowserStackLocal Unit Tests.csproj", "{FF1ABB6F-4A2C-48F4-B4DB-47DAD566D2F9}" +Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "BrowserStackLocal Unit Tests", "BrowserStackLocal Unit Tests\BrowserStackLocal Unit Tests.csproj", "{FF1ABB6F-4A2C-48F4-B4DB-47DAD566D2F9}" +EndProject +Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "BrowserStackLocalIntegrationTests", "BrowserStackLocalIntegrationTests\BrowserStackLocalIntegrationTests.csproj", "{01FFB287-C79A-4476-AEDB-EE8DE80EB3B3}" + ProjectSection(ProjectDependencies) = postProject + {F58E1C9A-5910-4DA8-B531-9F4A6B0AE8C8} = {F58E1C9A-5910-4DA8-B531-9F4A6B0AE8C8} + EndProjectSection EndProject Global GlobalSection(SolutionConfigurationPlatforms) = preSolution @@ -21,8 +26,15 @@ Global {FF1ABB6F-4A2C-48F4-B4DB-47DAD566D2F9}.Debug|Any CPU.Build.0 = Debug|Any CPU {FF1ABB6F-4A2C-48F4-B4DB-47DAD566D2F9}.Release|Any CPU.ActiveCfg = Release|Any CPU {FF1ABB6F-4A2C-48F4-B4DB-47DAD566D2F9}.Release|Any CPU.Build.0 = Release|Any CPU + {01FFB287-C79A-4476-AEDB-EE8DE80EB3B3}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {01FFB287-C79A-4476-AEDB-EE8DE80EB3B3}.Debug|Any CPU.Build.0 = Debug|Any CPU + {01FFB287-C79A-4476-AEDB-EE8DE80EB3B3}.Release|Any CPU.ActiveCfg = Release|Any CPU + {01FFB287-C79A-4476-AEDB-EE8DE80EB3B3}.Release|Any CPU.Build.0 = Release|Any CPU EndGlobalSection GlobalSection(SolutionProperties) = preSolution HideSolutionNode = FALSE EndGlobalSection + GlobalSection(ExtensibilityGlobals) = postSolution + SolutionGuid = {3019DB30-CCF5-4543-B787-FCBBD97B21C8} + EndGlobalSection EndGlobal diff --git a/BrowserStackLocal/BrowserStackLocal/BrowserStackLocal.csproj b/BrowserStackLocal/BrowserStackLocal/BrowserStackLocal.csproj index 944eff6..688943d 100644 --- a/BrowserStackLocal/BrowserStackLocal/BrowserStackLocal.csproj +++ b/BrowserStackLocal/BrowserStackLocal/BrowserStackLocal.csproj @@ -1,55 +1,26 @@ - - - + - Debug - AnyCPU - {F58E1C9A-5910-4DA8-B531-9F4A6B0AE8C8} - Library - Properties BrowserStack BrowserStackLocal - v2.0 - 512 - + net7.0 + false + BrowserStackLocal + BrowserStackLocal + C# Bindings for BrowserStack Local + 3.0.0 + 3.0.0 + 3.0.0 + BrowserStack + BrowserStack + Copyright © 2016 + MIT-LICENSE.txt - - true - full - false - bin\Debug\ - DEBUG;TRACE - prompt - 4 - - - pdbonly - true - bin\Release\ - TRACE - prompt - 4 - AnyCPU - - - - ..\packages\Newtonsoft.Json.8.0.3\lib\net20\Newtonsoft.Json.dll - True - - - - - - - - + - + - - - \ No newline at end of file + diff --git a/BrowserStackLocal/BrowserStackLocal/BrowserStackTunnel.cs b/BrowserStackLocal/BrowserStackLocal/BrowserStackTunnel.cs index 4fee3f3..4134cc2 100644 --- a/BrowserStackLocal/BrowserStackLocal/BrowserStackTunnel.cs +++ b/BrowserStackLocal/BrowserStackLocal/BrowserStackTunnel.cs @@ -6,26 +6,40 @@ using System.Security.AccessControl; using System.Security.Principal; -using Newtonsoft.Json; 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 }; public class BrowserStackTunnel : IDisposable { - static readonly string binaryName = "BrowserStackLocal.exe"; - static readonly string downloadURL = "https://s3.amazonaws.com/browserStack/browserstack-local/BrowserStackLocal.exe"; + static readonly string uname = Util.GetUName(); + static readonly string binaryName = GetBinaryName(); + + 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) : + Environment.ExpandEnvironmentVariables("%HOMEDRIVE%%HOMEPATH%"); public static readonly string[] basePaths = new string[] { - Path.Combine(Environment.ExpandEnvironmentVariables("%HOMEDRIVE%%HOMEPATH%"), ".browserstack"), + Path.Combine(homepath, ".browserstack"), Directory.GetCurrentDirectory(), Path.GetTempPath() }; - int basePathsIndex = -1; + public int basePathsIndex = -1; protected string binaryAbsolute = ""; protected string binaryArguments = ""; - + protected StringBuilder output; public LocalState localState; protected string logFilePath = ""; @@ -33,8 +47,62 @@ public class BrowserStackTunnel : IDisposable Process process = null; - public virtual void addBinaryPath(string binaryAbsolute) + static bool IsDarwin(string osName) + { + return osName.Contains("darwin"); + } + + + static bool IsWindows() { + return Environment.OSVersion.VersionString?.ToLower().Contains("windows") ?? false; + } + + static bool IsLinux(string osName) + { + return osName.Contains("linux"); + } + + static bool IsAlpine() + { + try + { + string[] output = Util.RunShellCommand("grep", "-w \'NAME\' /etc/os-release"); + return output[0]?.ToLower()?.Contains("alpine") ?? false; + } + catch (System.Exception ex) + { + Console.WriteLine("Exception while check isAlpine " + ex); + } + return false; + } + + static string GetBinaryName() + { + if (IsWindows()) return "BrowserStackLocal.exe"; + if (IsDarwin(uname)) return "BrowserStackLocal-darwin-x64"; + + if (IsLinux(uname)) + { + if (Util.Is64BitOS()) + { + return IsAlpine() ? "BrowserStackLocal-alpine" : "BrowserStackLocal-linux-x64"; + } + return "BrowserStackLocal-linux-ia32"; + } + + return "BrowserStackLocal.exe"; + } + + 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); @@ -51,31 +119,104 @@ public virtual void addBinaryArguments(string binaryArguments) 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.exe is not already running."); + throw new Exception("Binary not found or failed to launch. Make sure that BrowserStackLocal is not already running."); } basePathsIndex++; binaryAbsolute = Path.Combine(basePaths[basePathsIndex], binaryName); } + + public void modifyBinaryPermission() + { + if (!IsWindows()) + { + try + { + using (Process proc = Process.Start("/bin/bash", $"-c \"chmod 0755 {this.binaryAbsolute}\"")) + { + proc.WaitForExit(); + } + } + catch + { + throw new Exception("Error in changing permission for file " + this.binaryAbsolute); + } + } + else + { + 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)); + 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 + { + { "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()) { - client.DownloadFile(downloadURL, this.binaryAbsolute); + client.DownloadFile(sourceUrl + "/" + binaryName, this.binaryAbsolute); } if (!File.Exists(binaryAbsolute)) @@ -83,10 +224,7 @@ public void downloadBinary() throw new Exception("Error accessing file " + binaryAbsolute); } - 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)); - dInfo.SetAccessControl(dSecurity); + modifyBinaryPermission(); } public virtual void Run(string accessKey, string folder, string logFilePath, string processType) @@ -114,7 +252,7 @@ public virtual void Run(string accessKey, string folder, string logFilePath, str { File.WriteAllText(logFilePath, string.Empty); } - RunProcess(arguments, processType); + RunProcess(arguments, processType); } private void RunProcess(string arguments, string processType) @@ -138,10 +276,42 @@ private void RunProcess(string arguments, string processType) { if (e.Data != null) { - JObject binaryOutput = JObject.Parse(e.Data); - if(binaryOutput.GetValue("state") != null && !binaryOutput.GetValue("state").ToString().ToLower().Equals("connected")) + JObject binaryOutput = null; + try { - throw new Exception("Eror while executing BrowserStackLocal " + processType + " " + e.Data); + binaryOutput = JObject.Parse(e.Data); + } + catch (Exception) + { + SetTunnelState(LocalState.Error); + throw new Exception($"Error while parsing JSON {e.Data}"); + } + + JToken connectionState = binaryOutput.GetValue("state"); + if (connectionState != null) + { + if (connectionState.ToString().ToLower().Equals("connected")) + { + SetTunnelState(LocalState.Connected); + } + else if (connectionState.ToString().ToLower().Equals("disconnected")) + { + SetTunnelState(LocalState.Disconnected); + throw new Exception("Error while executing BrowserStackLocal " + processType + " " + e.Data); + } + else + { + SetTunnelState(LocalState.Error); + throw new Exception("Error while executing BrowserStackLocal " + processType + " " + e.Data); + } + } + else + { + JToken message = binaryOutput.GetValue("message"); + if (message != null && message.Type == JTokenType.String && message.ToString() == "BrowserStackLocal stopped successfully") + { + SetTunnelState(LocalState.Disconnected); + } } } }); @@ -158,13 +328,19 @@ private void RunProcess(string arguments, string processType) process.BeginOutputReadLine(); process.BeginErrorReadLine(); - TunnelStateChanged(LocalState.Idle, LocalState.Connecting); - AppDomain.CurrentDomain.ProcessExit += new EventHandler((s, e) => Kill()); + SetTunnelState(LocalState.Connecting); + AppDomain.CurrentDomain.ProcessExit += new EventHandler((s, e) => + { + Kill(); + }); process.WaitForExit(); } - private void TunnelStateChanged(LocalState prevState, LocalState state) { } + private void SetTunnelState(LocalState newState) + { + localState = newState; + } public bool IsConnected() { @@ -178,13 +354,13 @@ public void Kill() process.Close(); process.Kill(); process = null; - localState = LocalState.Disconnected; + SetTunnelState(LocalState.Disconnected); } } public void Dispose() { - if(process != null) + if (process != null) { Kill(); } diff --git a/BrowserStackLocal/BrowserStackLocal/Local.cs b/BrowserStackLocal/BrowserStackLocal/Local.cs index 26155bf..6c57251 100644 --- a/BrowserStackLocal/BrowserStackLocal/Local.cs +++ b/BrowserStackLocal/BrowserStackLocal/Local.cs @@ -2,6 +2,7 @@ using System.Collections.Generic; using System.IO; using System.Text.RegularExpressions; +using System.Reflection; namespace BrowserStack { @@ -12,6 +13,11 @@ public class Local private string customLogPath = ""; private string argumentString = ""; private string customBinaryPath = ""; + private string bindingVersion = ""; + private string userAgent = "browserstack-local-csharp"; + private bool isFallbackEnabled = false; + private Exception downloadFailureException = null; + protected BrowserStackTunnel tunnel = null; private static KeyValuePair emptyStringPair = new KeyValuePair(); @@ -63,7 +69,11 @@ private void addArgs(string key, string value) { } - else + else if (key.Equals("source")) + { + + } + else { result = valueCommands.Find(pair => pair.Key == key); if (!result.Equals(emptyStringPair)) @@ -92,11 +102,102 @@ private void addArgs(string key, string value) } } } - + + public static string GetVersionString(string pVersionString) + { + string tVersion = "Unknown"; + string[] aVersion; + + if (string.IsNullOrEmpty(pVersionString)) { return tVersion; } + aVersion = pVersionString.Split('.'); + if (aVersion.Length > 0) { tVersion = aVersion[0]; } + if (aVersion.Length > 1) { tVersion += "." + aVersion[1]; } + if (aVersion.Length > 2) { tVersion += "." + aVersion[2].PadLeft(4, '0'); } + if (aVersion.Length > 3) { tVersion += "." + aVersion[3].PadLeft(4, '0'); } + + return tVersion; + } + public static Assembly GetAssemblyEmbedded(string pAssemblyDisplayName) + { + Assembly tMyAssembly = null; + + if (string.IsNullOrEmpty(pAssemblyDisplayName)) { return tMyAssembly; } + try + { + tMyAssembly = Assembly.Load(pAssemblyDisplayName); + } + catch (Exception ex) + { + string m = ex.Message; + Console.Error.WriteLine(m); + } + return tMyAssembly; + } + public static string GetVersionStringFromAssemblyEmbedded(string pAssemblyDisplayName) + { + string tVersion = "Unknown"; + Assembly tMyAssembly = null; + + tMyAssembly = GetAssemblyEmbedded(pAssemblyDisplayName); + if (tMyAssembly == null) { return tVersion; } + tVersion = GetVersionString(tMyAssembly.GetName().Version.ToString()); + return tVersion; + } + public Local() { - tunnel = new BrowserStackTunnel(); + bindingVersion = GetVersionStringFromAssemblyEmbedded("BrowserStackLocal"); + userAgent = userAgent + "/" + bindingVersion; + tunnel = new BrowserStackTunnel(userAgent); } + + private void DownloadVerifyAndRunBinary() + { + tunnel.basePathsIndex = -1; + tunnel.addBinaryPath(customBinaryPath, accessKey, isFallbackEnabled, downloadFailureException); + try + { + while (true) + { + bool except = false; + try + { + tunnel.Run(accessKey, folder, customLogPath, "start"); + } + catch (System.ComponentModel.Win32Exception) + { + except = true; + } + catch (Exception e) + { + except = true; + Console.WriteLine(e.ToString()); + } + if (except) + { + tunnel.fallbackPaths(); + } + else + { + break; + } + } + } + catch (Exception err) + { + if (!isFallbackEnabled) + { + isFallbackEnabled = true; + downloadFailureException = err; + DownloadVerifyAndRunBinary(); + } + else + { + throw err; + } + } + } + public void start(List> options) { foreach (KeyValuePair pair in options) @@ -111,7 +212,7 @@ public void start(List> options) accessKey = Environment.GetEnvironmentVariable("BROWSERSTACK_ACCESS_KEY"); if (accessKey == null || accessKey.Trim().Length == 0) { - throw new Exception("BROWSERSTACK_ACCESS_KEY cannot be empty. "+ + throw new Exception("BROWSERSTACK_ACCESS_KEY cannot be empty. " + "Specify one by adding key to options or adding to the environment variable BROWSERSTACK_ACCESS_KEY."); } Regex.Replace(this.accessKey, @"\s+", ""); @@ -123,24 +224,10 @@ public void start(List> options) } argumentString += "-logFile \"" + customLogPath + "\" "; - tunnel.addBinaryPath(customBinaryPath); + argumentString += "--source \"c-sharp:" + bindingVersion + "\" "; tunnel.addBinaryArguments(argumentString); - while (true) { - bool except = false; - try { - tunnel.Run(accessKey, folder, customLogPath, "start"); - } catch (Exception) - { - except = true; - } - if (except) - { - tunnel.fallbackPaths(); - } else - { - break; - } - } + + DownloadVerifyAndRunBinary(); } public void stop() diff --git a/BrowserStackLocal/BrowserStackLocal/MIT-LICENSE.txt b/BrowserStackLocal/BrowserStackLocal/MIT-LICENSE.txt new file mode 100644 index 0000000..e070d10 --- /dev/null +++ b/BrowserStackLocal/BrowserStackLocal/MIT-LICENSE.txt @@ -0,0 +1,22 @@ +The MIT License (MIT) + +Copyright (c) 2016 BrowserStack + +Permission is hereby granted, free of charge, to any person obtaining a copy +of this software and associated documentation files (the "Software"), to deal +in the Software without restriction, including without limitation the rights +to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +copies of the Software, and to permit persons to whom the Software is +furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in +all copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN +THE SOFTWARE. + diff --git a/BrowserStackLocal/BrowserStackLocal/Properties/AssemblyInfo.cs b/BrowserStackLocal/BrowserStackLocal/Properties/AssemblyInfo.cs deleted file mode 100644 index e9df8ed..0000000 --- a/BrowserStackLocal/BrowserStackLocal/Properties/AssemblyInfo.cs +++ /dev/null @@ -1,36 +0,0 @@ -using System.Reflection; -using System.Runtime.CompilerServices; -using System.Runtime.InteropServices; - -// General Information about an assembly is controlled through the following -// set of attributes. Change these attribute values to modify the information -// associated with an assembly. -[assembly: AssemblyTitle("BrowserStackLocal")] -[assembly: AssemblyDescription("C# Bindings for BrowserStack Local")] -[assembly: AssemblyConfiguration("")] -[assembly: AssemblyCompany("BrowserStack")] -[assembly: AssemblyProduct("BrowserStackLocal")] -[assembly: AssemblyCopyright("Copyright © 2016")] -[assembly: AssemblyTrademark("")] -[assembly: AssemblyCulture("")] - -// Setting ComVisible to false makes the types in this assembly not visible -// to COM components. If you need to access a type in this assembly from -// COM, set the ComVisible attribute to true on that type. -[assembly: ComVisible(false)] - -// The following GUID is for the ID of the typelib if this project is exposed to COM -[assembly: Guid("f58e1c9a-5910-4da8-b531-9f4a6b0ae8c8")] - -// Version information for an assembly consists of the following four values: -// -// Major Version -// Minor Version -// Build Number -// Revision -// -// You can specify all the values or you can default the Build and Revision Numbers -// by using the '*' as shown below: -// [assembly: AssemblyVersion("1.0.*")] -[assembly: AssemblyVersion("1.3.0.0")] -[assembly: AssemblyFileVersion("1.3.0.0")] diff --git a/BrowserStackLocal/BrowserStackLocal/Util.cs b/BrowserStackLocal/BrowserStackLocal/Util.cs new file mode 100644 index 0000000..56b2ddd --- /dev/null +++ b/BrowserStackLocal/BrowserStackLocal/Util.cs @@ -0,0 +1,52 @@ +using System; +using System.Diagnostics; + +namespace BrowserStack +{ + public class Util { + + // Only Unix Support + public static string[] RunShellCommand(string command, string args = "") + { + ProcessStartInfo psi = new ProcessStartInfo { + RedirectStandardOutput = true, + RedirectStandardError = true, + UseShellExecute = false, + CreateNoWindow = true, + FileName = command, + Arguments = args + }; + + Process process = new Process { StartInfo = psi }; + process.Start(); + string output = process.StandardOutput.ReadToEnd(); + string error = process.StandardError.ReadToEnd(); + process.WaitForExit(); + return new string[]{output, error}; + } + + public static string GetUName() + { + string osName = ""; + try + { + string[] output = RunShellCommand("uname"); + osName = output[0]?.ToLower(); + } + catch (System.Exception) {} + return osName; + } + + // Using for Linux Only + public static bool Is64BitOS() + { + #if NET48_OR_GREATER || NETSTANDARD2_0_OR_GREATER || NETCOREAPP3_0_OR_GREATER + return Environment.Is64BitOperatingSystem; + #endif + // https://learn.microsoft.com/en-gb/dotnet/standard/choosing-core-framework-server?WT.mc_id=dotnet-35129-website + // linux won't be supported in .NET Framework and fallback to 64 bit + return true; + } + } +} + diff --git a/BrowserStackLocal/BrowserStackLocal/packages.config b/BrowserStackLocal/BrowserStackLocal/packages.config deleted file mode 100644 index ee989f3..0000000 --- a/BrowserStackLocal/BrowserStackLocal/packages.config +++ /dev/null @@ -1,4 +0,0 @@ - - - - \ No newline at end of file diff --git a/BrowserStackLocal/BrowserStackLocalIntegrationTests/BrowserStackLocalIntegrationTests.csproj b/BrowserStackLocal/BrowserStackLocalIntegrationTests/BrowserStackLocalIntegrationTests.csproj new file mode 100644 index 0000000..54542d7 --- /dev/null +++ b/BrowserStackLocal/BrowserStackLocalIntegrationTests/BrowserStackLocalIntegrationTests.csproj @@ -0,0 +1,22 @@ + + + + net7.0 + false + + + + + + + + + + + + + ..\BrowserStackLocal\bin\$(Configuration)\net7.0\BrowserStackLocal.dll + + + + diff --git a/BrowserStackLocal/BrowserStackLocalIntegrationTests/IntegrationTests.cs b/BrowserStackLocal/BrowserStackLocalIntegrationTests/IntegrationTests.cs new file mode 100644 index 0000000..c0b1e39 --- /dev/null +++ b/BrowserStackLocal/BrowserStackLocalIntegrationTests/IntegrationTests.cs @@ -0,0 +1,85 @@ +using NUnit.Framework; +using System; +using System.Diagnostics; +using System.Collections.Generic; +using OpenQA.Selenium; +using OpenQA.Selenium.Remote; +using OpenQA.Selenium.Edge; +using BrowserStack; + +namespace BrowserStackLocalIntegrationTests +{ + public class IntegrationTests + { + static readonly OperatingSystem os = Environment.OSVersion; + static readonly string binaryName = os.Platform.ToString() == "Unix" ? "BrowserStackLocal-darwin-x64" : "BrowserStackLocal"; + private static string username = Environment.GetEnvironmentVariable("BROWSERSTACK_USERNAME"); + private static string accesskey = Environment.GetEnvironmentVariable("BROWSERSTACK_ACCESS_KEY"); + + private List> options = new List>() { + new KeyValuePair("key", accesskey), + new KeyValuePair("verbose", "true"), + new KeyValuePair("forcelocal", "true"), + }; + + [Test] + public void TestStartsAndStopsLocalSession() + { + Local local = new Local(); + + void startWithOptions() + { + local.start(options); + } + + Assert.DoesNotThrow(new TestDelegate(startWithOptions)); + Process[] binaryInstances = Process.GetProcessesByName(binaryName); + Assert.AreNotEqual(binaryInstances.Length, 0); + + IWebDriver driver; + EdgeOptions capability = new EdgeOptions(); + capability.AddAdditionalCapability("browserstack.user", username); + capability.AddAdditionalCapability("browserstack.key", accesskey); + capability.AddAdditionalCapability("browserstack.local", true); + capability.AddAdditionalCapability("build", "C Sharp binding Integration Test"); + + driver = new RemoteWebDriver( + new Uri("http://hub.browserstack.com/wd/hub/"), capability + ); + driver.Navigate().GoToUrl("http://bs-local.com:45691/check"); + String status = driver.FindElement(By.TagName("body")).Text; + + Assert.AreEqual(status, "Up and running"); + + driver.Quit(); + local.stop(); + + binaryInstances = Process.GetProcessesByName(binaryName); + Assert.AreEqual(binaryInstances.Length, 0); + } + + [Test] + public void TestBinaryState() + { + Local local = new Local(); + + Assert.AreEqual(local.isRunning(), false); + + local.start(options); + // TODO: Fix bug with method `isRunning` and make assertion pass + // Assert.AreEqual(local.isRunning(), true); + + local.stop(); + Assert.AreEqual(local.isRunning(), false); + } + + [TearDown] + public void TestCleanup() + { + foreach(Process p in Process.GetProcessesByName(binaryName)) + { + p.Kill(); + } + } + } +} diff --git a/BrowserStackLocalExample/BrowserStackExample/BrowserStackExample.csproj b/BrowserStackLocalExample/BrowserStackExample/BrowserStackExample.csproj index c672b24..fc2e2b6 100644 --- a/BrowserStackLocalExample/BrowserStackExample/BrowserStackExample.csproj +++ b/BrowserStackLocalExample/BrowserStackExample/BrowserStackExample.csproj @@ -1,97 +1,37 @@ - - - + + - Debug - AnyCPU {9297DCCC-AE88-4E12-934A-B2BEA4864B49} Exe - Properties + net7.0 BrowserStackExample BrowserStackExample - v4.5 - 512 - publish\ - true - Disk - false - Foreground - 7 - Days - false - false - true - 0 - 1.0.0.%2a - false - false - true + enable + enable + false AnyCPU - true - full - false bin\Debug\ DEBUG;TRACE - prompt 4 AnyCPU - pdbonly - true bin\Release\ TRACE - prompt 4 + + + + + - ..\..\BrowserStackLocal\BrowserStackLocal\bin\Debug\BrowserStackLocal.dll - - - - - - - - - - - - ..\packages\Selenium.WebDriver.2.52.0\lib\net40\WebDriver.dll + ..\..\BrowserStackLocal\BrowserStackLocal\bin\Debug\net7.0\BrowserStackLocal.dll True - - - - - - - - - - False - Microsoft .NET Framework 4.5 %28x86 and x64%29 - true - - - False - .NET Framework 3.5 SP1 - false - - - - - - - - \ No newline at end of file + + diff --git a/BrowserStackLocalExample/BrowserStackExample/Example.cs b/BrowserStackLocalExample/BrowserStackExample/Example.cs index 16bec42..28e410f 100644 --- a/BrowserStackLocalExample/BrowserStackExample/Example.cs +++ b/BrowserStackLocalExample/BrowserStackExample/Example.cs @@ -2,6 +2,7 @@ using BrowserStack; using System.Collections.Generic; using OpenQA.Selenium; +using OpenQA.Selenium.Chrome; using OpenQA.Selenium.Remote; namespace BrowserStackExample @@ -10,36 +11,44 @@ class Example { static void Main(string[] args) { + // Start BrowserStack Local Local local = new Local(); - - List> options = new List>() { + var bsLocalArgs = new List>() + { new KeyValuePair("key", BROWSERSTACK_ACCESS_KEY), - //new KeyValuePair("localIdentifier", "identifier"), - //new KeyValuePair("f", "C:\\Users\\Admin\\Desktop\\"), - new KeyValuePair("onlyAutomate", "true"), - new KeyValuePair("verbose", "true"), new KeyValuePair("forcelocal", "true"), + new KeyValuePair("verbose", "true"), new KeyValuePair("binarypath", "C:\\Users\\Admin\\Desktop\\BrowserStackLocal.exe"), new KeyValuePair("logfile", "C:\\Users\\Admin\\Desktop\\local.log"), }; - local.start(options); - - // Run WebDriver Tests - IWebDriver driver; - DesiredCapabilities capability = DesiredCapabilities.Firefox(); - capability.SetCapability("browserstack.user", BROWSERSTACK_USERNAME); - capability.SetCapability("browserstack.key", BROWSERSTACK_ACCESS_KEY); - //capability.SetCapability("browserstack.localIdentifier", "identifier"); - capability.SetCapability("browserstack.local", true); - capability.SetCapability("build", "build"); + local.start(bsLocalArgs); - driver = new RemoteWebDriver( - new Uri("http://hub.browserstack.com/wd/hub/"), capability + // Define BrowserStack capabilities + var browserstackOptions = new Dictionary + { + { "userName", BROWSERSTACK_USERNAME }, + { "accessKey", BROWSERSTACK_ACCESS_KEY }, + { "local", "true" }, + { "build", "build" } + }; + + // Set up ChromeOptions + ChromeOptions chromeOptions = new ChromeOptions(); + chromeOptions.BrowserVersion = "latest"; + chromeOptions.PlatformName = "Windows 10"; + chromeOptions.AddAdditionalOption("bstack:options", browserstackOptions); + + // Launch Remote WebDriver + IWebDriver driver = new RemoteWebDriver( + new Uri("https://hub.browserstack.com/wd/hub"), + chromeOptions ); + + // Run your test driver.Navigate().GoToUrl("http://www.google.com"); Console.WriteLine(driver.Title); - IWebElement query = driver.FindElement(By.Name("q")); + var query = driver.FindElement(By.Name("q")); query.SendKeys("Browserstack"); query.Submit(); Console.WriteLine(driver.Title); diff --git a/BrowserStackLocalExample/BrowserStackExample/packages.config b/BrowserStackLocalExample/BrowserStackExample/packages.config index d4dee4a..d085f87 100644 --- a/BrowserStackLocalExample/BrowserStackExample/packages.config +++ b/BrowserStackLocalExample/BrowserStackExample/packages.config @@ -1,4 +1,4 @@  - - \ No newline at end of file + + diff --git a/CODEOWNERS b/CODEOWNERS new file mode 100644 index 0000000..ddd85cc --- /dev/null +++ b/CODEOWNERS @@ -0,0 +1 @@ +* @browserstack/local-dev diff --git a/README.md b/README.md index d1e7f82..3c60585 100644 --- a/README.md +++ b/README.md @@ -1,6 +1,6 @@ # browserstack-local-csharp -[![Build Status](https://travis-ci.org/browserstack/browserstack-local-csharp.svg?branch=master)](https://travis-ci.org/browserstack/browserstack-local-csharp) +[![.NET package CI](https://github.com/browserstack/browserstack-local-csharp/actions/workflows/ci.yml/badge.svg)](https://github.com/browserstack/browserstack-local-csharp/actions/workflows/ci.yml) C# bindings for BrowserStack Local. @@ -20,7 +20,7 @@ Local local = new Local(); # replace with your key. You can also set an environment variable - "BROWSERSTACK_ACCESS_KEY". List> bsLocalArgs = new List>() { new KeyValuePair("key", ""), -} +}; # starts the Local instance with the required arguments local.start(bsLocalArgs); @@ -81,6 +81,30 @@ bsLocalArgs.Add(new KeyValuePair("proxyUser", "user")); bsLocalArgs.Add(new KeyValuePair("proxyPass", "password")); ``` +#### Local Proxy +To use local proxy in local testing - + +* localProxyHost: Hostname/IP of proxy, remaining proxy options are ignored if this option is absent +* localProxyPort: Port for the proxy, defaults to 8081 when -localProxyHost is used +* localProxyUser: Username for connecting to proxy (Basic Auth Only) +* localProxyPass: Password for USERNAME, will be ignored if USERNAME is empty or not specified + +``` +bsLocalArgs.Add(new KeyValuePair("localProxyHost", "127.0.0.1")); +bsLocalArgs.Add(new KeyValuePair("localProxyPort", "8000")); +bsLocalArgs.Add(new KeyValuePair("-localProxyUser", "user")); +bsLocalArgs.Add(new KeyValuePair("-localProxyPass", "password")); +``` + +#### PAC (Proxy Auto-Configuration) +To use PAC (Proxy Auto-Configuration) in local testing - + +* pac-file: PAC (Proxy Auto-Configuration) file’s absolute path + +``` +bsLocalArgs.Add(new KeyValuePair("-pac-file", "")); +``` + #### Local Identifier If doing simultaneous multiple local testing connections, set this uniquely for different processes - ``` pFad - Phonifier reborn

Pfad - The Proxy pFad of © 2024 Garber Painting. All rights reserved.

Note: This service is not intended for secure transactions such as banking, social media, email, or purchasing. Use at your own risk. We assume no liability whatsoever for broken pages.


Alternative Proxies:

Alternative Proxy

pFad Proxy

pFad v3 Proxy

pFad v4 Proxy