├── .github └── workflows │ ├── desktop.yml │ ├── dotnet-core.yml │ └── workshop.yml ├── .gitignore ├── .gitpod.yml ├── CONTRIBUTING.md ├── Dotnet ├── Dotnet.csproj ├── SeleniumWeb.cs └── Usings.cs ├── DotnetCore ├── README.md └── Sauce.Demo │ ├── Core.BestPractices.Web │ ├── AssemblyInfo.cs │ ├── Core.BestPractices.Web.csproj │ ├── DesktopWebPageObjects │ │ ├── BaseWebPage.cs │ │ ├── CartComponent.cs │ │ ├── LoginPage.cs │ │ └── ProductsPage.cs │ ├── MobileWebPageObjects │ │ ├── Android │ │ │ └── LoginPage.cs │ │ └── IOS │ │ │ └── LoginPage.cs │ ├── README.md │ └── Tests │ │ ├── AllTestsBase.cs │ │ ├── Desktop │ │ ├── Accesibility.cs │ │ ├── DesktopTests.cs │ │ ├── VisualTests.cs │ │ └── WebTestsBase.cs │ │ ├── Mobile │ │ ├── Android │ │ │ ├── AndroidEmusimTests.cs │ │ │ └── RealDeviceAndroidWebTests.cs │ │ ├── EmusimBaseTest.cs │ │ ├── IOS │ │ │ ├── IOSEmusimTests.cs │ │ │ └── RealDeviceIOSWebTests.cs │ │ └── MobileBaseTest.cs │ │ └── TestConfigData.cs │ ├── Core.Common │ ├── Constants.cs │ ├── Core.Common.csproj │ ├── SauceLabsEndpoint.cs │ ├── TestConfigData.cs │ └── Wait.cs │ ├── Core.Selenium.Examples │ ├── AllTestsBase.cs │ ├── AssemblyInfo.cs │ ├── AxeAccesibility.cs │ ├── Constants.cs │ ├── Core.Selenium.Examples.csproj │ ├── CrossBrowser │ │ ├── End │ │ │ └── CrossBrowserTests.cs │ │ ├── Start │ │ │ └── CrossBrowserTests.cs │ │ └── Wait.cs │ ├── DesktopTests.cs │ ├── Emusim.Web │ │ ├── EmusimBaseTest.cs │ │ ├── End │ │ │ ├── AndroidEmusimTests.cs │ │ │ └── IOSEmusimTests.cs │ │ └── Start │ │ │ └── IOSEmusimTests.cs │ ├── RDC.Web │ │ ├── End │ │ │ └── RealDeviceIOSWebTests.cs │ │ ├── MobileBaseTest.cs │ │ └── Start │ │ │ ├── RealDeviceAndroidWebTests.cs │ │ │ └── RealDeviceIOSWebTests.cs │ ├── Selenium4 │ │ ├── NewFeatures │ │ │ ├── AttributeProperty.cs │ │ │ ├── ChromeNetwork.cs │ │ │ ├── FirefoxAddon.cs │ │ │ ├── FirefoxContext.cs │ │ │ ├── MSEdge.cs │ │ │ ├── NewWindow.cs │ │ │ ├── RelativeLocators.cs │ │ │ ├── Timeouts.cs │ │ │ ├── ViewPageChrome.cs │ │ │ └── ViewPageFirefox.cs │ │ └── Resources │ │ │ └── ninja_saucebot-1.0-an+fx.xpi │ ├── Selenium4Demo.cs │ ├── VisualTests.cs │ └── WORKSHOP.md │ └── Sauce.Demo.sln ├── LICENSE ├── README.md ├── SauceExamples ├── Appium3.MsTest.Scripts.yml ├── Common │ ├── Common.csproj │ ├── Properties │ │ └── AssemblyInfo.cs │ ├── SauceLabs │ │ ├── ApiKeys.cs │ │ ├── App.cs │ │ ├── EmusimAPI.cs │ │ ├── Rdc.cs │ │ ├── SauceJavaScriptExecutor.cs │ │ ├── SauceLabsCapabilities.cs │ │ ├── SauceLabsEndpoint.cs │ │ ├── SauceUser.cs │ │ └── SimpleSauce.cs │ ├── TestData │ │ ├── CrossBrowserData.cs │ │ └── DeviceCombinations.cs │ ├── Wait.cs │ ├── WebDriverFactory.cs │ ├── app.config │ └── packages.config ├── Core.Appium.MsTest.BestPractices │ ├── AssemblyInfo.cs │ ├── Core.Appium.Nunit.BestPractices.csproj │ ├── README.md │ ├── Screens │ │ ├── Android │ │ │ ├── BaseAndroidScreen.cs │ │ │ ├── LoginScreen.cs │ │ │ └── ProductsScreen.cs │ │ └── iOS │ │ │ ├── BaseIosScreen.cs │ │ │ ├── LoginScreen.cs │ │ │ └── ProductsScreen.cs │ └── Tests │ │ ├── AndroidFeatures.cs │ │ ├── AndroidTest.cs │ │ ├── BaseNativeAppTest.cs │ │ ├── IOSFeatures.cs │ │ └── IOSTest.cs ├── Core.Appium.MsTest.Scripts │ ├── Core.Appium.Examples.csproj │ ├── Emusim │ │ └── Browser │ │ │ └── AndroidWebTests.cs │ └── RealDevices │ │ ├── Browser │ │ └── LegacyRdc │ │ │ └── AndroidGetStarted.cs │ │ └── NativeApp │ │ ├── DownloadAssets.cs │ │ └── LegacyRdc │ │ └── iOSExamples.cs ├── DotnetCore │ └── Core.Selenium4.MsTest.Scripts │ │ ├── Core.Selenium4.MsTest.Scripts.csproj │ │ ├── SanityTest.cs │ │ └── SpecFlow │ │ ├── Features │ │ ├── Test1.feature │ │ ├── Test1.feature.cs │ │ ├── Test2.feature │ │ ├── Test2.feature.cs │ │ ├── Test3.feature │ │ └── Test3.feature.cs │ │ ├── Hooks │ │ ├── DriverSetup.cs │ │ └── Hooks.cs │ │ ├── StepDefinitions │ │ └── ParallelSteps.cs │ │ └── specflow1.json ├── DotnetFramework │ └── Appium │ │ ├── Appium4.MsTest.Scripts │ │ ├── Appium4.MsTest.Scripts.csproj │ │ ├── Properties │ │ │ └── AssemblyInfo.cs │ │ ├── RealDevices │ │ │ └── NativeApp │ │ │ │ └── iOSExamples.cs │ │ └── packages.config │ │ ├── Appium4.NUnit.Scripts │ │ ├── Appium4.NUnit.Scripts.csproj │ │ ├── Emusim │ │ │ ├── Browser │ │ │ │ └── W3CEmusim.cs │ │ │ └── NativeApp │ │ │ │ ├── AndroidGetStarted.cs │ │ │ │ ├── GetStartedIos.cs │ │ │ │ └── iOSParallelAndCrossBrowser.cs │ │ ├── Properties │ │ │ └── AssemblyInfo.cs │ │ ├── RealDevices │ │ │ └── NativeApp │ │ │ │ ├── LegacyRdc │ │ │ │ ├── AndroidExamples.cs │ │ │ │ ├── AndroidGetStarted.cs │ │ │ │ ├── iOSExamples.cs │ │ │ │ └── iOSGetStarted.cs │ │ │ │ └── UP │ │ │ │ ├── AndroidAdvanced.cs │ │ │ │ ├── AndroidGetStarted.cs │ │ │ │ └── GetStartedIos.cs │ │ └── packages.config │ │ └── SampleApps │ │ ├── Android.SauceLabs.Mobile.Sample.app.2.2.1.apk │ │ └── iOS.RealDevice.SauceLabs.Mobile.Sample.app.2.2.1.ipa ├── SauceExamples.sln ├── Selenium4DotNetFramework │ ├── Drivers │ │ └── chromedriver.exe │ ├── Properties │ │ └── AssemblyInfo.cs │ ├── Selenium4.MsTest.Scripts.csproj │ ├── Selenium4SauceTests.cs │ └── packages.config ├── SeleniumMsTest │ ├── .runsettings │ ├── ExtendedDebugging.cs │ ├── Onboarding │ │ └── InstantSauceTest.cs │ ├── ParallelTests │ │ ├── DataDriven │ │ │ ├── BaseWebTest.cs │ │ │ ├── DataDrivenCrossBrowserParallelMethods.cs │ │ │ └── MsTestCrossBrowserData.cs │ │ ├── ParallelAtMethodLevel.cs │ │ └── ParallelSeleniumMethods.cs │ ├── Properties │ │ └── AssemblyInfo.cs │ ├── Selenium3.MsTest.Scripts.csproj │ ├── Selenium3WithW3C.cs │ ├── app.config │ └── packages.config ├── SeleniumNunit │ ├── App.config │ ├── BestPractices │ │ ├── BaseTest.cs │ │ ├── CrossBrowserExamples │ │ │ ├── BaseCrossBrowserTest.cs │ │ │ ├── CrossBrowserTests.cs │ │ │ ├── NamespaceSetup.cs │ │ │ └── SauceLabsPage.cs │ │ ├── ParallelTestsWithBestPractices.cs │ │ └── UltimateQAHomePage.cs │ ├── NLog.config │ ├── NLog.xsd │ ├── OnboardingTests │ │ ├── InstantSauceTest.cs │ │ ├── InstantSauceTest2.cs │ │ ├── InstantSauceTest3.cs │ │ └── InstantSauceTest4.cs │ ├── Parallel │ │ ├── ParallelAtMethodsWithSelenium.cs │ │ └── ParallelTestsAtClassLevel.cs │ ├── Properties │ │ └── AssemblyInfo.cs │ ├── SaucePerformance │ │ ├── CustomCapabilitiesTests.cs │ │ └── PerformanceDemo.cs │ ├── Selenium3.Nunit.Scripts.csproj │ ├── SimpleExamples │ │ ├── LoggingWithSauce.cs │ │ ├── RestApiForVdc.cs │ │ ├── ReusableTests.cs │ │ ├── SauceConnectTests.cs │ │ ├── SimpleHeadlessTest.cs │ │ ├── SimpleSauceTest.cs │ │ └── W3CExamplesOnSelenium3.cs │ ├── Visual │ │ ├── README.md │ │ └── VisualTestSimple.cs │ └── packages.config ├── Set-RdcEnvironmentVariables.ps1 └── Web.Tests │ ├── Antipatterns │ ├── Parallelization │ │ ├── BrokenBaseTest.cs │ │ ├── BrokenLoginFeature.cs │ │ ├── LoginFeatureWorksInParallel.cs │ │ └── WorkingBaseTest.cs │ └── PoorTests.cs │ ├── BestPractices │ ├── Elements │ │ └── CartElement.cs │ ├── Pages │ │ ├── BasePage.cs │ │ ├── CheckoutCompletePage.cs │ │ ├── CheckoutInformationPage.cs │ │ ├── CheckoutOverviewPage.cs │ │ ├── Item.cs │ │ ├── OrderConfirmationPage.cs │ │ ├── ProductsPage.cs │ │ ├── SauceDemoLoginPage.cs │ │ └── YourShoppingCartPage.cs │ └── test │ │ ├── BaseTest.cs │ │ ├── LoginFeature.cs │ │ ├── LogoutFeature.cs │ │ ├── PerformanceTesting.cs │ │ ├── ProductsPageFeature.cs │ │ └── ShoppingCartFeature.cs │ ├── CrossBrowserData.cs │ ├── Properties │ └── AssemblyInfo.cs │ ├── Selenium3.Nunit.Framework.csproj │ ├── app.config │ └── packages.config ├── SeleniumExamples ├── MSTestExamples │ ├── AssemblyInfo.cs │ ├── MSTestExamples.csproj │ ├── TestBase.cs │ └── demo │ │ ├── AuthenticationTest.cs │ │ ├── CartTest.cs │ │ ├── CheckoutTests.cs │ │ └── NavigationTests.cs ├── NUnitExamples │ ├── AssemblyInfo.cs │ ├── NUnit.runsettings │ ├── NUnitExamples.csproj │ ├── TestBase.cs │ └── demo │ │ ├── AuthenticationTest.cs │ │ ├── CartTest.cs │ │ ├── CheckoutTests.cs │ │ └── NavigationTests.cs └── SeleniumExamples.sln ├── Test-SauceConnectState.Tests.ps1 ├── Test-SauceConnectState.ps1 ├── _config.yml ├── azure-pipelines.yml ├── env-variables-example.yml ├── index.html ├── setEnvironmentVariables.ps1 └── workshop.yml /.github/workflows/desktop.yml: -------------------------------------------------------------------------------- 1 | name: Web Tests 2 | 3 | on: 4 | push: 5 | branches: [ master ] 6 | pull_request: 7 | branches: [ master ] 8 | 9 | jobs: 10 | build: 11 | 12 | runs-on: ubuntu-latest 13 | 14 | steps: 15 | - uses: actions/checkout@v2 16 | - name: Setup .NET 17 | uses: actions/setup-dotnet@v1 18 | with: 19 | dotnet-version: 5.0.x 20 | - name: Restore dependencies 21 | working-directory: ./dotnetCore/Sauce.Demo/Core.BestPractices.Web 22 | run: dotnet restore 23 | - name: Build 24 | working-directory: ./dotnetCore/Sauce.Demo/Core.BestPractices.Web 25 | run: dotnet build --no-restore 26 | - name: Test 27 | run: dotnet test --no-build --verbosity normal 28 | -------------------------------------------------------------------------------- /.github/workflows/dotnet-core.yml: -------------------------------------------------------------------------------- 1 | name: .NET Core 2 | 3 | on: 4 | push: 5 | branches: [ master ] 6 | pull_request: 7 | branches: [ master ] 8 | 9 | jobs: 10 | build: 11 | 12 | runs-on: windows-latest 13 | 14 | steps: 15 | - uses: actions/checkout@v2 16 | - name: Setup .NET Core 17 | uses: actions/setup-dotnet@v1 18 | with: 19 | dotnet-version: 3.1.101 20 | - name: Install dependencies 21 | run: | 22 | dotnet restore ~/SauceExamples/DotnetCore/Core.Selenium4.MsTest.Scripts/Core.Selenium4.MsTest.Scripts.csproj --verbosity detailed 23 | - name: Build 24 | run: dotnet build --configuration Release --no-restore 25 | - name: Test 26 | run: dotnet test --no-restore --verbosity normal 27 | -------------------------------------------------------------------------------- /.github/workflows/workshop.yml: -------------------------------------------------------------------------------- 1 | name: .NET Workshop 2 | 3 | on: 4 | push: 5 | branches: [ 2_hr_workshop ] 6 | pull_request: 7 | branches: [ master ] 8 | 9 | env: 10 | SCREENER_API_KEY: ${{ secrets.SCREENER_API_KEY }} 11 | SAUCE_USERNAME: ${{ secrets.SAUCE_USERNAME }} 12 | SAUCE_ACCESS_KEY: ${{ secrets.SAUCE_ACCESS_KEY }} 13 | 14 | jobs: 15 | build: 16 | 17 | runs-on: windows-latest 18 | 19 | steps: 20 | - uses: actions/checkout@v2 21 | - name: Setup .NET 22 | uses: actions/setup-dotnet@v1 23 | with: 24 | dotnet-version: 5.0.x 25 | - name: Restore dependencies 26 | #working-directory: './demo-csharp/DotnetCore/Sauce.Demo/' 27 | run: | 28 | cd ./DotnetCore/Sauce.Demo/ 29 | dotnet restore 30 | - name: Build 31 | run: | 32 | cd ./DotnetCore/Sauce.Demo/ 33 | dotnet build --no-restore 34 | - name: Test desktop web 35 | env: 36 | SAUCE_USERNAME: ${{ secrets.SAUCE_USERNAME }} 37 | SAUCE_ACCESS_KEY: ${{ secrets.SAUCE_ACCESS_KEY }} 38 | run: | 39 | cd ./DotnetCore/Sauce.Demo/ 40 | dotnet test ./Core.Selenium.Examples --filter TestCategory=desktop 41 | - name: Test emusim web 42 | run: | 43 | cd ./DotnetCore/Sauce.Demo/ 44 | dotnet test ./Core.Selenium.Examples --filter TestCategory=emusim 45 | - name: Test rdc web 46 | run: | 47 | cd ./DotnetCore/Sauce.Demo/ 48 | dotnet test ./Core.Selenium.Examples --filter TestCategory=rdc 49 | - name: Run all tests 50 | run: | 51 | cd ./DotnetCore/Sauce.Demo/ 52 | dotnet test ./Core.Selenium.Examples 53 | 54 | -------------------------------------------------------------------------------- /.gitpod.yml: -------------------------------------------------------------------------------- 1 | image: gitpod/workspace-dotnet:2023-04-14-07-10-23 2 | 3 | tasks: 4 | - init: | 5 | cd Dotnet 6 | dotnet build 7 | command: dotnet test 8 | -------------------------------------------------------------------------------- /CONTRIBUTING.md: -------------------------------------------------------------------------------- 1 | 2 | -------------------------------------------------------------------------------- /Dotnet/Dotnet.csproj: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | net7.0 5 | enable 6 | enable 7 | 8 | false 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | 23 | -------------------------------------------------------------------------------- /Dotnet/Usings.cs: -------------------------------------------------------------------------------- 1 | global using Microsoft.VisualStudio.TestTools.UnitTesting; -------------------------------------------------------------------------------- /DotnetCore/README.md: -------------------------------------------------------------------------------- 1 | # Dotnet Sauce Examples 2 | 3 | All examples in this repo are using the latest version of .NET. .NET Framework is now obsolete, but those examples are here. 4 | 5 | ## ⚙️Setup 6 | * Download [.NET](https://dotnet.microsoft.com/download) (not .NET Core) 7 | * Open terminal and run dotnet --info to ensure installation. 8 | * `git clone https://github.com/saucelabs-training/demo-csharp.git` 9 | * Download an IDE of your choice. Probably VS for Mac, or VS Code. 10 | 11 | ## TOC 12 | 13 | * [Best practices for web testing](./DotnetCore/Sauce.Demo/Core.BestPractices.Web) 14 | * [Selenium test](./DotnetCore/Sauce.Demo/Core.Selenium.Examples/DesktopTests.cs) 15 | 16 | ## Running tests 17 | 18 | * Run all the tests inside of the `Core.BestPractices.Web.csproj` 19 | 20 | ```bash 21 | cd demo-csharp/DotnetCore/Sauce.Demo/Core.BestPractices.Web/ 22 | # run all tests 23 | dotnet test 24 | # run only visual tests 25 | cd demo-csharp/DotnetCore/Sauce.Demo/Core.Selenium.Examples 26 | dotnet test --filter VisualTestOnChrome 27 | ``` 28 | -------------------------------------------------------------------------------- /DotnetCore/Sauce.Demo/Core.BestPractices.Web/AssemblyInfo.cs: -------------------------------------------------------------------------------- 1 | //Docs on NUnit parallelization: https://github.com/nunit/docs/wiki/Framework-Parallel-Test-Execution 2 | 3 | using NUnit.Framework; 4 | 5 | [assembly: Parallelizable(ParallelScope.Fixtures)] 6 | //Set this value to the Maximum amount of VMs that you have in Sauce Labs 7 | [assembly: LevelOfParallelism(100)] -------------------------------------------------------------------------------- /DotnetCore/Sauce.Demo/Core.BestPractices.Web/Core.BestPractices.Web.csproj: -------------------------------------------------------------------------------- 1 |  2 | 3 | 4 | net5.0 5 | 6 | false 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | 23 | 24 | -------------------------------------------------------------------------------- /DotnetCore/Sauce.Demo/Core.BestPractices.Web/DesktopWebPageObjects/BaseWebPage.cs: -------------------------------------------------------------------------------- 1 | using Core.Common; 2 | using OpenQA.Selenium; 3 | 4 | namespace Core.BestPractices.Web.DesktopWebPageObjects 5 | { 6 | public class BaseWebPage 7 | { 8 | public readonly IWebDriver Driver; 9 | 10 | public BaseWebPage(IWebDriver driver) 11 | { 12 | Driver = driver; 13 | BaseUrl = "https://www.saucedemo.com"; 14 | } 15 | 16 | public IJavaScriptExecutor JavaScriptExecutor => (IJavaScriptExecutor) Driver; 17 | 18 | //public SauceJavaScriptExecutor SauceJsExecutor => 19 | // new SauceJavaScriptExecutor(_driver); 20 | 21 | public Wait Wait => new(Driver); 22 | public string BaseUrl { get; } 23 | 24 | public void TakeSnapshot() 25 | { 26 | JavaScriptExecutor.ExecuteScript("/*@visual.snapshot*/", GetType().Name); 27 | } 28 | } 29 | } -------------------------------------------------------------------------------- /DotnetCore/Sauce.Demo/Core.BestPractices.Web/DesktopWebPageObjects/CartComponent.cs: -------------------------------------------------------------------------------- 1 | using OpenQA.Selenium; 2 | 3 | namespace Core.BestPractices.Web.DesktopWebPageObjects 4 | { 5 | public class CartComponent 6 | { 7 | private readonly IWebDriver _driver; 8 | 9 | public CartComponent(IWebDriver driver) 10 | { 11 | _driver = driver; 12 | } 13 | 14 | private string CartItemCounterText 15 | { 16 | get 17 | { 18 | try 19 | { 20 | return _driver.FindElement(By.XPath("//*[@class='fa-layers-counter shopping_cart_badge']")).Text; 21 | } 22 | catch (NoSuchElementException) 23 | { 24 | return "0"; 25 | } 26 | } 27 | } 28 | 29 | public bool HasItems => int.Parse(CartItemCounterText) > 0; 30 | 31 | public int ItemCount => int.Parse(CartItemCounterText); 32 | 33 | public CartComponent InjectUserWithItems() 34 | { 35 | ((IJavaScriptExecutor) _driver).ExecuteScript( 36 | "window.sessionStorage.setItem('session-username', 'standard-user')"); 37 | ((IJavaScriptExecutor) _driver).ExecuteScript("window.sessionStorage.setItem('cart-contents', '[4,1]')"); 38 | _driver.Navigate().Refresh(); 39 | return this; 40 | } 41 | } 42 | } -------------------------------------------------------------------------------- /DotnetCore/Sauce.Demo/Core.BestPractices.Web/DesktopWebPageObjects/LoginPage.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Collections.Generic; 3 | using Core.Common; 4 | using OpenQA.Selenium; 5 | 6 | namespace Core.BestPractices.Web.DesktopWebPageObjects 7 | { 8 | public class LoginPage : BaseWebPage 9 | { 10 | public LoginPage(IWebDriver driver) : base(driver) 11 | { 12 | } 13 | 14 | private By UsernameLocator { get; } = By.CssSelector("#user-name"); 15 | 16 | public LoginPage Visit() 17 | { 18 | Driver.Navigate().GoToUrl(BaseUrl); 19 | return this; 20 | } 21 | 22 | internal Dictionary GetPerformance() 23 | { 24 | var metrics = new Dictionary 25 | { 26 | ["type"] = "sauce:performance" 27 | }; 28 | return (Dictionary) ((IJavaScriptExecutor) Driver).ExecuteScript("sauce:log", metrics); 29 | } 30 | 31 | public ProductsPage Login(string username) 32 | { 33 | //SauceJsExecutor.LogMessage( 34 | // $"Start login with user=>{username} and pass=>{password}"); 35 | var usernameField = Wait.UntilIsVisible(UsernameLocator); 36 | usernameField.SendKeys(username); 37 | Driver.FindElement(By.CssSelector("#password")).SendKeys("secret_sauce"); 38 | Driver.FindElement(By.CssSelector(".btn_action")).Click(); 39 | //SauceJsExecutor.LogMessage($"{MethodBase.GetCurrentMethod().Name} success"); 40 | return new ProductsPage(Driver); 41 | } 42 | 43 | public Action IsVisible() 44 | { 45 | return IsElementVisible; 46 | } 47 | 48 | private void IsElementVisible() 49 | { 50 | new Wait(Driver).UntilIsVisible(UsernameLocator); 51 | } 52 | } 53 | } -------------------------------------------------------------------------------- /DotnetCore/Sauce.Demo/Core.BestPractices.Web/DesktopWebPageObjects/ProductsPage.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using OpenQA.Selenium; 3 | 4 | namespace Core.BestPractices.Web.DesktopWebPageObjects 5 | { 6 | public class ProductsPage : BaseWebPage 7 | { 8 | private readonly string _pageUrlPart; 9 | 10 | public ProductsPage(IWebDriver driver) : base(driver) 11 | { 12 | _pageUrlPart = "inventory.html"; 13 | } 14 | 15 | public bool IsLoaded => Wait.UntilIsDisplayedById("inventory_filter_container"); 16 | 17 | private IWebElement LogoutLink => Driver.FindElement(By.Id("logout_sidebar_link")); 18 | 19 | private IWebElement HamburgerElement => Driver.FindElement(By.ClassName("bm-burger-button")); 20 | 21 | public int ProductCount => 22 | Driver.FindElements(By.ClassName("inventory_item")).Count; 23 | 24 | public CartComponent Cart => new(Driver); 25 | 26 | public void Logout() 27 | { 28 | HamburgerElement.Click(); 29 | LogoutLink.Click(); 30 | } 31 | 32 | internal ProductsPage Open() 33 | { 34 | Driver.Navigate().GoToUrl($"{BaseUrl}/{_pageUrlPart}"); 35 | return this; 36 | } 37 | 38 | public void AddFirstProductToCart() 39 | { 40 | Wait.UntilIsVisibleByCss("button[class='btn_primary btn_inventory']").Click(); 41 | } 42 | 43 | public Action IsVisible() 44 | { 45 | return IsCartElementVisible; 46 | } 47 | 48 | private void IsCartElementVisible() 49 | { 50 | Wait.UntilIsVisible(By.Id("inventory_container")); 51 | } 52 | } 53 | } -------------------------------------------------------------------------------- /DotnetCore/Sauce.Demo/Core.BestPractices.Web/MobileWebPageObjects/Android/LoginPage.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using Core.BestPractices.Web.DesktopWebPageObjects; 3 | using Core.Common; 4 | using OpenQA.Selenium; 5 | using OpenQA.Selenium.Appium.Android; 6 | 7 | namespace Core.BestPractices.Web.MobileWebPageObjects.Android 8 | { 9 | public class LoginPage 10 | { 11 | public LoginPage(AndroidDriver driver) 12 | { 13 | Driver = driver; 14 | Wait = new Wait(Driver); 15 | } 16 | 17 | private By UsernameLocator { get; } = By.CssSelector("#user-name"); 18 | 19 | public AndroidDriver Driver { get; } 20 | public Wait Wait { get; } 21 | 22 | public LoginPage Visit() 23 | { 24 | Driver.Navigate().GoToUrl(Constants.BaseUrl); 25 | return this; 26 | } 27 | 28 | public ProductsPage Login(string username) 29 | { 30 | //SauceJsExecutor.LogMessage( 31 | // $"Start login with user=>{username} and pass=>{password}"); 32 | var usernameField = Wait.UntilIsVisible(UsernameLocator); 33 | usernameField.SendKeys(username); 34 | Driver.FindElement(By.CssSelector("#password")).SendKeys("secret_sauce"); 35 | Driver.FindElement(By.CssSelector(".btn_action")).Click(); 36 | //SauceJsExecutor.LogMessage($"{MethodBase.GetCurrentMethod().Name} success"); 37 | return new ProductsPage(Driver); 38 | } 39 | 40 | public Action IsVisible() 41 | { 42 | return IsElementVisible; 43 | } 44 | 45 | private void IsElementVisible() 46 | { 47 | new Wait(Driver).UntilIsVisible(UsernameLocator); 48 | } 49 | } 50 | } -------------------------------------------------------------------------------- /DotnetCore/Sauce.Demo/Core.BestPractices.Web/MobileWebPageObjects/IOS/LoginPage.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using Core.BestPractices.Web.DesktopWebPageObjects; 3 | using Core.Common; 4 | using OpenQA.Selenium; 5 | using OpenQA.Selenium.Appium.iOS; 6 | 7 | namespace Core.BestPractices.Web.MobileWebPageObjects.IOS 8 | { 9 | public class LoginPage 10 | { 11 | public LoginPage(IOSDriver driver) 12 | { 13 | Driver = driver; 14 | Wait = new Wait(Driver); 15 | } 16 | 17 | private By UsernameLocator { get; } = By.CssSelector("#user-name"); 18 | 19 | public IOSDriver Driver { get; } 20 | public Wait Wait { get; } 21 | 22 | public LoginPage Visit() 23 | { 24 | Driver.Navigate().GoToUrl(Constants.BaseUrl); 25 | return this; 26 | } 27 | 28 | public ProductsPage Login(string username) 29 | { 30 | //SauceJsExecutor.LogMessage( 31 | // $"Start login with user=>{username} and pass=>{password}"); 32 | var usernameField = Wait.UntilIsVisible(UsernameLocator); 33 | usernameField.SendKeys(username); 34 | Driver.FindElement(By.CssSelector("#password")).SendKeys("secret_sauce"); 35 | Driver.FindElement(By.CssSelector(".btn_action")).Click(); 36 | //SauceJsExecutor.LogMessage($"{MethodBase.GetCurrentMethod().Name} success"); 37 | return new ProductsPage(Driver); 38 | } 39 | 40 | public Action IsVisible() 41 | { 42 | return IsElementVisible; 43 | } 44 | 45 | private void IsElementVisible() 46 | { 47 | new Wait(Driver).UntilIsVisible(UsernameLocator); 48 | } 49 | } 50 | } -------------------------------------------------------------------------------- /DotnetCore/Sauce.Demo/Core.BestPractices.Web/README.md: -------------------------------------------------------------------------------- 1 | # Best Practices for Web Testing 2 | 3 | A good testing strategy doesn't only focus on web testing. Instead, it tackles risks at all levels of the system. 4 | 5 | In this repository, you will find a cohesive Greybox (we have some insight into the app code) testing strategy using: 6 | 7 | ✅ Browser testing on desktop 8 | 9 | ✅ Browser testing on mobile 10 | 11 | ✅ Visual testing 12 | 13 | ✅ Accessibility testing 14 | 15 | ✅ CICD pipeline executed on push and PR 16 | 17 | ✅ Sauce Labs cloud infrastructure 18 | 19 | ## Test Strategy 20 | 21 | | Expected Behavior | Tested? | Test Type | Rationale | Tech | 22 | |---|---|---|---|---| 23 | | Every web page of the app looks correct on desktop | ✅ | Visual test | A visual test efficiently validates app rendering | Selenium, Screener.io | 24 | | Every web page of the app looks correct on mobile | ✅ | Visual test | A visual test efficiently validates app rendering | Selenium, Screener.io | 25 | | A user can successfully check out on desktop | ✅ | Functional web test | Functional testing of the most critical functionality is important | Selenium | 26 | | A user can successfully check out on mobile | ✅ | Functional mobile test | Although redundant to a functional web test, it's relatively easy to test this on a mobile device as well | Appium | 27 | | App is accessibility friendly | ✅ | Selenium web test | Accessibility in applications is becoming extremely important | Selenium, Axe 28 | | Front-end performance is at least an A | 🙅‍♂️ | Front-end performance test | Front-end perf is an important aspect of any digital quality effort | Selenium, Sauce Labs | 29 | | Test code runs on every commit in under 5 minutes | 🙅‍♂️ | CICD | Slow feedback makes it hard to iterate | Github Actions | 30 | | App is secure | 🙅‍♂️ | Not covered here, but something to consider for testing strategy | | 31 | 32 | 33 | ## ⚙️Setup 34 | 35 | * [Download .NET 5](https://dotnet.microsoft.com/download) 36 | * Open terminal and run `dotnet --info` to ensure installation. 37 | * `git clone https://github.com/saucelabs-training/demo-csharp.git` 38 | * Download an IDE of your choice. Probably VS for Mac, or VS Code. 39 | 40 | ## Running all tests 41 | 42 | * Run all the tests inside of the `Core.BestPractices.Web.csproj` 43 | 44 | ```bash 45 | cd demo-csharp/DotnetCore/Sauce.Demo/Core.BestPractices.Web/ 46 | dotnet test 47 | ``` 48 | 49 | ### Run accessibility test 50 | 51 | ```bash 52 | cd demo-csharp/DotnetCore/Sauce.Demo/Core.BestPractices.Web/ 53 | dotnet test --filter Name=AccessibilityTest --verbosity normal 54 | ``` 55 | 56 | ### Run visual tests 57 | 58 | ``` 59 | cd demo-csharp/DotnetCore/Sauce.Demo/Core.BestPractices.Web/ 60 | dotnet test --filter VisualTests --verbosity normal 61 | ``` 62 | 63 | 64 | -------------------------------------------------------------------------------- /DotnetCore/Sauce.Demo/Core.BestPractices.Web/Tests/AllTestsBase.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Collections.Generic; 3 | using Core.Common; 4 | using NUnit.Framework; 5 | using NUnit.Framework.Interfaces; 6 | using OpenQA.Selenium; 7 | using OpenQA.Selenium.Appium; 8 | using OpenQA.Selenium.Appium.Android; 9 | using OpenQA.Selenium.Appium.iOS; 10 | using OpenQA.Selenium.Remote; 11 | 12 | namespace Core.BestPractices.Web.Tests 13 | { 14 | [TestFixture] 15 | public class AllTestsBase 16 | { 17 | public IWebDriver Driver { get; set; } 18 | 19 | public string SauceUserName => 20 | Environment.GetEnvironmentVariable("SAUCE_USERNAME"); 21 | 22 | public string SauceAccessKey => 23 | Environment.GetEnvironmentVariable("SAUCE_ACCESS_KEY"); 24 | 25 | public Dictionary SauceOptions; 26 | 27 | public string ScreenerApiKey => 28 | Environment.GetEnvironmentVariable("SCREENER_API_KEY"); 29 | 30 | public IJavaScriptExecutor JsExecutor => (IJavaScriptExecutor) Driver; 31 | 32 | public IWebDriver GetVisualDriver(ICapabilities capabilities) 33 | { 34 | //TimeSpan.FromSeconds(120) = needed so that there isn't a 'The HTTP request to the remote WebDriver server for URL' error 35 | var driver = new RemoteWebDriver(new Uri("https://hub.screener.io:443/wd/hub"), capabilities, 36 | TimeSpan.FromSeconds(120)); 37 | //Needed so that Screener 'end' command doesn't timeout 38 | driver.Manage().Timeouts().AsynchronousJavaScript = TimeSpan.FromSeconds(60); 39 | return driver; 40 | } 41 | 42 | public IWebDriver GetDesktopDriver(ICapabilities browserOptions) 43 | { 44 | return new RemoteWebDriver(new Uri("https://ondemand.saucelabs.com/wd/hub"), browserOptions); 45 | } 46 | 47 | public AndroidDriver GetAndroidDriver(AppiumOptions appiumOptions) 48 | { 49 | return new(new SauceLabsEndpoint().EmusimUri(SauceUserName, SauceAccessKey), appiumOptions, TimeSpan 50 | .FromSeconds(240)); 51 | } 52 | 53 | public IOSDriver GetIOSDriver(AppiumOptions appiumOptions) 54 | { 55 | return new(new SauceLabsEndpoint().EmusimUri(SauceUserName, SauceAccessKey), appiumOptions, TimeSpan 56 | .FromSeconds(240)); 57 | } 58 | 59 | public void ExecuteSauceCleanupSteps(IWebDriver driver) 60 | { 61 | var isPassed = TestContext.CurrentContext.Result.Outcome.Status 62 | == TestStatus.Passed; 63 | var script = "sauce:job-result=" + (isPassed ? "passed" : "failed"); 64 | ((IJavaScriptExecutor) driver).ExecuteScript(script); 65 | } 66 | } 67 | } -------------------------------------------------------------------------------- /DotnetCore/Sauce.Demo/Core.BestPractices.Web/Tests/Desktop/Accesibility.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Collections.Generic; 3 | using Core.BestPractices.Web.DesktopWebPageObjects; 4 | using NUnit.Framework; 5 | using OpenQA.Selenium; 6 | using OpenQA.Selenium.Chrome; 7 | using OpenQA.Selenium.Remote; 8 | using Selenium.Axe; 9 | 10 | namespace Core.BestPractices.Web.Tests.Desktop 11 | { 12 | [TestFixture] 13 | public class Accesibility : WebTestsBase 14 | { 15 | [SetUp] 16 | public void SetupDesktopTests() 17 | { 18 | var chromeOptions = new ChromeOptions 19 | { 20 | BrowserVersion = "latest", 21 | PlatformName = "Windows 10", 22 | UseSpecCompliantProtocol = true 23 | }; 24 | chromeOptions.AddAdditionalCapability("sauce:options", SauceOptions, true); 25 | Driver = GetDesktopDriver(chromeOptions.ToCapabilities()); 26 | } 27 | [Test] 28 | public void AccessibilityTest() 29 | { 30 | Driver.Navigate().GoToUrl("https://www.ultimateqa.com"); 31 | var results = Driver.Analyze(); 32 | Assert.IsNull(results.Error); 33 | } 34 | } 35 | } 36 | -------------------------------------------------------------------------------- /DotnetCore/Sauce.Demo/Core.BestPractices.Web/Tests/Desktop/WebTestsBase.cs: -------------------------------------------------------------------------------- 1 | using System.Collections.Generic; 2 | using Core.Common; 3 | using NUnit.Framework; 4 | 5 | namespace Core.BestPractices.Web.Tests.Desktop 6 | { 7 | [TestFixture] 8 | public class WebTestsBase : AllTestsBase 9 | { 10 | [TearDown] 11 | public void CleanUpAfterEveryTestMethod() 12 | { 13 | if (Driver == null) 14 | return; 15 | ExecuteSauceCleanupSteps(Driver); 16 | Driver.Quit(); 17 | } 18 | 19 | [SetUp] 20 | public void Setup() 21 | { 22 | SauceOptions = new Dictionary 23 | { 24 | ["username"] = SauceUserName, 25 | ["accessKey"] = SauceAccessKey, 26 | ["name"] = TestContext.CurrentContext.Test.Name, 27 | ["build"] = Constants.BuildId 28 | }; 29 | } 30 | } 31 | } -------------------------------------------------------------------------------- /DotnetCore/Sauce.Demo/Core.BestPractices.Web/Tests/Mobile/Android/AndroidEmusimTests.cs: -------------------------------------------------------------------------------- 1 | using Core.BestPractices.Web.MobileWebPageObjects.Android; 2 | using Core.Common; 3 | using FluentAssertions; 4 | using NUnit.Framework; 5 | using OpenQA.Selenium.Appium; 6 | using OpenQA.Selenium.Appium.Android; 7 | using OpenQA.Selenium.Appium.Enums; 8 | 9 | namespace Core.BestPractices.Web.Tests.Mobile.Android 10 | { 11 | [TestFixture] 12 | [TestFixtureSource(typeof(TestConfigData), nameof(TestConfigData.PopularAndroidSimulators))] 13 | public class AndroidEmusimTests : EmusimBaseTest 14 | { 15 | [SetUp] 16 | public void Setup() 17 | { 18 | var appiumOptions = new AppiumOptions(); 19 | appiumOptions.AddAdditionalCapability(MobileCapabilityType.DeviceName, DeviceName); 20 | appiumOptions.AddAdditionalCapability(MobileCapabilityType.PlatformName, "Android"); 21 | appiumOptions.AddAdditionalCapability(MobileCapabilityType.PlatformVersion, PlatformVersion); 22 | appiumOptions.AddAdditionalCapability(MobileCapabilityType.BrowserName, "Chrome"); 23 | appiumOptions.AddAdditionalCapability(MobileCapabilityType.AppiumVersion, "1.20.2"); 24 | appiumOptions.AddAdditionalCapability("name", TestContext.CurrentContext.Test.Name); 25 | appiumOptions.AddAdditionalCapability("build", Constants.BuildId); 26 | 27 | _driver = GetAndroidDriver(appiumOptions); 28 | } 29 | 30 | [TearDown] 31 | public void EmusimTeardown() 32 | { 33 | if (_driver == null) return; 34 | 35 | ExecuteSauceCleanupSteps(_driver); 36 | _driver.Quit(); 37 | } 38 | 39 | private AndroidDriver _driver; 40 | 41 | public AndroidEmusimTests(string deviceName, string platformVersion) : base(deviceName, platformVersion) 42 | { 43 | } 44 | 45 | [Test] 46 | public void LoginPageOpens() 47 | { 48 | var loginPage = new LoginPage(_driver); 49 | loginPage.Visit(); 50 | loginPage.IsVisible().Should().NotThrow(); 51 | } 52 | } 53 | } -------------------------------------------------------------------------------- /DotnetCore/Sauce.Demo/Core.BestPractices.Web/Tests/Mobile/Android/RealDeviceAndroidWebTests.cs: -------------------------------------------------------------------------------- 1 | using Core.BestPractices.Web.DesktopWebPageObjects; 2 | using FluentAssertions; 3 | using NUnit.Framework; 4 | using OpenQA.Selenium.Appium.Android; 5 | 6 | namespace Core.BestPractices.Web.Tests.Mobile.Android 7 | { 8 | [TestFixtureSource(typeof(TestConfigData), nameof(TestConfigData.MostPopularAndroidDevices))] 9 | [Parallelizable] 10 | public class RealDeviceAndroidWebTests : MobileBaseTest 11 | { 12 | [SetUp] 13 | public void AndroidSetup() 14 | { 15 | Driver = GetAndroidDriver(MobileOptions); 16 | } 17 | 18 | [TearDown] 19 | public void Teardown() 20 | { 21 | if (Driver == null) return; 22 | 23 | ExecuteSauceCleanupSteps(Driver); 24 | Driver.Quit(); 25 | } 26 | 27 | public new AndroidDriver Driver { get; set; } 28 | 29 | public RealDeviceAndroidWebTests(string deviceName, string platform, string browser) : 30 | base(deviceName, platform, browser) 31 | { 32 | } 33 | 34 | [Test] 35 | [Retry(1)] 36 | public void ShouldOpenHomePage() 37 | { 38 | var loginPage = new LoginPage(Driver); 39 | loginPage.Visit(); 40 | loginPage.IsVisible().Should().NotThrow(); 41 | } 42 | [Test] 43 | [Retry(1)] 44 | public void LoginWorks() 45 | { 46 | var loginPage = new LoginPage(Driver); 47 | loginPage.Visit(); 48 | loginPage.Login("standard_user"); 49 | new ProductsPage(Driver).IsVisible().Should().NotThrow(); 50 | } 51 | } 52 | } -------------------------------------------------------------------------------- /DotnetCore/Sauce.Demo/Core.BestPractices.Web/Tests/Mobile/EmusimBaseTest.cs: -------------------------------------------------------------------------------- 1 | namespace Core.BestPractices.Web.Tests.Mobile 2 | { 3 | public class EmusimBaseTest : AllTestsBase 4 | { 5 | public string DeviceName; 6 | public string PlatformVersion; 7 | 8 | public EmusimBaseTest(string deviceName, string platformVersion) 9 | { 10 | DeviceName = deviceName; 11 | PlatformVersion = platformVersion; 12 | } 13 | } 14 | } -------------------------------------------------------------------------------- /DotnetCore/Sauce.Demo/Core.BestPractices.Web/Tests/Mobile/IOS/IOSEmusimTests.cs: -------------------------------------------------------------------------------- 1 | using Core.BestPractices.Web.MobileWebPageObjects.IOS; 2 | using Core.Common; 3 | using FluentAssertions; 4 | using NUnit.Framework; 5 | using OpenQA.Selenium.Appium; 6 | using OpenQA.Selenium.Appium.Enums; 7 | using OpenQA.Selenium.Appium.iOS; 8 | 9 | namespace Core.BestPractices.Web.Tests.Mobile.IOS 10 | { 11 | [TestFixture] 12 | [TestFixtureSource(typeof(TestConfigData), nameof(TestConfigData.PopularIOSSimulators))] 13 | public class IOSEmusimTests : EmusimBaseTest 14 | { 15 | [SetUp] 16 | public void Setup() 17 | { 18 | var appiumOptions = new AppiumOptions(); 19 | appiumOptions.AddAdditionalCapability(MobileCapabilityType.DeviceName, DeviceName); 20 | appiumOptions.AddAdditionalCapability(MobileCapabilityType.PlatformName, "iOS"); 21 | appiumOptions.AddAdditionalCapability(MobileCapabilityType.PlatformVersion, PlatformVersion); 22 | appiumOptions.AddAdditionalCapability(MobileCapabilityType.BrowserName, "Safari"); 23 | appiumOptions.AddAdditionalCapability("name", TestContext.CurrentContext.Test.Name); 24 | appiumOptions.AddAdditionalCapability("build", Constants.BuildId); 25 | 26 | _driver = GetIOSDriver(appiumOptions); 27 | } 28 | 29 | [TearDown] 30 | public void EmusimTeardown() 31 | { 32 | if (_driver == null) return; 33 | 34 | ExecuteSauceCleanupSteps(_driver); 35 | _driver.Quit(); 36 | } 37 | 38 | private IOSDriver _driver; 39 | 40 | public IOSEmusimTests(string deviceName, string platformVersion) : base(deviceName, platformVersion) 41 | { 42 | } 43 | 44 | [Test] 45 | public void LoginPageOpens() 46 | { 47 | var loginPage = new LoginPage(_driver); 48 | loginPage.Visit(); 49 | loginPage.IsVisible().Should().NotThrow(); 50 | } 51 | } 52 | } -------------------------------------------------------------------------------- /DotnetCore/Sauce.Demo/Core.BestPractices.Web/Tests/Mobile/IOS/RealDeviceIOSWebTests.cs: -------------------------------------------------------------------------------- 1 | using Core.BestPractices.Web.DesktopWebPageObjects; 2 | using FluentAssertions; 3 | using NUnit.Framework; 4 | using OpenQA.Selenium.Appium.iOS; 5 | 6 | namespace Core.BestPractices.Web.Tests.Mobile.IOS 7 | { 8 | [TestFixtureSource(typeof(TestConfigData), nameof(TestConfigData.MostPopularIOSDevices))] 9 | [Parallelizable] 10 | public class RealDeviceIOSWebTests : MobileBaseTest 11 | { 12 | [SetUp] 13 | public void IOSSetup() 14 | { 15 | Driver = GetIOSDriver(MobileOptions); 16 | } 17 | 18 | [TearDown] 19 | public void Teardown() 20 | { 21 | if (Driver == null) return; 22 | 23 | ExecuteSauceCleanupSteps(Driver); 24 | Driver.Quit(); 25 | } 26 | 27 | public new IOSDriver Driver { get; set; } 28 | 29 | public RealDeviceIOSWebTests(string deviceName, string platform, string browser) : 30 | base(deviceName, platform, browser) 31 | { 32 | } 33 | 34 | [Test] 35 | public void ShouldOpenHomePage() 36 | { 37 | var loginPage = new LoginPage(Driver); 38 | loginPage.Visit(); 39 | loginPage.IsVisible().Should().NotThrow(); 40 | } 41 | [Test] 42 | [Retry(1)] 43 | public void LoginWorks() 44 | { 45 | var loginPage = new LoginPage(Driver); 46 | loginPage.Visit(); 47 | loginPage.Login("standard_user"); 48 | new ProductsPage(Driver).IsVisible().Should().NotThrow(); 49 | } 50 | } 51 | } -------------------------------------------------------------------------------- /DotnetCore/Sauce.Demo/Core.BestPractices.Web/Tests/Mobile/MobileBaseTest.cs: -------------------------------------------------------------------------------- 1 | using Core.Common; 2 | using NUnit.Framework; 3 | using OpenQA.Selenium.Appium; 4 | using OpenQA.Selenium.Appium.Enums; 5 | 6 | namespace Core.BestPractices.Web.Tests.Mobile 7 | { 8 | public class MobileBaseTest : AllTestsBase 9 | { 10 | [SetUp] 11 | public void MobileBaseSetup() 12 | { 13 | MobileOptions = new AppiumOptions(); 14 | MobileOptions.AddAdditionalCapability(MobileCapabilityType.DeviceName, DeviceName); 15 | MobileOptions.AddAdditionalCapability(MobileCapabilityType.PlatformName, Platform); 16 | MobileOptions.AddAdditionalCapability(MobileCapabilityType.BrowserName, Browser); 17 | MobileOptions.AddAdditionalCapability("name", TestContext.CurrentContext.Test.Name); 18 | MobileOptions.AddAdditionalCapability("newCommandTimeout", 90); 19 | MobileOptions.AddAdditionalCapability("build", Constants.BuildId); 20 | } 21 | 22 | public readonly string DeviceName; 23 | public readonly string Platform; 24 | public readonly string Browser; 25 | 26 | public MobileBaseTest(string deviceName, string platform, string browser) 27 | { 28 | DeviceName = deviceName; 29 | Platform = platform; 30 | Browser = browser; 31 | } 32 | 33 | public AppiumOptions MobileOptions { get; set; } 34 | } 35 | } -------------------------------------------------------------------------------- /DotnetCore/Sauce.Demo/Core.Common/Constants.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | 3 | namespace Core.Common 4 | { 5 | public static class Constants 6 | { 7 | public static string BaseUrl => "https://www.saucedemo.com"; 8 | 9 | public static string BuildId { get; set; } = DateTime.Now.ToString("F"); 10 | } 11 | } -------------------------------------------------------------------------------- /DotnetCore/Sauce.Demo/Core.Common/Core.Common.csproj: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | net5.0 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | -------------------------------------------------------------------------------- /DotnetCore/Sauce.Demo/Core.Common/SauceLabsEndpoint.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | 3 | namespace Core.Common 4 | { 5 | public class SauceLabsEndpoint 6 | { 7 | public static string SauceUsWestDomain = "@ondemand.us-west-1.saucelabs.com/wd/hub"; 8 | public string SauceHubUrl => "https://ondemand.saucelabs.com/wd/hub"; 9 | public Uri SauceHubUri => new(SauceHubUrl); 10 | public static Uri UsWestHubUri => new($"https://{SauceUsWestDomain}"); 11 | 12 | public static string HeadlessSeleniumUrl => "https://ondemand.us-east-1.saucelabs.com/wd/hub"; 13 | 14 | public static string HeadlessRestApiUrl => "https://us-east-1.saucelabs.com/rest/v1"; 15 | 16 | public Uri EmusimUri(string sauceUser, string sauceKey) 17 | { 18 | return new($"https://{sauceUser}:{sauceKey}{SauceUsWestDomain}"); 19 | } 20 | } 21 | } -------------------------------------------------------------------------------- /DotnetCore/Sauce.Demo/Core.Common/Wait.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using OpenQA.Selenium; 3 | using OpenQA.Selenium.Support.UI; 4 | using ExpectedConditions = SeleniumExtras.WaitHelpers.ExpectedConditions; 5 | 6 | namespace Core.Common 7 | { 8 | public class Wait 9 | { 10 | private readonly By _locator; 11 | 12 | public Wait(IWebDriver driver) 13 | { 14 | _wait = new WebDriverWait(driver, TimeSpan.FromSeconds(15)); 15 | } 16 | 17 | public Wait(IWebDriver driver, By locator) 18 | { 19 | _locator = locator; 20 | _wait = new WebDriverWait(driver, TimeSpan.FromSeconds(15)); 21 | } 22 | 23 | public Wait(IWebDriver driver, int timeoutInSeconds) 24 | { 25 | _wait = new WebDriverWait(driver, TimeSpan.FromSeconds(timeoutInSeconds)); 26 | } 27 | 28 | private WebDriverWait _wait { get; } 29 | 30 | public IWebElement UntilIsVisible(By locator) 31 | { 32 | return _wait.Until(ExpectedConditions.ElementIsVisible(locator)); 33 | } 34 | 35 | public IWebElement UntilIsVisibleByClass(string className) 36 | { 37 | return UntilIsVisible(By.ClassName(className)); 38 | } 39 | 40 | public bool IsVisible() 41 | { 42 | return _wait.Until(ExpectedConditions.ElementIsVisible(_locator)).Displayed; 43 | } 44 | 45 | public IWebElement UntilIsVisibleById(string id) 46 | { 47 | return UntilIsVisible(By.Id(id)); 48 | } 49 | 50 | //This type of method is really useful for negative assertions 51 | //To wait until something isn't displayed and then assert on it 52 | public bool UntilIsDisplayedById(string id) 53 | { 54 | bool isDisplayed; 55 | try 56 | { 57 | isDisplayed = UntilIsVisible(By.Id(id)).Displayed; 58 | } 59 | catch (WebDriverTimeoutException) 60 | { 61 | return false; 62 | } 63 | 64 | return isDisplayed; 65 | } 66 | 67 | public IWebElement UntilIsVisibleByCss(string css) 68 | { 69 | return UntilIsVisible(By.CssSelector(css)); 70 | } 71 | } 72 | } -------------------------------------------------------------------------------- /DotnetCore/Sauce.Demo/Core.Selenium.Examples/AllTestsBase.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Collections.Generic; 3 | using Core.Common; 4 | using NUnit.Framework; 5 | using NUnit.Framework.Interfaces; 6 | using OpenQA.Selenium; 7 | using OpenQA.Selenium.Appium; 8 | using OpenQA.Selenium.Appium.Android; 9 | using OpenQA.Selenium.Appium.iOS; 10 | using OpenQA.Selenium.Remote; 11 | 12 | namespace Core.Selenium.Examples 13 | { 14 | [TestFixture] 15 | public class AllTestsBase 16 | { 17 | public IWebDriver Driver { get; set; } 18 | 19 | public string SauceUserName => 20 | Environment.GetEnvironmentVariable("SAUCE_USERNAME"); 21 | 22 | public string SauceAccessKey => 23 | Environment.GetEnvironmentVariable("SAUCE_ACCESS_KEY"); 24 | 25 | public Dictionary SauceOptions; 26 | 27 | public string ScreenerApiKey => 28 | Environment.GetEnvironmentVariable("SCREENER_API_KEY"); 29 | 30 | public IJavaScriptExecutor JsExecutor => (IJavaScriptExecutor) Driver; 31 | 32 | public IWebDriver GetVisualDriver(ICapabilities capabilities) 33 | { 34 | //TimeSpan.FromSeconds(120) = needed so that there isn't a 'The HTTP request to the remote WebDriver server for URL' error 35 | var driver = new RemoteWebDriver(new Uri("https://hub.screener.io:443/wd/hub"), capabilities, 36 | TimeSpan.FromSeconds(120)); 37 | //Needed so that Screener 'end' command doesn't timeout 38 | driver.Manage().Timeouts().AsynchronousJavaScript = TimeSpan.FromSeconds(60); 39 | return driver; 40 | } 41 | 42 | public IWebDriver GetDesktopDriver(ICapabilities browserOptions) 43 | { 44 | return new RemoteWebDriver(new Uri("https://ondemand.saucelabs.com/wd/hub"), browserOptions); 45 | } 46 | 47 | public AndroidDriver GetAndroidDriver(AppiumOptions appiumOptions) 48 | { 49 | return new(new SauceLabsEndpoint().EmusimUri(SauceUserName, SauceAccessKey), appiumOptions, TimeSpan 50 | .FromSeconds(240)); 51 | } 52 | 53 | public IOSDriver GetIOSDriver(AppiumOptions appiumOptions) 54 | { 55 | return new(new SauceLabsEndpoint().EmusimUri(SauceUserName, SauceAccessKey), appiumOptions, TimeSpan 56 | .FromSeconds(240)); 57 | } 58 | 59 | public void ExecuteSauceCleanupSteps(IWebDriver driver) 60 | { 61 | var isPassed = TestContext.CurrentContext.Result.Outcome.Status 62 | == TestStatus.Passed; 63 | var script = "sauce:job-result=" + (isPassed ? "passed" : "failed"); 64 | ((IJavaScriptExecutor) driver).ExecuteScript(script); 65 | } 66 | } 67 | } -------------------------------------------------------------------------------- /DotnetCore/Sauce.Demo/Core.Selenium.Examples/AssemblyInfo.cs: -------------------------------------------------------------------------------- 1 | //Docs on NUnit parallelization: https://github.com/nunit/docs/wiki/Framework-Parallel-Test-Execution 2 | 3 | using NUnit.Framework; 4 | 5 | [assembly: Parallelizable(ParallelScope.Fixtures)] 6 | //Set this value to the Maximum amount of VMs that you have in Sauce Labs 7 | [assembly: LevelOfParallelism(100)] -------------------------------------------------------------------------------- /DotnetCore/Sauce.Demo/Core.Selenium.Examples/AxeAccesibility.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Collections.Generic; 3 | using Microsoft.VisualStudio.TestTools.UnitTesting; 4 | using OpenQA.Selenium; 5 | using OpenQA.Selenium.Chrome; 6 | using OpenQA.Selenium.Remote; 7 | using Selenium.Axe; 8 | 9 | namespace Core.Selenium.Examples 10 | { 11 | [TestClass] 12 | public class AxeAccesibility 13 | { 14 | IWebDriver _webDriver; 15 | public TestContext TestContext { get; set; } 16 | 17 | [TestMethod] 18 | public void AccessibilityTest() 19 | { 20 | var browserOptions = new ChromeOptions 21 | { 22 | PlatformName = "Windows 10", 23 | BrowserVersion = "latest" 24 | }; 25 | var sauceOptions = new Dictionary 26 | { 27 | { "name", TestContext.TestName }, 28 | { "username", Environment.GetEnvironmentVariable("SAUCE_USERNAME") }, 29 | { "accessKey", Environment.GetEnvironmentVariable("SAUCE_ACCESS_KEY") } 30 | }; 31 | browserOptions.AddAdditionalOption("sauce:options", sauceOptions); 32 | _webDriver = new RemoteWebDriver(new Uri("https://ondemand.us-west-1.saucelabs.com/wd/hub"), browserOptions); 33 | _webDriver.Navigate().GoToUrl("https://www.saucedemo.com"); 34 | var results = _webDriver.Analyze(); 35 | Assert.IsNull(results.Error); 36 | } 37 | 38 | [TestCleanup] 39 | public void Teardown() 40 | { 41 | _webDriver?.Quit(); 42 | } 43 | } 44 | } 45 | -------------------------------------------------------------------------------- /DotnetCore/Sauce.Demo/Core.Selenium.Examples/Constants.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | 3 | namespace Core.Selenium.Examples 4 | { 5 | public static class Constants 6 | { 7 | public static string BaseUrl => "https://www.saucedemo.com"; 8 | 9 | public static string BuildId { get; set; } = DateTime.Now.ToString("F"); 10 | } 11 | } -------------------------------------------------------------------------------- /DotnetCore/Sauce.Demo/Core.Selenium.Examples/Core.Selenium.Examples.csproj: -------------------------------------------------------------------------------- 1 |  2 | 3 | 4 | net6.0 5 | false 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | all 17 | runtime; build; native; contentfiles; analyzers; buildtransitive 18 | 19 | 20 | 21 | 22 | 23 | 24 | 25 | 26 | 27 | 28 | 29 | 30 | 31 | 32 | 33 | 34 | -------------------------------------------------------------------------------- /DotnetCore/Sauce.Demo/Core.Selenium.Examples/CrossBrowser/Wait.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using OpenQA.Selenium; 3 | using OpenQA.Selenium.Support.UI; 4 | using ExpectedConditions = SeleniumExtras.WaitHelpers.ExpectedConditions; 5 | 6 | namespace Core.Selenium.Examples.CrossBrowser 7 | { 8 | public class Wait 9 | { 10 | private readonly By _locator; 11 | 12 | public Wait(IWebDriver driver) 13 | { 14 | _wait = new WebDriverWait(driver, TimeSpan.FromSeconds(15)); 15 | } 16 | 17 | public Wait(IWebDriver driver, By locator) 18 | { 19 | _locator = locator; 20 | _wait = new WebDriverWait(driver, TimeSpan.FromSeconds(15)); 21 | } 22 | 23 | public Wait(IWebDriver driver, int timeoutInSeconds) 24 | { 25 | _wait = new WebDriverWait(driver, TimeSpan.FromSeconds(timeoutInSeconds)); 26 | } 27 | 28 | private WebDriverWait _wait { get; } 29 | 30 | public IWebElement UntilIsVisible(By locator) 31 | { 32 | return _wait.Until(ExpectedConditions.ElementIsVisible(locator)); 33 | } 34 | 35 | public IWebElement UntilIsVisibleByClass(string className) 36 | { 37 | return UntilIsVisible(By.ClassName(className)); 38 | } 39 | 40 | public bool IsVisible() 41 | { 42 | return _wait.Until(ExpectedConditions.ElementIsVisible(_locator)).Displayed; 43 | } 44 | 45 | public IWebElement UntilIsVisibleById(string id) 46 | { 47 | return UntilIsVisible(By.Id(id)); 48 | } 49 | 50 | //This type of method is really useful for negative assertions 51 | //To wait until something isn't displayed and then assert on it 52 | public bool UntilIsDisplayedById(string id) 53 | { 54 | bool isDisplayed; 55 | try 56 | { 57 | isDisplayed = UntilIsVisible(By.Id(id)).Displayed; 58 | } 59 | catch (WebDriverTimeoutException) 60 | { 61 | return false; 62 | } 63 | 64 | return isDisplayed; 65 | } 66 | 67 | public IWebElement UntilIsVisibleByCss(string css) 68 | { 69 | return UntilIsVisible(By.CssSelector(css)); 70 | } 71 | } 72 | } -------------------------------------------------------------------------------- /DotnetCore/Sauce.Demo/Core.Selenium.Examples/Emusim.Web/EmusimBaseTest.cs: -------------------------------------------------------------------------------- 1 | namespace Core.Selenium.Examples.Emusim.Web 2 | { 3 | public class EmusimBaseTest : AllTestsBase 4 | { 5 | public string DeviceName; 6 | public string PlatformVersion; 7 | 8 | public EmusimBaseTest(string deviceName, string platformVersion) 9 | { 10 | DeviceName = deviceName; 11 | PlatformVersion = platformVersion; 12 | } 13 | } 14 | } -------------------------------------------------------------------------------- /DotnetCore/Sauce.Demo/Core.Selenium.Examples/Emusim.Web/End/AndroidEmusimTests.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using Core.Common; 3 | using NUnit.Framework; 4 | using OpenQA.Selenium; 5 | using OpenQA.Selenium.Appium; 6 | using OpenQA.Selenium.Appium.Android; 7 | using OpenQA.Selenium.Appium.Enums; 8 | using OpenQA.Selenium.Support.UI; 9 | using ExpectedConditions = SeleniumExtras.WaitHelpers.ExpectedConditions; 10 | 11 | namespace Core.Selenium.Examples.Emusim.Web.End 12 | { 13 | [TestFixture] 14 | [TestFixtureSource(typeof(TestConfigData), nameof(TestConfigData.AndroidSimulators))] 15 | [Category("emusim")] 16 | [Category("android-end")] 17 | public class AndroidEmusimTests : EmusimBaseTest 18 | { 19 | [SetUp] 20 | public void Setup() 21 | { 22 | var appiumOptions = new AppiumOptions(); 23 | appiumOptions.AddAdditionalCapability(MobileCapabilityType.DeviceName, DeviceName); 24 | appiumOptions.AddAdditionalCapability(MobileCapabilityType.PlatformName, "Android"); 25 | appiumOptions.AddAdditionalCapability(MobileCapabilityType.PlatformVersion, PlatformVersion); 26 | appiumOptions.AddAdditionalCapability(MobileCapabilityType.BrowserName, "Chrome"); 27 | appiumOptions.AddAdditionalCapability(MobileCapabilityType.AppiumVersion, "1.20.2"); 28 | appiumOptions.AddAdditionalCapability("name", TestContext.CurrentContext.Test.Name); 29 | appiumOptions.AddAdditionalCapability("build", Constants.BuildId); 30 | 31 | _driver = GetAndroidDriver(appiumOptions); 32 | } 33 | 34 | [TearDown] 35 | public void EmusimTeardown() 36 | { 37 | if (_driver == null) return; 38 | 39 | ExecuteSauceCleanupSteps(_driver); 40 | _driver.Quit(); 41 | } 42 | 43 | private AndroidDriver _driver; 44 | 45 | public AndroidEmusimTests(string deviceName, string platformVersion) : base(deviceName, platformVersion) 46 | { 47 | } 48 | 49 | [Test] 50 | [Retry(1)] 51 | public void ValidUserCanLogin() 52 | { 53 | _driver.Navigate().GoToUrl("https://www.saucedemo.com"); 54 | // Appium doesn't accept Ids as a locator strategy. Better to use CssSelector 55 | _driver.FindElement(By.CssSelector("#user-name")).SendKeys("standard_user"); 56 | _driver.FindElement(By.CssSelector("#password")).SendKeys("secret_sauce"); 57 | _driver.FindElement(By.CssSelector(".btn_action")).Click(); 58 | 59 | var wait = new WebDriverWait(_driver, TimeSpan.FromSeconds(20)); 60 | wait.Until(ExpectedConditions.ElementIsVisible(By.CssSelector("#inventory_container"))); 61 | } 62 | } 63 | } -------------------------------------------------------------------------------- /DotnetCore/Sauce.Demo/Core.Selenium.Examples/Emusim.Web/End/IOSEmusimTests.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using Core.Common; 3 | using NUnit.Framework; 4 | using OpenQA.Selenium; 5 | using OpenQA.Selenium.Appium; 6 | using OpenQA.Selenium.Appium.Enums; 7 | using OpenQA.Selenium.Appium.iOS; 8 | using OpenQA.Selenium.Support.UI; 9 | using ExpectedConditions = SeleniumExtras.WaitHelpers.ExpectedConditions; 10 | 11 | namespace Core.Selenium.Examples.Emusim.Web.End 12 | { 13 | [TestFixture] 14 | [TestFixtureSource(typeof(TestConfigData), nameof(TestConfigData.IOSSimulators))] 15 | [Category("ios-end")] 16 | [Category("emusim")] 17 | public class IOSEmusimTests : EmusimBaseTest 18 | { 19 | [SetUp] 20 | public void Setup() 21 | { 22 | var appiumOptions = new AppiumOptions(); 23 | appiumOptions.AddAdditionalCapability(MobileCapabilityType.DeviceName, DeviceName); 24 | appiumOptions.AddAdditionalCapability(MobileCapabilityType.PlatformName, "iOS"); 25 | appiumOptions.AddAdditionalCapability(MobileCapabilityType.PlatformVersion, PlatformVersion); 26 | appiumOptions.AddAdditionalCapability(MobileCapabilityType.BrowserName, "Safari"); 27 | appiumOptions.AddAdditionalCapability("name", TestContext.CurrentContext.Test.Name); 28 | appiumOptions.AddAdditionalCapability("build", Common.Constants.BuildId); 29 | 30 | _driver = GetIOSDriver(appiumOptions); 31 | } 32 | 33 | [TearDown] 34 | public void EmusimTeardown() 35 | { 36 | if (_driver == null) return; 37 | 38 | ExecuteSauceCleanupSteps(_driver); 39 | _driver.Quit(); 40 | } 41 | 42 | private IOSDriver _driver; 43 | 44 | public IOSEmusimTests(string deviceName, string platformVersion) : base(deviceName, platformVersion) 45 | { 46 | } 47 | 48 | [Test] 49 | [Retry(1)] 50 | public void ValidUserCanLogin() 51 | { 52 | _driver.Navigate().GoToUrl("https://www.saucedemo.com"); 53 | // Appium doesn't accept Ids as a locator strategy. Better to use CssSelector 54 | _driver.FindElement(By.CssSelector("#user-name")).SendKeys("standard_user"); 55 | _driver.FindElement(By.CssSelector("#password")).SendKeys("secret_sauce"); 56 | _driver.FindElement(By.CssSelector(".btn_action")).Click(); 57 | 58 | var wait = new WebDriverWait(_driver, TimeSpan.FromSeconds(20)); 59 | wait.Until(ExpectedConditions.ElementIsVisible(By.CssSelector("#inventory_container"))); 60 | } 61 | } 62 | } -------------------------------------------------------------------------------- /DotnetCore/Sauce.Demo/Core.Selenium.Examples/RDC.Web/End/RealDeviceIOSWebTests.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using Core.Common; 3 | using NUnit.Framework; 4 | using OpenQA.Selenium; 5 | using OpenQA.Selenium.Appium.iOS; 6 | using OpenQA.Selenium.Support.UI; 7 | using ExpectedConditions = SeleniumExtras.WaitHelpers.ExpectedConditions; 8 | 9 | namespace Core.Selenium.Examples.RDC.Web.End 10 | { 11 | [TestFixtureSource(typeof(TestConfigData), nameof(TestConfigData.MostPopularIOSDevices))] 12 | [Category("rdc")] 13 | [Category("ios-end")] 14 | public class RealDeviceIOSWebTests : MobileBaseTest 15 | { 16 | [SetUp] 17 | public void IOSSetup() 18 | { 19 | Driver = GetIOSDriver(MobileOptions); 20 | } 21 | 22 | [TearDown] 23 | public void Teardown() 24 | { 25 | if (Driver == null) return; 26 | 27 | ExecuteSauceCleanupSteps(Driver); 28 | Driver.Quit(); 29 | } 30 | 31 | public new IOSDriver Driver { get; set; } 32 | 33 | public RealDeviceIOSWebTests(string deviceName, string platform, string browser) : 34 | base(deviceName, platform, browser) 35 | { 36 | } 37 | 38 | [Test] 39 | [Retry(1)] 40 | public void LoginWorks() 41 | { 42 | Driver.Navigate().GoToUrl("https://www.saucedemo.com"); 43 | // Appium doesn't accept Ids as a locator strategy. Better to use CssSelector 44 | Driver.FindElement(By.CssSelector("#user-name")).SendKeys("standard_user"); 45 | Driver.FindElement(By.CssSelector("#password")).SendKeys("secret_sauce"); 46 | Driver.FindElement(By.CssSelector(".btn_action")).Click(); 47 | 48 | var wait = new WebDriverWait(Driver, TimeSpan.FromSeconds(20)); 49 | wait.Until(ExpectedConditions.ElementIsVisible(By.CssSelector("#inventory_container"))); 50 | } 51 | } 52 | } -------------------------------------------------------------------------------- /DotnetCore/Sauce.Demo/Core.Selenium.Examples/RDC.Web/MobileBaseTest.cs: -------------------------------------------------------------------------------- 1 | using NUnit.Framework; 2 | using OpenQA.Selenium.Appium; 3 | using OpenQA.Selenium.Appium.Enums; 4 | 5 | namespace Core.Selenium.Examples.RDC.Web 6 | { 7 | public class MobileBaseTest : AllTestsBase 8 | { 9 | [SetUp] 10 | public void MobileBaseSetup() 11 | { 12 | MobileOptions = new AppiumOptions(); 13 | MobileOptions.AddAdditionalCapability(MobileCapabilityType.DeviceName, DeviceName); 14 | MobileOptions.AddAdditionalCapability(MobileCapabilityType.PlatformName, Platform); 15 | MobileOptions.AddAdditionalCapability(MobileCapabilityType.BrowserName, Browser); 16 | MobileOptions.AddAdditionalCapability("name", TestContext.CurrentContext.Test.Name); 17 | MobileOptions.AddAdditionalCapability("newCommandTimeout", 90); 18 | MobileOptions.AddAdditionalCapability("build", Common.Constants.BuildId); 19 | } 20 | 21 | public readonly string DeviceName; 22 | public readonly string Platform; 23 | public readonly string Browser; 24 | 25 | public MobileBaseTest(string deviceName, string platform, string browser) 26 | { 27 | DeviceName = deviceName; 28 | Platform = platform; 29 | Browser = browser; 30 | } 31 | 32 | public AppiumOptions MobileOptions { get; set; } 33 | } 34 | } -------------------------------------------------------------------------------- /DotnetCore/Sauce.Demo/Core.Selenium.Examples/RDC.Web/Start/RealDeviceAndroidWebTests.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using Core.Common; 3 | using NUnit.Framework; 4 | using OpenQA.Selenium; 5 | using OpenQA.Selenium.Appium.Android; 6 | using OpenQA.Selenium.Support.UI; 7 | using ExpectedConditions = SeleniumExtras.WaitHelpers.ExpectedConditions; 8 | 9 | namespace Core.Selenium.Examples.RDC.Web.Start 10 | { 11 | [TestFixtureSource(typeof(TestConfigData), nameof(TestConfigData.AndroidDevices))] 12 | [Parallelizable] 13 | public class RealDeviceAndroidWebTests : MobileBaseTest 14 | { 15 | [SetUp] 16 | public void AndroidSetup() 17 | { 18 | Driver = GetAndroidDriver(MobileOptions); 19 | } 20 | 21 | [TearDown] 22 | public void Teardown() 23 | { 24 | if (Driver == null) return; 25 | 26 | ExecuteSauceCleanupSteps(Driver); 27 | Driver.Quit(); 28 | } 29 | 30 | public new AndroidDriver Driver { get; set; } 31 | 32 | public RealDeviceAndroidWebTests(string deviceName, string platform, string browser) : 33 | base(deviceName, platform, browser) 34 | { 35 | } 36 | 37 | [Test] 38 | [Retry(1)] 39 | public void LoginWorks() 40 | { 41 | Driver.Navigate().GoToUrl("https://www.saucedemo.com"); 42 | // Appium doesn't accept Ids as a locator strategy. Better to use CssSelector 43 | Driver.FindElement(By.CssSelector("#user-name")).SendKeys("standard_user"); 44 | Driver.FindElement(By.CssSelector("#password")).SendKeys("secret_sauce"); 45 | Driver.FindElement(By.CssSelector(".btn_action")).Click(); 46 | 47 | var wait = new WebDriverWait(Driver, TimeSpan.FromSeconds(20)); 48 | wait.Until(ExpectedConditions.ElementIsVisible(By.CssSelector("#inventory_container"))); 49 | } 50 | } 51 | } -------------------------------------------------------------------------------- /DotnetCore/Sauce.Demo/Core.Selenium.Examples/RDC.Web/Start/RealDeviceIOSWebTests.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using Core.Common; 3 | using NUnit.Framework; 4 | using OpenQA.Selenium; 5 | using OpenQA.Selenium.Appium.Android; 6 | using OpenQA.Selenium.Support.UI; 7 | using ExpectedConditions = SeleniumExtras.WaitHelpers.ExpectedConditions; 8 | 9 | namespace Core.Selenium.Examples.RDC.Web.Start 10 | { 11 | //TODO use [TestFixtureSource(typeof(TestConfigData), nameof(TestConfigData.MostPopularIOSDevices))] 12 | [TestFixtureSource(typeof(TestConfigData), nameof(TestConfigData.AndroidDevices))] 13 | public class RealDeviceIOSWebTests : MobileBaseTest 14 | { 15 | [SetUp] 16 | public void IOSSetup() 17 | { 18 | //TODO use GetIOSDriver() 19 | Driver = GetAndroidDriver(MobileOptions); 20 | } 21 | 22 | [TearDown] 23 | public void Teardown() 24 | { 25 | if (Driver == null) return; 26 | 27 | ExecuteSauceCleanupSteps(Driver); 28 | Driver.Quit(); 29 | } 30 | 31 | //TODO replace with IOSDriver 32 | public new AndroidDriver Driver { get; set; } 33 | 34 | public RealDeviceIOSWebTests(string deviceName, string platform, string browser) : 35 | base(deviceName, platform, browser) 36 | { 37 | } 38 | 39 | [Test] 40 | [Retry(1)] 41 | public void LoginWorks() 42 | { 43 | Driver.Navigate().GoToUrl("https://www.saucedemo.com"); 44 | // Appium doesn't accept Ids as a locator strategy. Better to use CssSelector 45 | Driver.FindElement(By.CssSelector("#user-name")).SendKeys("standard_user"); 46 | Driver.FindElement(By.CssSelector("#password")).SendKeys("secret_sauce"); 47 | Driver.FindElement(By.CssSelector(".btn_action")).Click(); 48 | 49 | var wait = new WebDriverWait(Driver, TimeSpan.FromSeconds(20)); 50 | wait.Until(ExpectedConditions.ElementIsVisible(By.CssSelector("#inventory_container"))); 51 | } 52 | } 53 | } -------------------------------------------------------------------------------- /DotnetCore/Sauce.Demo/Core.Selenium.Examples/Selenium4/NewFeatures/MSEdge.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Collections; 3 | using System.Collections.Generic; 4 | using Microsoft.VisualStudio.TestTools.UnitTesting; 5 | using OpenQA.Selenium; 6 | using OpenQA.Selenium.Edge; 7 | 8 | namespace Core.Selenium.Examples.Selenium4.NewFeatures 9 | { 10 | [TestClass] 11 | public class MSEdge 12 | { 13 | public IWebDriver Driver { get; set; } 14 | 15 | public TestContext TestContext { get; set; } 16 | 17 | [TestMethod] 18 | public void EdgeOptionsTest() 19 | { 20 | var browserOptions = new EdgeOptions(); 21 | browserOptions.BinaryLocation = "/Path/To/Binary"; 22 | browserOptions.AddArguments("foo"); 23 | browserOptions.AddUserProfilePreference("foo", "bar"); 24 | 25 | Dictionary caps = (Dictionary)browserOptions 26 | .ToCapabilities() 27 | .GetCapability("ms:edgeOptions"); 28 | 29 | List args = new List() { "foo" }; 30 | CollectionAssert.AreEquivalent(args, (ICollection)caps.GetValueOrDefault("args")); 31 | 32 | Assert.AreEqual("bar", ((Dictionary)caps.GetValueOrDefault("prefs"))!.GetValueOrDefault("foo")); 33 | Assert.AreEqual("/Path/To/Binary", caps.GetValueOrDefault("binary")); 34 | } 35 | } 36 | } -------------------------------------------------------------------------------- /DotnetCore/Sauce.Demo/Core.Selenium.Examples/Selenium4/NewFeatures/NewWindow.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Collections.Generic; 3 | using System.Drawing; 4 | using Microsoft.VisualStudio.TestTools.UnitTesting; 5 | using OpenQA.Selenium; 6 | using OpenQA.Selenium.Chrome; 7 | using OpenQA.Selenium.Remote; 8 | 9 | namespace Core.Selenium.Examples.Selenium4.NewFeatures 10 | { 11 | [TestClass] 12 | public class NewWindow 13 | { 14 | public IWebDriver Driver { get; set; } 15 | 16 | public TestContext TestContext { get; set; } 17 | 18 | [TestInitialize] 19 | public void Setup() 20 | { 21 | var browserOptions = new ChromeOptions(); 22 | browserOptions.PlatformName = "Windows 10"; 23 | browserOptions.BrowserVersion = "latest"; 24 | 25 | var sauceOptions = new Dictionary(); 26 | sauceOptions.Add("name", TestContext.TestName); 27 | sauceOptions.Add("username", Environment.GetEnvironmentVariable("SAUCE_USERNAME")); 28 | sauceOptions.Add("accessKey", Environment.GetEnvironmentVariable("SAUCE_ACCESS_KEY")); 29 | 30 | browserOptions.AddAdditionalOption("sauce:options", sauceOptions); 31 | var sauceUrl = new Uri("https://ondemand.us-west-1.saucelabs.com/wd/hub"); 32 | 33 | Driver = new RemoteWebDriver(sauceUrl, browserOptions); 34 | } 35 | 36 | [TestMethod] 37 | public void NewWindowWindow() 38 | { 39 | Driver.SwitchTo().NewWindow(WindowType.Window); 40 | Driver.Manage().Window.Position = new Point(100, 400); 41 | 42 | Assert.AreEqual(2, Driver.WindowHandles.Count); 43 | } 44 | 45 | [TestMethod] 46 | public void NewWindowTab() 47 | { 48 | Driver.SwitchTo().NewWindow(WindowType.Tab); 49 | 50 | Assert.AreEqual(2, Driver.WindowHandles.Count); 51 | } 52 | 53 | [TestCleanup] 54 | public void Teardown() 55 | { 56 | var isPassed = TestContext.CurrentTestOutcome == UnitTestOutcome.Passed; 57 | var script = "sauce:job-result=" + (isPassed ? "passed" : "failed"); 58 | ((IJavaScriptExecutor) Driver).ExecuteScript(script); 59 | 60 | Driver?.Quit(); 61 | } 62 | } 63 | } -------------------------------------------------------------------------------- /DotnetCore/Sauce.Demo/Core.Selenium.Examples/Selenium4/NewFeatures/RelativeLocators.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Collections.Generic; 3 | using Microsoft.VisualStudio.TestTools.UnitTesting; 4 | using OpenQA.Selenium; 5 | using OpenQA.Selenium.Chrome; 6 | using OpenQA.Selenium.Remote; 7 | using OpenQA.Selenium.Support.Extensions; 8 | 9 | namespace Core.Selenium.Examples.Selenium4.NewFeatures 10 | { 11 | [TestClass] 12 | public class RelativeLocators 13 | { 14 | public IWebDriver Driver { get; set; } 15 | 16 | public TestContext TestContext { get; set; } 17 | 18 | [TestInitialize] 19 | public void Setup() 20 | { 21 | var browserOptions = new ChromeOptions(); 22 | browserOptions.PlatformName = "Windows 10"; 23 | browserOptions.BrowserVersion = "latest"; 24 | 25 | var sauceOptions = new Dictionary(); 26 | sauceOptions.Add("name", TestContext.TestName); 27 | sauceOptions.Add("username", Environment.GetEnvironmentVariable("SAUCE_USERNAME")); 28 | sauceOptions.Add("accessKey", Environment.GetEnvironmentVariable("SAUCE_ACCESS_KEY")); 29 | 30 | browserOptions.AddAdditionalOption("sauce:options", sauceOptions); 31 | var sauceUrl = new Uri("https://ondemand.us-west-1.saucelabs.com/wd/hub"); 32 | 33 | Driver = new RemoteWebDriver(sauceUrl, browserOptions); 34 | } 35 | 36 | [TestMethod] 37 | public void RelativeLocationTest() 38 | { 39 | Driver.Navigate().GoToUrl("https://www.diemol.com/selenium-4-demo/relative-locators-demo.html"); 40 | 41 | IWebElement element = Driver.FindElement(RelativeBy.WithLocator(By.TagName("li")) 42 | .LeftOf(By.Id("berlin")) 43 | .Below(By.Id("warsaw"))); 44 | 45 | Driver.ExecuteJavaScript("arguments[0].style.filter='blur(8px)'", element); 46 | 47 | Assert.AreEqual("london", element.GetAttribute("id")); 48 | } 49 | 50 | [TestCleanup] 51 | public void Teardown() 52 | { 53 | var isPassed = TestContext.CurrentTestOutcome == UnitTestOutcome.Passed; 54 | var script = "sauce:job-result=" + (isPassed ? "passed" : "failed"); 55 | ((IJavaScriptExecutor) Driver).ExecuteScript(script); 56 | 57 | Driver?.Quit(); 58 | } 59 | } 60 | } -------------------------------------------------------------------------------- /DotnetCore/Sauce.Demo/Core.Selenium.Examples/Selenium4/NewFeatures/Timeouts.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Collections.Generic; 3 | using Microsoft.VisualStudio.TestTools.UnitTesting; 4 | using OpenQA.Selenium; 5 | using OpenQA.Selenium.Chrome; 6 | using OpenQA.Selenium.Remote; 7 | 8 | namespace Core.Selenium.Examples.Selenium4.NewFeatures 9 | { 10 | [TestClass] 11 | public class Timeouts 12 | { 13 | public IWebDriver Driver { get; set; } 14 | 15 | public TestContext TestContext { get; set; } 16 | 17 | [TestInitialize] 18 | public void Setup() 19 | { 20 | var browserOptions = new ChromeOptions(); 21 | browserOptions.PlatformName = "Windows 10"; 22 | browserOptions.BrowserVersion = "latest"; 23 | 24 | var sauceOptions = new Dictionary(); 25 | sauceOptions.Add("name", TestContext.TestName); 26 | sauceOptions.Add("username", Environment.GetEnvironmentVariable("SAUCE_USERNAME")); 27 | sauceOptions.Add("accessKey", Environment.GetEnvironmentVariable("SAUCE_ACCESS_KEY")); 28 | 29 | browserOptions.AddAdditionalOption("sauce:options", sauceOptions); 30 | var sauceUrl = new Uri("https://ondemand.us-west-1.saucelabs.com/wd/hub"); 31 | 32 | Driver = new RemoteWebDriver(sauceUrl, browserOptions); 33 | } 34 | 35 | [TestMethod] 36 | public void GetTimeoutsTest() 37 | { 38 | Driver.Manage().Timeouts().ImplicitWait = TimeSpan.FromSeconds(0.3); 39 | 40 | Assert.AreEqual(TimeSpan.FromSeconds(0.3), Driver.Manage().Timeouts().ImplicitWait); 41 | Assert.AreEqual(TimeSpan.FromSeconds(300), Driver.Manage().Timeouts().PageLoad); 42 | Assert.AreEqual(TimeSpan.FromSeconds(30), Driver.Manage().Timeouts().AsynchronousJavaScript); 43 | } 44 | 45 | [TestCleanup] 46 | public void Teardown() 47 | { 48 | var isPassed = TestContext.CurrentTestOutcome == UnitTestOutcome.Passed; 49 | var script = "sauce:job-result=" + (isPassed ? "passed" : "failed"); 50 | ((IJavaScriptExecutor) Driver).ExecuteScript(script); 51 | 52 | Driver?.Quit(); 53 | } 54 | } 55 | } -------------------------------------------------------------------------------- /DotnetCore/Sauce.Demo/Core.Selenium.Examples/Selenium4/NewFeatures/ViewPageChrome.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Collections.Generic; 3 | using System.IO; 4 | using Microsoft.VisualStudio.TestTools.UnitTesting; 5 | using OpenQA.Selenium; 6 | using OpenQA.Selenium.Chrome; 7 | using OpenQA.Selenium.Remote; 8 | 9 | namespace Core.Selenium.Examples.Selenium4.NewFeatures 10 | { 11 | [TestClass] 12 | public class ViewPageChrome 13 | { 14 | public IWebDriver Driver { get; set; } 15 | 16 | public TestContext TestContext { get; set; } 17 | 18 | [TestInitialize] 19 | public void Setup() 20 | { 21 | var browserOptions = new ChromeOptions(); 22 | browserOptions.PlatformName = "Windows 10"; 23 | browserOptions.BrowserVersion = "latest"; 24 | browserOptions.AddArguments("--headless"); 25 | 26 | var sauceOptions = new Dictionary(); 27 | sauceOptions.Add("name", TestContext.TestName); 28 | sauceOptions.Add("username", Environment.GetEnvironmentVariable("SAUCE_USERNAME")); 29 | sauceOptions.Add("accessKey", Environment.GetEnvironmentVariable("SAUCE_ACCESS_KEY")); 30 | 31 | browserOptions.AddAdditionalOption("sauce:options", sauceOptions); 32 | var sauceUrl = new Uri("https://ondemand.us-west-1.saucelabs.com/wd/hub"); 33 | 34 | Driver = new RemoteWebDriver(sauceUrl, browserOptions); 35 | } 36 | 37 | [TestMethod] 38 | public void ScreenshotTest() 39 | { 40 | Driver.Navigate().GoToUrl("https://www.saucedemo.com/v1/inventory.html"); 41 | var parentFullName = Directory.GetParent(Environment.CurrentDirectory)?.Parent?.Parent?.FullName; 42 | 43 | 44 | ((ITakesScreenshot) Driver).GetScreenshot().SaveAsFile(parentFullName + "/Selenium4/Resources/ChromeScreenshot.png", 45 | ScreenshotImageFormat.Png); 46 | } 47 | 48 | [TestMethod] 49 | public void PrintPageTest() 50 | { 51 | Driver.Navigate().GoToUrl("https://www.saucedemo.com/v1/inventory.html"); 52 | var parentFullName = Directory.GetParent(Environment.CurrentDirectory)?.Parent?.Parent?.FullName; 53 | 54 | ((ISupportsPrint) Driver).Print(new PrintOptions()).SaveAsFile(parentFullName + "/Selenium4/Resources/ChromePrintPage.pdf"); 55 | } 56 | 57 | [TestCleanup] 58 | public void Teardown() 59 | { 60 | var isPassed = TestContext.CurrentTestOutcome == UnitTestOutcome.Passed; 61 | var script = "sauce:job-result=" + (isPassed ? "passed" : "failed"); 62 | ((IJavaScriptExecutor) Driver).ExecuteScript(script); 63 | 64 | Driver?.Quit(); 65 | } 66 | } 67 | } -------------------------------------------------------------------------------- /DotnetCore/Sauce.Demo/Core.Selenium.Examples/Selenium4/Resources/ninja_saucebot-1.0-an+fx.xpi: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/saucelabs-training/demo-csharp/65768a379c08ecf0aa1d99ef1c466f9c2922e772/DotnetCore/Sauce.Demo/Core.Selenium.Examples/Selenium4/Resources/ninja_saucebot-1.0-an+fx.xpi -------------------------------------------------------------------------------- /DotnetCore/Sauce.Demo/Core.Selenium.Examples/Selenium4Demo.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Collections.Generic; 3 | using Microsoft.VisualStudio.TestTools.UnitTesting; 4 | using OpenQA.Selenium; 5 | using OpenQA.Selenium.Chrome; 6 | using OpenQA.Selenium.Remote; 7 | using OpenQA.Selenium.Support.UI; 8 | 9 | namespace Core.Selenium.Examples 10 | { 11 | [TestClass] 12 | public class Selenium4Demo 13 | { 14 | public IWebDriver Driver { get; set; } 15 | 16 | public TestContext TestContext { get; set; } 17 | 18 | [TestInitialize] 19 | public void Setup() 20 | { 21 | var browserOptions = new ChromeOptions(); 22 | browserOptions.PlatformName = "Windows 10"; 23 | browserOptions.BrowserVersion = "latest"; 24 | 25 | var sauceOptions = new Dictionary(); 26 | sauceOptions.Add("name", TestContext.TestName); 27 | sauceOptions.Add("username", Environment.GetEnvironmentVariable("SAUCE_USERNAME")); 28 | sauceOptions.Add("accessKey", Environment.GetEnvironmentVariable("SAUCE_ACCESS_KEY")); 29 | 30 | browserOptions.AddAdditionalOption("sauce:options", sauceOptions); 31 | var sauceUrl = new Uri("https://ondemand.us-west-1.saucelabs.com/wd/hub"); 32 | 33 | Driver = new RemoteWebDriver(sauceUrl, browserOptions); 34 | } 35 | 36 | [TestMethod] 37 | public void LoginTest() 38 | { 39 | Driver.Navigate().GoToUrl("https://www.saucedemo.com"); 40 | 41 | var usernameLocator = By.CssSelector("#user-name"); 42 | var passwordLocator = By.CssSelector("#password"); 43 | var submitLocator = By.CssSelector(".btn_action"); 44 | 45 | var wait = new WebDriverWait(Driver, TimeSpan.FromSeconds(15)); 46 | wait.Until(drv => drv.FindElement(usernameLocator)); 47 | 48 | var usernameElement = Driver.FindElement(usernameLocator); 49 | var passwordElement = Driver.FindElement(passwordLocator); 50 | var submitElement = Driver.FindElement(submitLocator); 51 | 52 | usernameElement.SendKeys("standard_user"); 53 | passwordElement.SendKeys("secret_sauce"); 54 | submitElement.Click(); 55 | 56 | Assert.AreEqual("https://www.saucedemo.com/inventory.html", Driver.Url); 57 | } 58 | 59 | [TestCleanup] 60 | public void Teardown() 61 | { 62 | var isPassed = TestContext.CurrentTestOutcome == UnitTestOutcome.Passed; 63 | var script = "sauce:job-result=" + (isPassed ? "passed" : "failed"); 64 | ((IJavaScriptExecutor) Driver).ExecuteScript(script); 65 | 66 | Driver?.Quit(); 67 | } 68 | } 69 | } -------------------------------------------------------------------------------- /DotnetCore/Sauce.Demo/Sauce.Demo.sln: -------------------------------------------------------------------------------- 1 |  2 | Microsoft Visual Studio Solution File, Format Version 12.00 3 | # Visual Studio Version 16 4 | VisualStudioVersion = 16.0.30503.244 5 | MinimumVisualStudioVersion = 10.0.40219.1 6 | Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Core.Selenium.Examples", "Core.Selenium.Examples\Core.Selenium.Examples.csproj", "{A191F1BF-F80A-4EB3-B883-E519C0CDAC56}" 7 | EndProject 8 | Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Core.BestPractices.Web", "Core.BestPractices.Web\Core.BestPractices.Web.csproj", "{14354D79-1A45-43A6-9F28-C3F910232DE6}" 9 | EndProject 10 | Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Core.Common", "Core.Common\Core.Common.csproj", "{820866CE-01F3-4695-9886-702B0AAB3CF2}" 11 | EndProject 12 | Global 13 | GlobalSection(SolutionConfigurationPlatforms) = preSolution 14 | Debug|Any CPU = Debug|Any CPU 15 | Release|Any CPU = Release|Any CPU 16 | EndGlobalSection 17 | GlobalSection(ProjectConfigurationPlatforms) = postSolution 18 | {A191F1BF-F80A-4EB3-B883-E519C0CDAC56}.Debug|Any CPU.ActiveCfg = Debug|Any CPU 19 | {A191F1BF-F80A-4EB3-B883-E519C0CDAC56}.Debug|Any CPU.Build.0 = Debug|Any CPU 20 | {A191F1BF-F80A-4EB3-B883-E519C0CDAC56}.Release|Any CPU.ActiveCfg = Release|Any CPU 21 | {A191F1BF-F80A-4EB3-B883-E519C0CDAC56}.Release|Any CPU.Build.0 = Release|Any CPU 22 | {14354D79-1A45-43A6-9F28-C3F910232DE6}.Debug|Any CPU.ActiveCfg = Debug|Any CPU 23 | {14354D79-1A45-43A6-9F28-C3F910232DE6}.Debug|Any CPU.Build.0 = Debug|Any CPU 24 | {14354D79-1A45-43A6-9F28-C3F910232DE6}.Release|Any CPU.ActiveCfg = Release|Any CPU 25 | {14354D79-1A45-43A6-9F28-C3F910232DE6}.Release|Any CPU.Build.0 = Release|Any CPU 26 | {820866CE-01F3-4695-9886-702B0AAB3CF2}.Debug|Any CPU.ActiveCfg = Debug|Any CPU 27 | {820866CE-01F3-4695-9886-702B0AAB3CF2}.Debug|Any CPU.Build.0 = Debug|Any CPU 28 | {820866CE-01F3-4695-9886-702B0AAB3CF2}.Release|Any CPU.ActiveCfg = Release|Any CPU 29 | {820866CE-01F3-4695-9886-702B0AAB3CF2}.Release|Any CPU.Build.0 = Release|Any CPU 30 | EndGlobalSection 31 | GlobalSection(SolutionProperties) = preSolution 32 | HideSolutionNode = FALSE 33 | EndGlobalSection 34 | GlobalSection(ExtensibilityGlobals) = postSolution 35 | SolutionGuid = {26619DE9-DDD0-4409-8602-0F8E1807C025} 36 | EndGlobalSection 37 | EndGlobal 38 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2018 nadvolod 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | -------------------------------------------------------------------------------- /SauceExamples/Appium3.MsTest.Scripts.yml: -------------------------------------------------------------------------------- 1 | pool: 2 | name: Hosted VS2017 3 | demands: 4 | - msbuild 5 | - visualstudio 6 | - vstest 7 | 8 | steps: 9 | - script: set 10 | displayName: print all variables 11 | - task: NuGetToolInstaller@0 12 | displayName: 'Use NuGet 4.4.1' 13 | inputs: 14 | versionSpec: 4.4.1 15 | 16 | - task: NuGetCommand@2 17 | displayName: 'NuGet restore' 18 | inputs: 19 | restoreSolution: '**\*.sln' 20 | 21 | - task: VSBuild@1 22 | displayName: 'Build solution' 23 | inputs: 24 | solution: '**\*.sln' 25 | msbuildArgs: '/p:DeployOnBuild=true /p:WebPublishMethod=Package /p:PackageAsSingleFile=true /p:SkipInvalidConfigurations=true /p:DesktopBuildPackageLocation="$(build.artifactstagingdirectory)\WebApp.zip" /p:DeployIisAppPath="Default Web Site"' 26 | logProjectEvents: true 27 | 28 | - task: PowerShell@2 29 | displayName: 'Set Sauce Environment Variables' 30 | inputs: 31 | targetType: filePath 32 | filePath: ./setEnvironmentVariables.ps1 33 | arguments: | 34 | '$env:SAUCE_USER 35 | $env:SAUCE_KEY 36 | $env:SAUCE_RDC_VODQANATIVEAPPAPIKEY 37 | $env:SAUCE_RDC_SAUCEDEMOIOSRDCAPIKEY'; 38 | 39 | # Using powershell ##vso command to set an environment variable in the system 40 | - powershell: | 41 | Write-Host "Sauce Username stored in ADO variables is=>$($env:SAUCE_USER)"; 42 | Write-Host "Sauce Access Key stored in ADO variables is=>$($env:SAUCE_KEY)"; 43 | Write-Host "Sauce Username stored in Env variables is=>$($env:SAUCE_USERNAME)"; 44 | Write-Host "Sauce Access Key stored in Env variables is=>$($env:SAUCE_ACCESS_KEY)"; 45 | Write-Host "Sauce Build Repository URI stored in Env variables is=>$($env:BUILD_REPOSITORY_URI)"; 46 | 47 | #checking to make sure that env variables were set between yml tasks 48 | - powershell: | 49 | Write-Host "Sauce Username stored in Env Variables variables is=>$($env:SAUCE_USERNAME)"; 50 | Write-Host "Sauce Access Key stored in ADO variables is=>$($env:SAUCE_ACCESS_KEY)"; 51 | displayName: display env variables bw posh tasks 52 | 53 | - task: VSTest@2 54 | displayName: 'Run Appium 3 Emusim and RDC Tests' 55 | inputs: 56 | searchFolder: '$(System.DefaultWorkingDirectory)' 57 | testSelector: 'testAssemblies' 58 | testAssemblyVer2: | 59 | **\Appium3.MsTest.Scripts.dll 60 | !**\SauceExamples\packages\*.dll 61 | !**\packages\*.dll 62 | runInParallel: true 63 | codeCoverageEnabled: true 64 | testRunTitle: 'Appium 3 Emusim and RDC Tests' 65 | rerunFailedTests: true 66 | rerunFailedThreshold: 10 67 | rerunMaxAttempts: 2 68 | failOnMinTestsNotRun: true -------------------------------------------------------------------------------- /SauceExamples/Common/Properties/AssemblyInfo.cs: -------------------------------------------------------------------------------- 1 | using System.Reflection; 2 | using System.Runtime.InteropServices; 3 | 4 | // General Information about an assembly is controlled through the following 5 | // set of attributes. Change these attribute values to modify the information 6 | // associated with an assembly. 7 | [assembly: AssemblyTitle("Common")] 8 | [assembly: AssemblyDescription("")] 9 | [assembly: AssemblyConfiguration("")] 10 | [assembly: AssemblyCompany("")] 11 | [assembly: AssemblyProduct("Common")] 12 | [assembly: AssemblyCopyright("Copyright © 2018")] 13 | [assembly: AssemblyTrademark("")] 14 | [assembly: AssemblyCulture("")] 15 | 16 | // Setting ComVisible to false makes the types in this assembly not visible 17 | // to COM components. If you need to access a type in this assembly from 18 | // COM, set the ComVisible attribute to true on that type. 19 | [assembly: ComVisible(false)] 20 | 21 | // The following GUID is for the ID of the typelib if this project is exposed to COM 22 | [assembly: Guid("be3ef376-360b-4b9c-bb24-e6c3b9545e4d")] 23 | 24 | // Version information for an assembly consists of the following four values: 25 | // 26 | // Major Version 27 | // Minor Version 28 | // Build Number 29 | // Revision 30 | // 31 | // You can specify all the values or you can default the Build and Revision Numbers 32 | // by using the '*' as shown below: 33 | // [assembly: AssemblyVersion("1.0.*")] 34 | [assembly: AssemblyVersion("1.0.0.0")] 35 | [assembly: AssemblyFileVersion("1.0.0.0")] 36 | -------------------------------------------------------------------------------- /SauceExamples/Common/SauceLabs/ApiKeys.cs: -------------------------------------------------------------------------------- 1 | namespace Common.SauceLabs 2 | { 3 | public class ApiKeys 4 | { 5 | public RdcKeys Rdc => new RdcKeys(); 6 | } 7 | 8 | public class RdcKeys 9 | { 10 | public App Apps => new App(); 11 | } 12 | } 13 | -------------------------------------------------------------------------------- /SauceExamples/Common/SauceLabs/App.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | 3 | namespace Common.SauceLabs 4 | { 5 | public class App 6 | { 7 | public string SampleAppIOS => Environment.GetEnvironmentVariable("SAUCE_DEMO_IOS_RDC_API_KEY", EnvironmentVariableTarget.User); 8 | 9 | public string SampleAppAndroid => 10 | Environment.GetEnvironmentVariable("RDC_SAUCE_DEMO_ANDROID_KEY", EnvironmentVariableTarget.User); 11 | 12 | public static string SauceDemoIosAppFileName => "iOS.RealDevice.Sample.ipa"; 13 | public string SampleAppAndroidFileName = "Android.SauceLabs.Mobile.Sample.app.2.7.0.apk"; 14 | 15 | public static string SauceDemoIosSimulatorAppFileName = "iOS.Simulator.SauceLabs.Mobile.Sample.app.2.7.0.zip"; 16 | 17 | public string SampleAppAndroidGithubUrl => 18 | "https://github.com/saucelabs/sample-app-mobile/releases/download/2.7.1/Android.SauceLabs.Mobile.Sample.app.2.7.1.apk"; 19 | 20 | public string SampleAppIosGithubUrl => 21 | "https://github.com/saucelabs/sample-app-mobile/releases/download/2.7.1/iOS.RealDevice.SauceLabs.Mobile.Sample.app.2.7.1.ipa"; 22 | } 23 | } 24 | -------------------------------------------------------------------------------- /SauceExamples/Common/SauceLabs/EmusimAPI.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using OpenQA.Selenium.Remote; 3 | using RestSharp; 4 | using RestSharp.Authenticators; 5 | 6 | namespace Common.SauceLabs 7 | { 8 | public class EmusimAPI 9 | { 10 | public void UpdateTestStatus(bool isTestPassed, SessionId sessionId) 11 | { 12 | //API Docs: https://wiki.saucelabs.com/display/DOCS/Job+Methods#JobMethods-UpdateJob 13 | 14 | var client = new RestClient 15 | { 16 | BaseUrl = new Uri("https://saucelabs.com/rest/v1"), 17 | Authenticator = new HttpBasicAuthenticator(SauceUser.Name, SauceUser.AccessKey) 18 | }; 19 | var request = new RestRequest($"/{SauceUser.Name}/jobs/{sessionId}", Method.PUT) 20 | { 21 | RequestFormat = DataFormat.Json 22 | }; 23 | request.AddJsonBody(new { passed = isTestPassed }); 24 | var response = client.Execute(request); 25 | } 26 | } 27 | } -------------------------------------------------------------------------------- /SauceExamples/Common/SauceLabs/Rdc.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Diagnostics; 3 | using OpenQA.Selenium.Remote; 4 | using RestSharp; 5 | 6 | namespace Common.SauceLabs 7 | { 8 | public class Rdc 9 | { 10 | public void UpdateTestStatus(bool isTestPassed, SessionId sessionId) 11 | { 12 | //API Docs for Legacy RDC: https://api.testobject.com/ 13 | 14 | var client = new RestClient 15 | { 16 | BaseUrl = new Uri("https://app.testobject.com/api/rest") 17 | }; 18 | var request = new RestRequest($"/v2/appium/session/{sessionId}/test", Method.PUT); 19 | var body = new { passed = isTestPassed }; 20 | Trace.WriteLine("isPassed=>" + isTestPassed); 21 | request.AddJsonBody(body); 22 | var response = client.Execute(request); 23 | } 24 | } 25 | } -------------------------------------------------------------------------------- /SauceExamples/Common/SauceLabs/SauceJavaScriptExecutor.cs: -------------------------------------------------------------------------------- 1 | using OpenQA.Selenium; 2 | 3 | namespace Common.SauceLabs 4 | { 5 | public class SauceJavaScriptExecutor 6 | { 7 | private IWebDriver _driver; 8 | 9 | public SauceJavaScriptExecutor(IWebDriver driver) 10 | { 11 | _driver = driver; 12 | } 13 | public void LogTestStatus(bool isPassed) 14 | { 15 | var script = "sauce:job-result=" + (isPassed ? "passed" : "failed"); 16 | TryToExecuteScript(script); 17 | } 18 | 19 | private void TryToExecuteScript(string script) 20 | { 21 | try 22 | { 23 | ((IJavaScriptExecutor)_driver).ExecuteScript(script); 24 | } 25 | catch (System.Exception) 26 | { 27 | //This is a poor practice to catch a generic Exception 28 | //However, I temporarily did this for headless implementation 29 | } 30 | } 31 | 32 | public void LogMessage(string message) 33 | { 34 | TryToExecuteScript($"sauce:context={message}"); 35 | } 36 | 37 | public void SetTestName(string testName) 38 | { 39 | TryToExecuteScript($"sauce:job-name={testName}"); 40 | } 41 | 42 | public void SetBuildName(string buildName) 43 | { 44 | TryToExecuteScript($"sauce:job-build={buildName}"); 45 | } 46 | } 47 | } 48 | -------------------------------------------------------------------------------- /SauceExamples/Common/SauceLabs/SauceLabsCapabilities.cs: -------------------------------------------------------------------------------- 1 | using System.Collections.Generic; 2 | 3 | namespace Common.SauceLabs 4 | { 5 | public class SauceLabsCapabilities 6 | { 7 | private List _tags; 8 | 9 | public bool IsUsingSauceLabs { get; } 10 | 11 | public SauceLabsCapabilities() 12 | { 13 | _tags = new List(); 14 | IsUsingSauceLabs = true; 15 | } 16 | 17 | public bool IsDebuggingEnabled { get; set; } 18 | 19 | public List Tags 20 | { 21 | get => _tags; 22 | set => _tags = value; 23 | } 24 | 25 | public static string BuildName { get; set; } 26 | public static string TunnelIdentifier => "tunnelIdentifier"; 27 | 28 | public bool IsHeadless { get; set; } 29 | } 30 | } -------------------------------------------------------------------------------- /SauceExamples/Common/SauceLabs/SauceLabsEndpoint.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | 3 | namespace Common.SauceLabs 4 | { 5 | public class SauceLabsEndpoint 6 | { 7 | private static string EmusimDomain = "@ondemand.saucelabs.com:443" + "/wd/hub"; 8 | public static string SauceUsWestDomain = "ondemand.us-west-1.saucelabs.com/wd/hub"; 9 | public string SauceHubUrl => "https://ondemand.saucelabs.com/wd/hub"; 10 | public Uri SauceHubUri => new Uri(SauceHubUrl); 11 | public Uri UsWestHubUri => new Uri($"https://{SauceUsWestDomain}"); 12 | 13 | public string HeadlessSeleniumUrl => "https://ondemand.us-east-1.saucelabs.com/wd/hub"; 14 | 15 | public string HeadlessRestApiUrl => "https://us-east-1.saucelabs.com/rest/v1"; 16 | 17 | public Uri EmusimUri(string sauceUser, string sauceKey) => 18 | new Uri($"https://{sauceUser}:{sauceKey}{EmusimDomain}"); 19 | } 20 | } -------------------------------------------------------------------------------- /SauceExamples/Common/SauceLabs/SauceUser.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | 3 | namespace Common.SauceLabs 4 | { 5 | public class SauceUser 6 | { 7 | public static string Name => 8 | Environment.GetEnvironmentVariable("SAUCE_USERNAME", EnvironmentVariableTarget.User); 9 | 10 | public static string AccessKey => 11 | Environment.GetEnvironmentVariable("SAUCE_ACCESS_KEY", EnvironmentVariableTarget.User); 12 | } 13 | } 14 | -------------------------------------------------------------------------------- /SauceExamples/Common/SauceLabs/SimpleSauce.cs: -------------------------------------------------------------------------------- 1 | namespace Common.SauceLabs 2 | { 3 | public class SimpleSauce 4 | { 5 | public Rdc Rdc => new Rdc(); 6 | public EmusimAPI EmuSim => new EmusimAPI(); 7 | } 8 | } -------------------------------------------------------------------------------- /SauceExamples/Common/TestData/DeviceCombinations.cs: -------------------------------------------------------------------------------- 1 | using System.Collections.Generic; 2 | 3 | namespace Common.TestData 4 | { 5 | public class DeviceCombinations 6 | { 7 | public static IEnumerable PopularIosDevices => new[] 8 | { 9 | new object[] { "iPhone 11.*" }, 10 | new object[] { "iPhone 12.*" }, 11 | new object[] { "iPhone.*" }, 12 | }; 13 | 14 | public static IEnumerable PopularAndroidDevices => new[] 15 | { 16 | new object[] { "Google Pixel.*", ""}, 17 | new object[] { "Samsung Galaxy.*", ""}, 18 | new object[] { "HTC.*", ""}, 19 | }; 20 | } 21 | } -------------------------------------------------------------------------------- /SauceExamples/Common/Wait.cs: -------------------------------------------------------------------------------- 1 | using OpenQA.Selenium; 2 | using OpenQA.Selenium.Support.UI; 3 | using System; 4 | using ExpectedConditions = SeleniumExtras.WaitHelpers.ExpectedConditions; 5 | 6 | namespace Common 7 | { 8 | public class Wait 9 | { 10 | private IWebDriver _driver; 11 | private WebDriverWait _wait { get; set; } 12 | 13 | private readonly By _locator; 14 | 15 | public Wait(IWebDriver driver) 16 | { 17 | _driver = driver; 18 | _wait = new WebDriverWait(_driver, TimeSpan.FromSeconds(5)); 19 | } 20 | 21 | public IWebElement UntilIsVisible(By locator) 22 | { 23 | return _wait.Until(ExpectedConditions.ElementIsVisible(locator)); 24 | } 25 | public IWebElement UntilIsVisibleByClass(string className) 26 | { 27 | return UntilIsVisible(By.ClassName(className)); 28 | } 29 | public Wait(IWebDriver driver, By locator) 30 | { 31 | _driver = driver; 32 | _locator = locator; 33 | _wait = new WebDriverWait(_driver, TimeSpan.FromSeconds(5)); 34 | } 35 | 36 | public Wait(IWebDriver driver, int timeoutInSeconds) 37 | { 38 | _driver = driver; 39 | _wait = new WebDriverWait(_driver, TimeSpan.FromSeconds(timeoutInSeconds)); 40 | } 41 | 42 | public bool IsVisible() 43 | { 44 | return _wait.Until(ExpectedConditions.ElementIsVisible(_locator)).Displayed; 45 | } 46 | 47 | public IWebElement UntilIsVisibleById(string id) 48 | { 49 | return UntilIsVisible(By.Id(id)); 50 | } 51 | //This type of method is really useful for negative assertions 52 | //To wait until something isn't displayed and then assert on it 53 | public bool UntilIsDisplayedById(string id) 54 | { 55 | bool isDisplayed; 56 | try 57 | { 58 | isDisplayed = UntilIsVisible(By.Id(id)).Displayed; 59 | } 60 | catch (WebDriverTimeoutException) 61 | { 62 | return false; 63 | } 64 | return isDisplayed; 65 | } 66 | 67 | public IWebElement UntilIsVisibleByCss(string css) 68 | { 69 | return UntilIsVisible(By.CssSelector(css)); 70 | } 71 | } 72 | } 73 | -------------------------------------------------------------------------------- /SauceExamples/Common/app.config: -------------------------------------------------------------------------------- 1 |  2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | -------------------------------------------------------------------------------- /SauceExamples/Common/packages.config: -------------------------------------------------------------------------------- 1 |  2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | -------------------------------------------------------------------------------- /SauceExamples/Core.Appium.MsTest.BestPractices/AssemblyInfo.cs: -------------------------------------------------------------------------------- 1 | using NUnit.Framework; 2 | 3 | //Docs on NUnit parallelization: https://github.com/nunit/docs/wiki/Framework-Parallel-Test-Execution 4 | [assembly: Parallelizable(ParallelScope.Fixtures)] 5 | //Set this value to the Maximum amount of VMs that you have in Sauce Labs 6 | [assembly: LevelOfParallelism(100)] 7 | 8 | namespace Core.Appium.Nunit.BestPractices 9 | { 10 | internal class AssemblyInfo 11 | { 12 | } 13 | } -------------------------------------------------------------------------------- /SauceExamples/Core.Appium.MsTest.BestPractices/Core.Appium.Nunit.BestPractices.csproj: -------------------------------------------------------------------------------- 1 |  2 | 3 | 4 | netcoreapp3.1 5 | 6 | false 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | 23 | 24 | -------------------------------------------------------------------------------- /SauceExamples/Core.Appium.MsTest.BestPractices/README.md: -------------------------------------------------------------------------------- 1 | # Automation Best Practices for Mobile 2 | 3 | This is a repository that shows how to do mobile automation best practices on Sauce Labs. 4 | The code and frameworks that you see here are what Sauce believes are the industry-best 5 | solutions. 6 | 7 | ## Run the tests 8 | 9 | * Clone the code 10 | 11 | ```bash 12 | cd demo-csharp/SauceExamples/Core.Appium.MsTest.BestPractices 13 | dotnet test --verbosity normal 14 | ``` 15 | 16 | * Output will look something like this 17 | ``` 18 | Determining projects to restore... 19 | Restored C:\Source\SauceLabs\demo-csharp\SauceExamples\Core.Appium.MsTest.BestPractices\Core.Appium.Nunit.BestPractices.csproj (in 405 ms). 20 | You are using a preview version of .NET. See: https://aka.ms/dotnet-core-preview 21 | C:\Source\SauceLabs\demo-csharp\SauceExamples\Common\WebDriverFactory.cs(83,70): warning CS0618: 'DesiredCapabilities' is obsolete: 'Use of DesiredCapabilities has been deprecated in favor of browser-specific Options classes' [C:\Source\SauceLabs\demo-csharp\SauceExamples\Common\Common.csproj] 22 | ``` 23 | -------------------------------------------------------------------------------- /SauceExamples/Core.Appium.MsTest.BestPractices/Screens/Android/BaseAndroidScreen.cs: -------------------------------------------------------------------------------- 1 | using Common; 2 | using OpenQA.Selenium.Appium.Android; 3 | 4 | namespace Core.Appium.Nunit.BestPractices.Screens.Android 5 | { 6 | public class BaseAndroidScreen 7 | { 8 | protected BaseAndroidScreen(AndroidDriver driver) 9 | { 10 | Driver = driver; 11 | WaitFor = new Wait(Driver); 12 | } 13 | 14 | public Wait WaitFor { get; set; } 15 | 16 | public AndroidDriver Driver { get; set; } 17 | } 18 | } -------------------------------------------------------------------------------- /SauceExamples/Core.Appium.MsTest.BestPractices/Screens/Android/LoginScreen.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using OpenQA.Selenium.Appium; 3 | using OpenQA.Selenium.Appium.Android; 4 | 5 | namespace Core.Appium.Nunit.BestPractices.Screens.Android 6 | { 7 | public class LoginScreen : BaseAndroidScreen 8 | { 9 | public LoginScreen(AndroidDriver driver) : base(driver) 10 | { 11 | } 12 | 13 | public Action IsVisible() 14 | { 15 | return IsUsernameFieldVisible; 16 | } 17 | 18 | private void IsUsernameFieldVisible() 19 | { 20 | WaitFor.UntilIsVisible( 21 | MobileBy.AccessibilityId("test-Username")); 22 | } 23 | 24 | public void Login(string username, string password) 25 | { 26 | var userName = WaitFor.UntilIsVisible( 27 | MobileBy.AccessibilityId("test-Username")); 28 | userName.SendKeys(username); 29 | 30 | var passwordField = WaitFor.UntilIsVisible( 31 | MobileBy.AccessibilityId("test-Password")); 32 | passwordField.SendKeys(password); 33 | 34 | var login = WaitFor.UntilIsVisible( 35 | MobileBy.AccessibilityId("test-LOGIN")); 36 | login.Click(); 37 | } 38 | } 39 | } -------------------------------------------------------------------------------- /SauceExamples/Core.Appium.MsTest.BestPractices/Screens/Android/ProductsScreen.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using OpenQA.Selenium; 3 | using OpenQA.Selenium.Appium.Android; 4 | 5 | namespace Core.Appium.Nunit.BestPractices.Screens.Android 6 | { 7 | public class ProductsScreen : BaseAndroidScreen 8 | { 9 | public ProductsScreen(AndroidDriver driver) : base(driver) 10 | { 11 | } 12 | 13 | public Action IsVisible() 14 | { 15 | return IsCartElementVisible; 16 | } 17 | 18 | private void IsCartElementVisible() 19 | { 20 | WaitFor.UntilIsVisible(By.XPath("//android.view.ViewGroup[@content-desc='test-Cart']")); 21 | } 22 | } 23 | } -------------------------------------------------------------------------------- /SauceExamples/Core.Appium.MsTest.BestPractices/Screens/iOS/BaseIosScreen.cs: -------------------------------------------------------------------------------- 1 | using Common; 2 | using OpenQA.Selenium.Appium.iOS; 3 | 4 | namespace Core.Appium.Nunit.BestPractices.Screens.iOS 5 | { 6 | public class BaseIosScreen 7 | { 8 | protected BaseIosScreen(IOSDriver driver) 9 | { 10 | Driver = driver; 11 | WaitFor = new Wait(Driver); 12 | } 13 | 14 | public Wait WaitFor { get; set; } 15 | 16 | public IOSDriver Driver { get; set; } 17 | } 18 | } -------------------------------------------------------------------------------- /SauceExamples/Core.Appium.MsTest.BestPractices/Screens/iOS/LoginScreen.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using OpenQA.Selenium.Appium; 3 | using OpenQA.Selenium.Appium.iOS; 4 | 5 | namespace Core.Appium.Nunit.BestPractices.Screens.iOS 6 | { 7 | public class LoginScreen : BaseIosScreen 8 | { 9 | public LoginScreen(IOSDriver driver) : base(driver) 10 | { 11 | } 12 | 13 | public Action IsVisible() 14 | { 15 | return IsUsernameFieldVisible; 16 | } 17 | 18 | private void IsUsernameFieldVisible() 19 | { 20 | WaitFor.UntilIsVisible( 21 | MobileBy.AccessibilityId("test-Username")); 22 | } 23 | 24 | public void Login(string username, string password) 25 | { 26 | var userName = WaitFor.UntilIsVisible( 27 | MobileBy.AccessibilityId("test-Username")); 28 | userName.SendKeys(username); 29 | 30 | var passwordField = WaitFor.UntilIsVisible( 31 | MobileBy.AccessibilityId("test-Password")); 32 | passwordField.SendKeys(password); 33 | 34 | var login = WaitFor.UntilIsVisible( 35 | MobileBy.AccessibilityId("test-LOGIN")); 36 | login.Click(); 37 | } 38 | } 39 | } -------------------------------------------------------------------------------- /SauceExamples/Core.Appium.MsTest.BestPractices/Screens/iOS/ProductsScreen.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using OpenQA.Selenium; 3 | using OpenQA.Selenium.Appium.iOS; 4 | 5 | namespace Core.Appium.Nunit.BestPractices.Screens.iOS 6 | { 7 | public class ProductsScreen : BaseIosScreen 8 | { 9 | public ProductsScreen(IOSDriver driver) : base(driver) 10 | { 11 | } 12 | 13 | public Action IsVisible() 14 | { 15 | return IsCartElementVisible; 16 | } 17 | 18 | private void IsCartElementVisible() 19 | { 20 | WaitFor.UntilIsVisible(By.Name("test-Cart")); 21 | } 22 | } 23 | } -------------------------------------------------------------------------------- /SauceExamples/Core.Appium.MsTest.BestPractices/Tests/AndroidFeatures.cs: -------------------------------------------------------------------------------- 1 | using Common.TestData; 2 | using Core.Appium.Nunit.BestPractices.Screens.Android; 3 | using FluentAssertions; 4 | using NUnit.Framework; 5 | 6 | namespace Core.Appium.Nunit.BestPractices.Tests 7 | { 8 | [TestFixtureSource(typeof(DeviceCombinations), nameof(DeviceCombinations.PopularAndroidDevices))] 9 | [Parallelizable] 10 | public class AndroidFeatures : AndroidTest 11 | { 12 | public AndroidFeatures(string deviceName, string deviceVersion) : base(deviceName, deviceVersion) 13 | { 14 | } 15 | 16 | [Test] 17 | public void ShouldOpenApp() 18 | { 19 | var loginScreen = new LoginScreen(Driver); 20 | loginScreen.IsVisible().Should().NotThrow(); 21 | } 22 | 23 | [Test] 24 | public void ShouldLogin() 25 | { 26 | var loginScreen = new LoginScreen(Driver); 27 | loginScreen.Login("standard_user", "secret_sauce"); 28 | 29 | new ProductsScreen(Driver).IsVisible().Should().NotThrow(); 30 | } 31 | } 32 | } -------------------------------------------------------------------------------- /SauceExamples/Core.Appium.MsTest.BestPractices/Tests/BaseNativeAppTest.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | 3 | namespace Core.Appium.Nunit.BestPractices.Tests 4 | { 5 | public class BaseNativeAppTest 6 | { 7 | public static string HubUrlPart => "ondemand.us-west-1.saucelabs.com/wd/hub"; 8 | public string SauceUser => Environment.GetEnvironmentVariable("SAUCE_USERNAME"); 9 | 10 | public string SauceAccessKey => 11 | Environment.GetEnvironmentVariable("SAUCE_ACCESS_KEY"); 12 | 13 | public string Url => $"https://{SauceUser}:{SauceAccessKey}@{HubUrlPart}"; 14 | } 15 | } -------------------------------------------------------------------------------- /SauceExamples/Core.Appium.MsTest.BestPractices/Tests/IOSFeatures.cs: -------------------------------------------------------------------------------- 1 | using Common.TestData; 2 | using Core.Appium.Nunit.BestPractices.Screens.iOS; 3 | using FluentAssertions; 4 | using NUnit.Framework; 5 | 6 | namespace Core.Appium.Nunit.BestPractices.Tests 7 | { 8 | [TestFixtureSource(typeof(DeviceCombinations), nameof(DeviceCombinations.PopularIosDevices))] 9 | [Parallelizable] 10 | public class IosFeatures : IosTest 11 | { 12 | public IosFeatures(string deviceName) : base(deviceName) 13 | { 14 | } 15 | 16 | [Test] 17 | [Retry(1)] 18 | public void ShouldOpenApp() 19 | { 20 | var loginScreen = new LoginScreen(Driver); 21 | loginScreen.IsVisible().Should().NotThrow(); 22 | } 23 | 24 | [Test] 25 | [Retry(1)] 26 | public void ShouldLogin() 27 | { 28 | var loginScreen = new LoginScreen(Driver); 29 | loginScreen.Login("standard_user", "secret_sauce"); 30 | 31 | new ProductsScreen(Driver).IsVisible().Should().NotThrow(); 32 | } 33 | } 34 | } -------------------------------------------------------------------------------- /SauceExamples/Core.Appium.MsTest.BestPractices/Tests/IOSTest.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using Common.SauceLabs; 3 | using NUnit.Framework; 4 | using NUnit.Framework.Interfaces; 5 | using OpenQA.Selenium; 6 | using OpenQA.Selenium.Appium; 7 | using OpenQA.Selenium.Appium.Enums; 8 | using OpenQA.Selenium.Appium.iOS; 9 | 10 | namespace Core.Appium.Nunit.BestPractices.Tests 11 | { 12 | public class IosTest : BaseNativeAppTest 13 | { 14 | private readonly string _deviceName; 15 | public IOSDriver Driver; 16 | 17 | public IosTest(string deviceName) 18 | { 19 | _deviceName = deviceName; 20 | } 21 | 22 | [SetUp] 23 | public void Setup() 24 | { 25 | var appiumCaps = new AppiumOptions(); 26 | appiumCaps.AddAdditionalCapability(MobileCapabilityType.DeviceName, _deviceName); 27 | 28 | appiumCaps.AddAdditionalCapability(MobileCapabilityType.PlatformName, "iOS"); 29 | appiumCaps.AddAdditionalCapability("newCommandTimeout", 90); 30 | appiumCaps.AddAdditionalCapability("name", TestContext.CurrentContext.Test.Name); 31 | /* 32 | * You need to upload your own Native Mobile App to Sauce Storage! 33 | * https://wiki.saucelabs.com/display/DOCS/Uploading+your+Application+to+Sauce+Storage 34 | * You can use either storage: or storage:filename= 35 | */ 36 | appiumCaps.AddAdditionalCapability("app", new ApiKeys().Rdc.Apps.SampleAppIosGithubUrl); 37 | Driver = new IOSDriver(new Uri(Url), appiumCaps, TimeSpan.FromSeconds(180)); 38 | } 39 | 40 | [TearDown] 41 | public void Teardown() 42 | { 43 | if (Driver == null) return; 44 | 45 | var isTestPassed = TestContext.CurrentContext.Result.Outcome.Status == TestStatus.Passed; 46 | ((IJavaScriptExecutor) Driver).ExecuteScript("sauce:job-result=" + (isTestPassed ? "passed" : "failed")); 47 | Driver.Quit(); 48 | } 49 | } 50 | } -------------------------------------------------------------------------------- /SauceExamples/Core.Appium.MsTest.Scripts/Core.Appium.Examples.csproj: -------------------------------------------------------------------------------- 1 |  2 | 3 | 4 | net5.0 5 | 6 | false 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | all 18 | runtime; build; native; contentfiles; analyzers; buildtransitive 19 | 20 | 21 | 22 | 23 | 24 | 25 | 26 | 27 | 28 | -------------------------------------------------------------------------------- /SauceExamples/Core.Appium.MsTest.Scripts/Emusim/Browser/AndroidWebTests.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using Common.SauceLabs; 3 | using Microsoft.VisualStudio.TestTools.UnitTesting; 4 | using OpenQA.Selenium; 5 | using OpenQA.Selenium.Appium; 6 | using OpenQA.Selenium.Appium.Enums; 7 | using OpenQA.Selenium.Remote; 8 | 9 | namespace Core.Appium.Examples.Emusim.Browser 10 | { 11 | [TestClass] 12 | public class AndroidWebTests 13 | { 14 | private RemoteWebDriver _driver; 15 | private string _sauceUserName; 16 | private string _sauceAccessKey; 17 | public TestContext TestContext { get; set; } 18 | 19 | [TestInitialize] 20 | public void Setup() 21 | { 22 | _sauceUserName = Environment.GetEnvironmentVariable("SAUCE_USERNAME", EnvironmentVariableTarget.User); 23 | _sauceAccessKey = Environment.GetEnvironmentVariable("SAUCE_ACCESS_KEY", EnvironmentVariableTarget.User); 24 | 25 | var appiumOptions = new AppiumOptions(); 26 | appiumOptions.AddAdditionalCapability(MobileCapabilityType.DeviceName, "Samsung Galaxy S9 WQHD GoogleAPI Emulator"); 27 | appiumOptions.AddAdditionalCapability(MobileCapabilityType.PlatformName, "Android"); 28 | appiumOptions.AddAdditionalCapability(MobileCapabilityType.PlatformVersion, "9.0"); 29 | appiumOptions.AddAdditionalCapability(MobileCapabilityType.BrowserName, "Chrome"); 30 | appiumOptions.AddAdditionalCapability(MobileCapabilityType.AppiumVersion, "1.9.1"); 31 | appiumOptions.AddAdditionalCapability("name", TestContext.TestName); 32 | 33 | _driver = new RemoteWebDriver(new SauceLabsEndpoint().EmusimUri(_sauceUserName, _sauceAccessKey), 34 | appiumOptions.ToCapabilities(), TimeSpan.FromSeconds(120)); 35 | } 36 | [TestCleanup] 37 | public void Teardown() 38 | { 39 | if (_driver == null) return; 40 | 41 | var isPassed = TestContext.CurrentTestOutcome == UnitTestOutcome.Passed; 42 | ((IJavaScriptExecutor)_driver).ExecuteScript("sauce:job-result=" + (isPassed ? "passed" : "failed")); 43 | _driver.Quit(); 44 | } 45 | 46 | [TestMethod] 47 | public void ShouldOpenBrowser() 48 | { 49 | _driver.Navigate().GoToUrl("https://www.saucedemo.com"); 50 | Assert.AreNotEqual(0, _driver.Manage().Window.Size); 51 | } 52 | } 53 | } 54 | -------------------------------------------------------------------------------- /SauceExamples/Core.Appium.MsTest.Scripts/RealDevices/Browser/LegacyRdc/AndroidGetStarted.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using Common.SauceLabs; 3 | using Microsoft.VisualStudio.TestTools.UnitTesting; 4 | using OpenQA.Selenium.Appium; 5 | using OpenQA.Selenium.Appium.Android; 6 | using OpenQA.Selenium.Appium.Enums; 7 | 8 | [assembly: Parallelize(Workers = 100, Scope = ExecutionScope.MethodLevel)] 9 | 10 | namespace Core.Appium.Examples.RealDevices.Browser.LegacyRdc 11 | { 12 | [TestClass] 13 | public class AndroidGetStarted 14 | { 15 | private static string RdcUsHubUrl => "https://us1.appium.testobject.com/wd/hub"; 16 | private AndroidDriver _driver; 17 | 18 | [TestMethod] 19 | public void ShouldOpenNativeAndroidApp() 20 | { 21 | var capabilities = new AppiumOptions(); 22 | //We can run on any version of the platform as long as it's the correct device 23 | //Make sure to pick an Android or iOS device based on your app 24 | capabilities.AddAdditionalCapability(MobileCapabilityType.DeviceName, "Google Pixel 4"); 25 | capabilities.AddAdditionalCapability(MobileCapabilityType.PlatformName, "Android"); 26 | 27 | /* 28 | * !!!!!! 29 | * TODO first you must upload an app to RDC so that you get your app key 30 | * Then, make sure you can hardcode it here just to get started 31 | */ 32 | capabilities.AddAdditionalCapability("testobject_api_key", new ApiKeys().Rdc.Apps.SampleAppAndroid); 33 | capabilities.AddAdditionalCapability("newCommandTimeout", 90); 34 | 35 | //60 seconds for the connection timeout 36 | _driver = new AndroidDriver(new Uri(RdcUsHubUrl), capabilities); 37 | var size = _driver.Manage().Window.Size; 38 | Assert.AreNotEqual(0, size.Height); 39 | 40 | //Always making sure to end the session at the end of any test 41 | _driver?.Quit(); 42 | } 43 | } 44 | } 45 | -------------------------------------------------------------------------------- /SauceExamples/DotnetCore/Core.Selenium4.MsTest.Scripts/Core.Selenium4.MsTest.Scripts.csproj: -------------------------------------------------------------------------------- 1 |  2 | 3 | 4 | netcoreapp3.1 5 | 6 | false 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | 23 | 24 | 25 | 26 | Test3.feature 27 | 28 | 29 | 30 | 31 | 32 | $(UsingMicrosoftNETSdk) 33 | %(RelativeDir)%(Filename).feature$(DefaultLanguageSourceExtension) 34 | 35 | 36 | 37 | 38 | -------------------------------------------------------------------------------- /SauceExamples/DotnetCore/Core.Selenium4.MsTest.Scripts/SanityTest.cs: -------------------------------------------------------------------------------- 1 | using Microsoft.VisualStudio.TestTools.UnitTesting; 2 | 3 | namespace Core.Selenium4.MsTest.Scripts 4 | { 5 | [TestClass] 6 | public class SanityTest 7 | { 8 | [TestMethod] 9 | public void TestMethod1() 10 | { 11 | Assert.IsTrue(true); 12 | } 13 | } 14 | } 15 | -------------------------------------------------------------------------------- /SauceExamples/DotnetCore/Core.Selenium4.MsTest.Scripts/SpecFlow/Features/Test1.feature: -------------------------------------------------------------------------------- 1 | Feature: Can run in parallel 2 | 3 | Scenario: Page visible when opened 4 | When a user navigates to Sauce Demo 5 | Then a user sees the Sauce Demo login page 6 | 7 | -------------------------------------------------------------------------------- /SauceExamples/DotnetCore/Core.Selenium4.MsTest.Scripts/SpecFlow/Features/Test2.feature: -------------------------------------------------------------------------------- 1 | Feature: Can run in parallel 2 2 | 3 | Scenario: Page visible when opened 2 4 | When a user navigates to Sauce Demo 5 | Then a user sees the Sauce Demo login page -------------------------------------------------------------------------------- /SauceExamples/DotnetCore/Core.Selenium4.MsTest.Scripts/SpecFlow/Features/Test3.feature: -------------------------------------------------------------------------------- 1 | Feature: Can run in parallel and has multiple scenarios 2 | 3 | Scenario: Page visible when opened 3 4 | When a user navigates to Sauce Demo 5 | Then a user sees the Sauce Demo login page 6 | 7 | Scenario: Page visible when opened 4 8 | When a user navigates to Sauce Demo 9 | Then a user sees the Sauce Demo login page 10 | 11 | Scenario: Page visible when opened 5 12 | When a user navigates to Sauce Demo 13 | Then a user sees the Sauce Demo login page -------------------------------------------------------------------------------- /SauceExamples/DotnetCore/Core.Selenium4.MsTest.Scripts/SpecFlow/Hooks/DriverSetup.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Collections.Generic; 3 | using BoDi; 4 | using OpenQA.Selenium; 5 | using OpenQA.Selenium.Chrome; 6 | using OpenQA.Selenium.Remote; 7 | using TechTalk.SpecFlow; 8 | 9 | namespace Core.Selenium4.MsTest.Scripts.SpecFlow.Hooks 10 | { 11 | [Binding] 12 | public class DriverSetup 13 | { 14 | private IObjectContainer _objectContainer; 15 | public IWebDriver Driver; 16 | 17 | public DriverSetup(IObjectContainer objectContainer) 18 | { 19 | _objectContainer = objectContainer; 20 | 21 | } 22 | 23 | [BeforeScenario] 24 | public void BeforeScenario() 25 | { 26 | //TODO please supply your Sauce Labs user name in an environment variable 27 | var sauceUserName = Environment.GetEnvironmentVariable("SAUCE_USERNAME", EnvironmentVariableTarget.User); 28 | //TODO please supply your own Sauce Labs access Key in an environment variable 29 | var sauceAccessKey = Environment.GetEnvironmentVariable("SAUCE_ACCESS_KEY", EnvironmentVariableTarget.User); 30 | var sauceOptions = new Dictionary 31 | { 32 | ["username"] = sauceUserName, 33 | ["accessKey"] = sauceAccessKey 34 | }; 35 | var chromeOptions = new ChromeOptions 36 | { 37 | BrowserVersion = "latest", 38 | PlatformName = "Windows 10" 39 | }; 40 | chromeOptions.AddAdditionalOption("sauce:options", sauceOptions); 41 | 42 | Driver = new RemoteWebDriver(new Uri("https://ondemand.saucelabs.com/wd/hub"), 43 | chromeOptions.ToCapabilities(), TimeSpan.FromSeconds(30)); 44 | _objectContainer.RegisterInstanceAs(Driver); 45 | } 46 | 47 | } 48 | } 49 | -------------------------------------------------------------------------------- /SauceExamples/DotnetCore/Core.Selenium4.MsTest.Scripts/SpecFlow/Hooks/Hooks.cs: -------------------------------------------------------------------------------- 1 | using OpenQA.Selenium; 2 | using TechTalk.SpecFlow; 3 | 4 | namespace Core.Selenium4.MsTest.Scripts.SpecFlow.Hooks 5 | { 6 | [Binding] 7 | public class Hooks 8 | { 9 | public IWebDriver Driver { get; } 10 | private readonly ScenarioContext _scenarioContext; 11 | 12 | public Hooks(IWebDriver driver, ScenarioContext scenarioContext) 13 | { 14 | Driver = driver; 15 | _scenarioContext = scenarioContext; 16 | } 17 | 18 | [AfterScenario] 19 | public void Teardown() 20 | { 21 | if (Driver == null){ return; } 22 | 23 | var passed = _scenarioContext.ScenarioExecutionStatus == ScenarioExecutionStatus.OK; 24 | ((IJavaScriptExecutor)Driver).ExecuteScript("sauce:job-result=" + (passed ? "passed" : "failed")); 25 | Driver.Quit(); 26 | } 27 | 28 | } 29 | } 30 | -------------------------------------------------------------------------------- /SauceExamples/DotnetCore/Core.Selenium4.MsTest.Scripts/SpecFlow/StepDefinitions/ParallelSteps.cs: -------------------------------------------------------------------------------- 1 | using Microsoft.VisualStudio.TestTools.UnitTesting; 2 | using OpenQA.Selenium; 3 | using TechTalk.SpecFlow; 4 | //SpecFlow nor any BDD framework car parallelize on methods, so keep it at ClassLevel 5 | [assembly: Parallelize(Workers = 100, Scope = ExecutionScope.ClassLevel)] 6 | 7 | namespace Core.Selenium4.MsTest.Scripts.SpecFlow.StepDefinitions 8 | { 9 | [Binding] 10 | public class ParallelSteps 11 | { 12 | private readonly IWebDriver _driver; 13 | 14 | public ParallelSteps(IWebDriver driver) 15 | { 16 | _driver = driver; 17 | } 18 | 19 | [When(@"a user navigates to Sauce Demo")] 20 | public void WhenAUserNavigatesToSauceDemo() 21 | { 22 | _driver.Navigate().GoToUrl("https://www.saucedemo.com"); 23 | } 24 | 25 | [Then(@"a user sees the Sauce Demo login page")] 26 | public void ThenAUserSeesTheSauceDemoLoginPage() 27 | { 28 | Assert.IsTrue(_driver.Url.Contains("saucedemo.com")); 29 | } 30 | } 31 | } 32 | -------------------------------------------------------------------------------- /SauceExamples/DotnetCore/Core.Selenium4.MsTest.Scripts/SpecFlow/specflow1.json: -------------------------------------------------------------------------------- 1 | { 2 | "bindingCulture": 3 | { 4 | "language" :"en-us" 5 | }, 6 | "language": 7 | { 8 | "feature": "en-us" 9 | } 10 | } 11 | -------------------------------------------------------------------------------- /SauceExamples/DotnetFramework/Appium/Appium4.MsTest.Scripts/Properties/AssemblyInfo.cs: -------------------------------------------------------------------------------- 1 | using Microsoft.VisualStudio.TestTools.UnitTesting; 2 | using System.Reflection; 3 | using System.Runtime.InteropServices; 4 | using ExecutionScope = Microsoft.VisualStudio.TestTools.UnitTesting.ExecutionScope; 5 | 6 | [assembly: AssemblyTitle("Appium4.MsTest.Scripts")] 7 | [assembly: AssemblyDescription("")] 8 | [assembly: AssemblyConfiguration("")] 9 | [assembly: AssemblyCompany("")] 10 | [assembly: AssemblyProduct("Appium4.MsTest.Scripts")] 11 | [assembly: AssemblyCopyright("Copyright © 2020")] 12 | [assembly: AssemblyTrademark("")] 13 | [assembly: AssemblyCulture("")] 14 | 15 | [assembly: ComVisible(false)] 16 | 17 | [assembly: Guid("d72ec113-d49d-48b9-bdf9-bee48241c74e")] 18 | 19 | // [assembly: AssemblyVersion("1.0.*")] 20 | [assembly: AssemblyVersion("1.0.0.0")] 21 | [assembly: AssemblyFileVersion("1.0.0.0")] 22 | [assembly: Parallelize(Workers = 100, Scope = ExecutionScope.MethodLevel)] -------------------------------------------------------------------------------- /SauceExamples/DotnetFramework/Appium/Appium4.MsTest.Scripts/packages.config: -------------------------------------------------------------------------------- 1 |  2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | -------------------------------------------------------------------------------- /SauceExamples/DotnetFramework/Appium/Appium4.NUnit.Scripts/Emusim/Browser/W3CEmusim.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Collections.Generic; 3 | using System.Reflection; 4 | using NUnit.Framework; 5 | using OpenQA.Selenium.Remote; 6 | using OpenQA.Selenium.Safari; 7 | using Assert = NUnit.Framework.Assert; 8 | 9 | namespace Appium4.NUnit.Scripts.Emusim.Browser 10 | { 11 | [TestFixture] 12 | public class W3CEmusim 13 | { 14 | private RemoteWebDriver _driver; 15 | private object _sauceUserName; 16 | private object _sauceAccessKey; 17 | private Dictionary _sauceOptions; 18 | 19 | [SetUp] 20 | public void Setup() 21 | { 22 | _sauceUserName = Environment.GetEnvironmentVariable("SAUCE_USERNAME", EnvironmentVariableTarget.User); 23 | _sauceAccessKey = Environment.GetEnvironmentVariable("SAUCE_ACCESS_KEY", EnvironmentVariableTarget.User); 24 | _sauceOptions = new Dictionary 25 | { 26 | ["username"] = _sauceUserName, 27 | ["accessKey"] = _sauceAccessKey, 28 | ["deviceName"] = "iPhone XS Max Simulator", 29 | ["platformVersion"] = "13.0" 30 | }; 31 | var options = new SafariOptions 32 | { 33 | BrowserVersion = "latest", 34 | PlatformName = "iOS" 35 | }; 36 | _sauceOptions.Add("name", MethodBase.GetCurrentMethod().Name); 37 | options.AddAdditionalCapability("sauce:options", _sauceOptions); 38 | 39 | _driver = new RemoteWebDriver(new Uri("https://ondemand.saucelabs.com/wd/hub"), 40 | options.ToCapabilities(), TimeSpan.FromSeconds(30)); 41 | } 42 | [TearDown] 43 | public void Teardown() 44 | { 45 | _driver?.Quit(); 46 | } 47 | 48 | 49 | [Test] 50 | public void AndroidOnEmusimUsingW3C() 51 | { 52 | GoToThenAssert(); 53 | } 54 | private void GoToThenAssert() 55 | { 56 | _driver.Navigate().GoToUrl("https://www.saucedemo.com"); 57 | Assert.IsTrue(_driver.Url.Contains("saucedemo.com")); 58 | } 59 | } 60 | } 61 | -------------------------------------------------------------------------------- /SauceExamples/DotnetFramework/Appium/Appium4.NUnit.Scripts/Properties/AssemblyInfo.cs: -------------------------------------------------------------------------------- 1 | using NUnit.Framework; 2 | using System.Reflection; 3 | using System.Runtime.InteropServices; 4 | 5 | [assembly: AssemblyTitle("Appium4.NUnit.Scripts")] 6 | [assembly: AssemblyDescription("")] 7 | [assembly: AssemblyConfiguration("")] 8 | [assembly: AssemblyCompany("")] 9 | [assembly: AssemblyProduct("Appium4.NUnit.Scripts")] 10 | [assembly: AssemblyCopyright("Copyright © 2020")] 11 | [assembly: AssemblyTrademark("")] 12 | [assembly: AssemblyCulture("")] 13 | 14 | [assembly: ComVisible(false)] 15 | 16 | [assembly: Guid("ee8b089a-1659-418b-9c41-fc6970df1c8b")] 17 | 18 | // [assembly: AssemblyVersion("1.0.*")] 19 | [assembly: AssemblyVersion("1.0.0.0")] 20 | [assembly: AssemblyFileVersion("1.0.0.0")] 21 | [assembly: LevelOfParallelism(100)] -------------------------------------------------------------------------------- /SauceExamples/DotnetFramework/Appium/Appium4.NUnit.Scripts/RealDevices/NativeApp/LegacyRdc/AndroidGetStarted.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using Common.SauceLabs; 3 | using NUnit.Framework; 4 | using OpenQA.Selenium.Appium; 5 | using OpenQA.Selenium.Appium.Android; 6 | using OpenQA.Selenium.Appium.Enums; 7 | 8 | namespace Appium4.NUnit.Scripts.RealDevices.NativeApp.LegacyRdc 9 | { 10 | [TestFixture] 11 | [Parallelizable] 12 | 13 | public class AndroidGetStarted 14 | { 15 | private static string RdcUsHubUrl => "https://us1.appium.testobject.com/wd/hub"; 16 | private AndroidDriver _driver; 17 | 18 | [Test] 19 | [Category("Android")] 20 | [Category("SimpleTest")] 21 | [Category("Rdc")] 22 | [Category("NativeApp")] 23 | [Category("Appium4NUnitScripts")] 24 | 25 | public void ShouldOpenNativeAndroidApp() 26 | { 27 | var capabilities = new AppiumOptions(); 28 | //We can run on any version of the platform as long as it's the correct device 29 | //Make sure to pick an Android or iOS device based on your app 30 | capabilities.AddAdditionalCapability(MobileCapabilityType.DeviceName, "Google Pixel 4"); 31 | capabilities.AddAdditionalCapability(MobileCapabilityType.PlatformName, "Android"); 32 | 33 | /* 34 | * !!!!!! 35 | * TODO first you must upload an app to RDC so that you get your app key 36 | * Then, make sure you can hardcode it here just to get started 37 | */ 38 | capabilities.AddAdditionalCapability("testobject_api_key", new ApiKeys().Rdc.Apps.SampleAppAndroid); 39 | capabilities.AddAdditionalCapability("name", TestContext.CurrentContext.Test.Name); 40 | capabilities.AddAdditionalCapability("newCommandTimeout", 90); 41 | //TODO it's a best practice to set the appium version so that you're always getting the latest 42 | capabilities.AddAdditionalCapability("appiumVersion", "1.16.0"); 43 | 44 | 45 | //60 seconds for the connection timeout 46 | _driver = new AndroidDriver(new Uri(RdcUsHubUrl), capabilities); 47 | var size = _driver.Manage().Window.Size; 48 | Assert.AreNotEqual(0, size.Height); 49 | 50 | //Always making sure to end the session at the end of any test 51 | _driver?.Quit(); 52 | } 53 | } 54 | } 55 | -------------------------------------------------------------------------------- /SauceExamples/DotnetFramework/Appium/Appium4.NUnit.Scripts/RealDevices/NativeApp/LegacyRdc/iOSGetStarted.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using Common.SauceLabs; 3 | using NUnit.Framework; 4 | using OpenQA.Selenium.Appium; 5 | using OpenQA.Selenium.Appium.Enums; 6 | using OpenQA.Selenium.Appium.iOS; 7 | 8 | namespace Appium4.NUnit.Scripts.RealDevices.NativeApp.LegacyRdc 9 | { 10 | [TestFixture] 11 | [Parallelizable] 12 | public class IOsGetStarted 13 | { 14 | private static string RdcUsHubUrl => "https://us1.appium.testobject.com/wd/hub"; 15 | private IOSDriver _driver; 16 | 17 | [Test] 18 | [Category("Android")] 19 | [Category("SimpleTest")] 20 | [Category("Rdc")] 21 | [Category("NativeApp")] 22 | [Category("Appium4NUnitScripts")] 23 | 24 | public void ShouldOpenNativeIOSApp() 25 | { 26 | var capabilities = new AppiumOptions(); 27 | //We can run on any version of the platform as long as it's the correct device 28 | //Make sure to pick an Android or iOS device based on your app 29 | capabilities.AddAdditionalCapability(MobileCapabilityType.DeviceName, "iPhone.*"); 30 | capabilities.AddAdditionalCapability(MobileCapabilityType.PlatformName, "iOS"); 31 | 32 | /* 33 | * !!!!!! 34 | * TODO first you must upload an app to RDC so that you get your app key 35 | * Then, make sure you can hardcode it here just to get started 36 | */ 37 | capabilities.AddAdditionalCapability("testobject_api_key", new ApiKeys().Rdc.Apps.SampleAppIOS); 38 | capabilities.AddAdditionalCapability("name", TestContext.CurrentContext.Test.Name); 39 | capabilities.AddAdditionalCapability("newCommandTimeout", 90); 40 | //TODO it's a best practice to set the appium version so that you're always getting the latest 41 | capabilities.AddAdditionalCapability("appiumVersion", "1.16.0"); 42 | 43 | 44 | //60 seconds for the connection timeout 45 | _driver = new IOSDriver(new Uri(RdcUsHubUrl), capabilities); 46 | var size = int.Parse(_driver.Manage().Window.Size.Height.ToString()); 47 | Assert.Greater(size, 0); 48 | 49 | //Always making sure to end the session at the end of any test 50 | _driver?.Quit(); 51 | } 52 | } 53 | } 54 | -------------------------------------------------------------------------------- /SauceExamples/DotnetFramework/Appium/Appium4.NUnit.Scripts/packages.config: -------------------------------------------------------------------------------- 1 |  2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | -------------------------------------------------------------------------------- /SauceExamples/DotnetFramework/Appium/SampleApps/Android.SauceLabs.Mobile.Sample.app.2.2.1.apk: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/saucelabs-training/demo-csharp/65768a379c08ecf0aa1d99ef1c466f9c2922e772/SauceExamples/DotnetFramework/Appium/SampleApps/Android.SauceLabs.Mobile.Sample.app.2.2.1.apk -------------------------------------------------------------------------------- /SauceExamples/DotnetFramework/Appium/SampleApps/iOS.RealDevice.SauceLabs.Mobile.Sample.app.2.2.1.ipa: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/saucelabs-training/demo-csharp/65768a379c08ecf0aa1d99ef1c466f9c2922e772/SauceExamples/DotnetFramework/Appium/SampleApps/iOS.RealDevice.SauceLabs.Mobile.Sample.app.2.2.1.ipa -------------------------------------------------------------------------------- /SauceExamples/Selenium4DotNetFramework/Drivers/chromedriver.exe: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/saucelabs-training/demo-csharp/65768a379c08ecf0aa1d99ef1c466f9c2922e772/SauceExamples/Selenium4DotNetFramework/Drivers/chromedriver.exe -------------------------------------------------------------------------------- /SauceExamples/Selenium4DotNetFramework/Properties/AssemblyInfo.cs: -------------------------------------------------------------------------------- 1 | using Microsoft.VisualStudio.TestTools.UnitTesting; 2 | using System.Reflection; 3 | using System.Runtime.InteropServices; 4 | using ExecutionScope = Microsoft.VisualStudio.TestTools.UnitTesting.ExecutionScope; 5 | 6 | [assembly: AssemblyTitle("Selenium4DotNetFramework")] 7 | [assembly: AssemblyDescription("")] 8 | [assembly: AssemblyConfiguration("")] 9 | [assembly: AssemblyCompany("")] 10 | [assembly: AssemblyProduct("Selenium4DotNetFramework")] 11 | [assembly: AssemblyCopyright("Copyright © 2019")] 12 | [assembly: AssemblyTrademark("")] 13 | [assembly: AssemblyCulture("")] 14 | 15 | [assembly: ComVisible(false)] 16 | 17 | [assembly: Guid("0d11c201-3fcf-4836-9b1f-a45f461a7b02")] 18 | 19 | // [assembly: AssemblyVersion("1.0.*")] 20 | [assembly: AssemblyVersion("1.0.0.0")] 21 | [assembly: AssemblyFileVersion("1.0.0.0")] 22 | [assembly: Parallelize(Workers = 100, Scope = ExecutionScope.MethodLevel)] -------------------------------------------------------------------------------- /SauceExamples/Selenium4DotNetFramework/packages.config: -------------------------------------------------------------------------------- 1 |  2 | 3 | 4 | 5 | 6 | -------------------------------------------------------------------------------- /SauceExamples/SeleniumMsTest/ExtendedDebugging.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Collections.Generic; 3 | using System.Reflection; 4 | using Microsoft.VisualStudio.TestTools.UnitTesting; 5 | using OpenQA.Selenium; 6 | using OpenQA.Selenium.Remote; 7 | 8 | namespace Selenium3.MsTest.Scripts 9 | { 10 | [TestClass] 11 | [TestCategory("SimpleTest")] 12 | public class ExtendedDebugging 13 | { 14 | IWebDriver _driver; 15 | public TestContext TestContext { get; set; } 16 | [TestMethod] 17 | [Obsolete] 18 | public void ExtendedDebugTest() 19 | { 20 | var sauceUserName = Environment.GetEnvironmentVariable( 21 | "SAUCE_USERNAME", EnvironmentVariableTarget.User); 22 | var sauceAccessKey = Environment.GetEnvironmentVariable( 23 | "SAUCE_ACCESS_KEY", EnvironmentVariableTarget.User); 24 | 25 | 26 | DesiredCapabilities caps = new DesiredCapabilities(); 27 | caps.SetCapability("browserName", "Chrome"); 28 | caps.SetCapability("platform", "Windows 10"); 29 | caps.SetCapability("version", "latest"); 30 | caps.SetCapability("username", sauceUserName); 31 | caps.SetCapability("accessKey", sauceAccessKey); 32 | 33 | caps.SetCapability("name", MethodBase.GetCurrentMethod().Name); 34 | caps.SetCapability("build", "SampleReleaseA"); 35 | var tags = new List { "Release1", "SmokeTests", "LoginFeature" }; 36 | caps.SetCapability("tags", tags); 37 | caps.SetCapability("extendedDebugging", true); 38 | _driver = new RemoteWebDriver(new Uri("https://ondemand.saucelabs.com/wd/hub"), caps, 39 | TimeSpan.FromSeconds(60)); 40 | _driver.Navigate().GoToUrl("https://www.google.com"); 41 | } 42 | 43 | [TestCleanup] 44 | public void CleanUpAfterEveryTestMethod() 45 | { 46 | var passed = TestContext.CurrentTestOutcome == UnitTestOutcome.Passed; 47 | ((IJavaScriptExecutor)_driver).ExecuteScript("sauce:context=" + "Stop Test"); 48 | ((IJavaScriptExecutor)_driver).ExecuteScript("sauce:job-result=" + (passed ? "passed" : "failed")); 49 | _driver?.Quit(); 50 | } 51 | } 52 | } 53 | -------------------------------------------------------------------------------- /SauceExamples/SeleniumMsTest/ParallelTests/DataDriven/BaseWebTest.cs: -------------------------------------------------------------------------------- 1 | using Common.SauceLabs; 2 | using OpenQA.Selenium; 3 | 4 | namespace Selenium3.MsTest.Scripts.ParallelTests.DataDriven 5 | { 6 | public class BaseWebTest 7 | { 8 | public SauceJavaScriptExecutor SauceReporter { get; set; } 9 | 10 | public IWebDriver Driver { get; set; } 11 | } 12 | } -------------------------------------------------------------------------------- /SauceExamples/SeleniumMsTest/Properties/AssemblyInfo.cs: -------------------------------------------------------------------------------- 1 | using Microsoft.VisualStudio.TestTools.UnitTesting; 2 | using System.Reflection; 3 | using System.Runtime.InteropServices; 4 | 5 | [assembly: AssemblyTitle("SeleniumMsTest")] 6 | [assembly: AssemblyDescription("")] 7 | [assembly: AssemblyConfiguration("")] 8 | [assembly: AssemblyCompany("")] 9 | [assembly: AssemblyProduct("SeleniumMsTest")] 10 | [assembly: AssemblyCopyright("Copyright © 2018")] 11 | [assembly: AssemblyTrademark("")] 12 | [assembly: AssemblyCulture("")] 13 | 14 | [assembly: ComVisible(false)] 15 | 16 | [assembly: Guid("8eac3c5c-fdf4-4a85-bfe9-19eb8b30e989")] 17 | 18 | // [assembly: AssemblyVersion("1.0.*")] 19 | [assembly: AssemblyVersion("1.0.0.0")] 20 | [assembly: AssemblyFileVersion("1.0.0.0")] 21 | [assembly: Parallelize(Workers = 100, Scope = ExecutionScope.MethodLevel)] -------------------------------------------------------------------------------- /SauceExamples/SeleniumMsTest/app.config: -------------------------------------------------------------------------------- 1 |  2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | -------------------------------------------------------------------------------- /SauceExamples/SeleniumMsTest/packages.config: -------------------------------------------------------------------------------- 1 |  2 | 3 | 4 | 5 | 6 | 7 | 8 | -------------------------------------------------------------------------------- /SauceExamples/SeleniumNunit/App.config: -------------------------------------------------------------------------------- 1 |  2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | -------------------------------------------------------------------------------- /SauceExamples/SeleniumNunit/BestPractices/BaseTest.cs: -------------------------------------------------------------------------------- 1 | using Common; 2 | using Common.SauceLabs; 3 | using NUnit.Framework; 4 | using NUnit.Framework.Interfaces; 5 | using OpenQA.Selenium; 6 | 7 | namespace Selenium3.Nunit.Scripts.BestPractices 8 | { 9 | [TestFixture()] 10 | [Category("AcceptanceTests")] 11 | public class BaseTest 12 | { 13 | [SetUp] 14 | public void ExecuteBeforeEveryTestMethod() 15 | { 16 | Driver = new WebDriverFactory().CreateSauceDriver(TestContext.CurrentContext.Test.Name); 17 | } 18 | [TearDown] 19 | public void CleanUpAfterEveryTestMethod() 20 | { 21 | //Make sure to check if your Driver is null before performing operations 22 | //with it in TearDown. It might not be initialized correctly 23 | if (Driver is null) 24 | { 25 | return; 26 | } 27 | new SauceJavaScriptExecutor(Driver).LogTestStatus(TestContext.CurrentContext.Result.Outcome.Status == TestStatus.Passed); 28 | Driver?.Quit(); 29 | } 30 | public IWebDriver Driver { get; set; } 31 | } 32 | } 33 | -------------------------------------------------------------------------------- /SauceExamples/SeleniumNunit/BestPractices/CrossBrowserExamples/NamespaceSetup.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Globalization; 3 | using Common.SauceLabs; 4 | using NUnit.Framework; 5 | 6 | namespace Selenium3.Nunit.Scripts.BestPractices.CrossBrowserExamples 7 | { 8 | [SetUpFixture] 9 | public class NamespaceSetup 10 | { 11 | [OneTimeSetUp] 12 | public void RunForWholeNamespace() 13 | { 14 | SauceLabsCapabilities.BuildName = $"CrossBrowserTests-{DateTime.Now.ToString(CultureInfo.InvariantCulture)}"; 15 | } 16 | } 17 | } 18 | -------------------------------------------------------------------------------- /SauceExamples/SeleniumNunit/BestPractices/CrossBrowserExamples/SauceLabsPage.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using OpenQA.Selenium; 3 | using OpenQA.Selenium.Support.UI; 4 | using ExpectedConditions = SeleniumExtras.WaitHelpers.ExpectedConditions; 5 | 6 | namespace Selenium3.Nunit.Scripts.BestPractices.CrossBrowserExamples 7 | { 8 | public class SauceLabsPage 9 | { 10 | private readonly IWebDriver _driver; 11 | 12 | public SauceLabsPage(IWebDriver driver) 13 | { 14 | _driver = driver; 15 | } 16 | 17 | public bool IsVisible 18 | { 19 | get 20 | { 21 | var wait = new WebDriverWait(_driver, TimeSpan.FromSeconds(10)); 22 | return wait.Until(ExpectedConditions.ElementIsVisible(By.Id("site-header"))).Displayed; 23 | } 24 | } 25 | 26 | public SauceLabsPage Open() 27 | { 28 | _driver.Navigate().GoToUrl("https://www.saucelabs.com"); 29 | return this; 30 | } 31 | } 32 | } 33 | -------------------------------------------------------------------------------- /SauceExamples/SeleniumNunit/BestPractices/ParallelTestsWithBestPractices.cs: -------------------------------------------------------------------------------- 1 | using FluentAssertions; 2 | using NUnit.Framework; 3 | 4 | namespace Selenium3.Nunit.Scripts.BestPractices 5 | { 6 | [TestFixture] 7 | [Parallelizable] 8 | [Category("Parallel selenium tests at the class level using best practices")] 9 | class ParallelTestsWithBestPractices : BaseTest 10 | { 11 | [Test] 12 | [Repeat(4)] 13 | public void Test1() 14 | { 15 | new UltimateQAHomePage(Driver).Open().IsVisible.Should().BeTrue(); 16 | } 17 | } 18 | [TestFixture] 19 | [Parallelizable] 20 | [Category("Parallel selenium tests at the class level using best practices")] 21 | class ParallelTestsWithBestPractices2 : BaseTest 22 | { 23 | [Test] 24 | [Repeat(4)] 25 | 26 | public void Test2() 27 | { 28 | new UltimateQAHomePage(Driver).Open().IsVisible.Should().BeTrue(); 29 | } 30 | } 31 | [TestFixture] 32 | [Parallelizable] 33 | [Category("Parallel selenium tests at the class level using best practices")] 34 | class ParallelTestsWithBestPractices3 : BaseTest 35 | { 36 | [Test] 37 | [Repeat(4)] 38 | 39 | public void Test3() 40 | { 41 | new UltimateQAHomePage(Driver).Open().IsVisible.Should().BeTrue(); 42 | } 43 | } 44 | [TestFixture] 45 | [Parallelizable] 46 | [Category("Parallel selenium tests at the class level using best practices")] 47 | class ParallelTestsWithBestPractices4 : BaseTest 48 | { 49 | [Test] 50 | [Repeat(4)] 51 | 52 | public void Test4() 53 | { 54 | new UltimateQAHomePage(Driver).Open().IsVisible.Should().BeTrue(); 55 | } 56 | } 57 | } 58 | -------------------------------------------------------------------------------- /SauceExamples/SeleniumNunit/BestPractices/UltimateQAHomePage.cs: -------------------------------------------------------------------------------- 1 | using System.Threading; 2 | using OpenQA.Selenium; 3 | 4 | namespace Selenium3.Nunit.Scripts.BestPractices 5 | { 6 | internal class UltimateQAHomePage 7 | { 8 | private readonly IWebDriver _driver; 9 | private IWebElement StartHereButton => 10 | _driver.FindElement(By.LinkText("Start learning now")); 11 | 12 | public UltimateQAHomePage(IWebDriver driver) 13 | { 14 | _driver = driver; 15 | } 16 | 17 | public bool IsVisible 18 | { 19 | get 20 | { 21 | try 22 | { 23 | return StartHereButton.Displayed; 24 | } 25 | catch (NoSuchElementException) 26 | { 27 | return false; 28 | } 29 | } 30 | } 31 | 32 | internal UltimateQAHomePage Open() 33 | { 34 | _driver.Navigate().GoToUrl("https://www.ultimateqa.com"); 35 | Thread.Sleep(30000); //thread.sleep is a bad practice in test automation and this is only done for example purposes 36 | return this; 37 | } 38 | } 39 | } -------------------------------------------------------------------------------- /SauceExamples/SeleniumNunit/NLog.config: -------------------------------------------------------------------------------- 1 |  2 | 8 | 9 | 12 | 13 | 14 | 18 | 19 | 20 | 25 | 26 | 31 | 32 | 33 | 34 | 35 | 36 | 40 | 41 | 42 | -------------------------------------------------------------------------------- /SauceExamples/SeleniumNunit/Parallel/ParallelTestsAtClassLevel.cs: -------------------------------------------------------------------------------- 1 | using NUnit.Framework; 2 | using Selenium3.Nunit.Scripts.SimpleExamples; 3 | 4 | namespace Selenium3.Nunit.Scripts.Parallel 5 | { 6 | [TestFixture] 7 | [Parallelizable] 8 | [Category("Parallel selenium tests at the class level")] 9 | class ParallelTestsAtClassLevel 10 | { 11 | [Test] 12 | public void Test1() 13 | { 14 | new ReusableTests().OpenBlogPage(); 15 | } 16 | } 17 | [TestFixture] 18 | [Parallelizable] 19 | [Category("Parallel selenium tests at the class level")] 20 | 21 | class ParallelizedTests2 22 | { 23 | [Test] 24 | public void Test2() 25 | { 26 | new ReusableTests().OpenBlogPage(); 27 | } 28 | } 29 | [TestFixture] 30 | [Parallelizable] 31 | [Category("Parallel selenium tests at the class level")] 32 | class ParallelizedTests3 33 | { 34 | [Test] 35 | public void Test3() 36 | { 37 | new ReusableTests().OpenBlogPage(); 38 | } 39 | } 40 | [TestFixture] 41 | [Parallelizable] 42 | [Category("Parallel selenium tests at the class level")] 43 | class ParallelizedTests4 44 | { 45 | [Test] 46 | public void Test4() 47 | { 48 | new ReusableTests().OpenBlogPage(); 49 | } 50 | } 51 | } 52 | -------------------------------------------------------------------------------- /SauceExamples/SeleniumNunit/Properties/AssemblyInfo.cs: -------------------------------------------------------------------------------- 1 | using NUnit.Framework; 2 | using System.Reflection; 3 | using System.Runtime.InteropServices; 4 | 5 | // General Information about an assembly is controlled through the following 6 | // set of attributes. Change these attribute values to modify the information 7 | // associated with an assembly. 8 | [assembly: AssemblyTitle("SeleniumNunit")] 9 | [assembly: AssemblyDescription("")] 10 | [assembly: AssemblyConfiguration("")] 11 | [assembly: AssemblyCompany("")] 12 | [assembly: AssemblyProduct("SeleniumNunit")] 13 | [assembly: AssemblyCopyright("Copyright © 2018")] 14 | [assembly: AssemblyTrademark("")] 15 | [assembly: AssemblyCulture("")] 16 | [assembly: LevelOfParallelism(100)] 17 | 18 | // Setting ComVisible to false makes the types in this assembly not visible 19 | // to COM components. If you need to access a type in this assembly from 20 | // COM, set the ComVisible attribute to true on that type. 21 | [assembly: ComVisible(false)] 22 | 23 | // The following GUID is for the ID of the typelib if this project is exposed to COM 24 | [assembly: Guid("d0544bbb-85d0-4bd0-82e2-2ac1c6bbbcf5")] 25 | 26 | // Version information for an assembly consists of the following four values: 27 | // 28 | // Major Version 29 | // Minor Version 30 | // Build Number 31 | // Revision 32 | // 33 | // You can specify all the values or you can default the Build and Revision Numbers 34 | // by using the '*' as shown below: 35 | // [assembly: AssemblyVersion("1.0.*")] 36 | [assembly: AssemblyVersion("1.0.0.0")] 37 | [assembly: AssemblyFileVersion("1.0.0.0")] 38 | -------------------------------------------------------------------------------- /SauceExamples/SeleniumNunit/SimpleExamples/LoggingWithSauce.cs: -------------------------------------------------------------------------------- 1 | using NLog; 2 | using NUnit.Framework; 3 | using OpenQA.Selenium; 4 | 5 | namespace Selenium3.Nunit.Scripts.SimpleExamples 6 | { 7 | [TestFixture] 8 | class LoggingWithSauce 9 | { 10 | private static readonly ILogger TheLogger = LogManager.GetCurrentClassLogger(); 11 | 12 | public IWebDriver Driver { get; private set; } 13 | 14 | [Test] 15 | public void LogWithNLog() 16 | { 17 | //this is a sample test action 18 | LogTestStep(TheLogger, "Open home page"); 19 | //peform other actions 20 | LogTestStep(TheLogger, "Click Lululemon logo"); 21 | 22 | } 23 | 24 | //It's critical for you to pass in the logger every time you use this method because 25 | //the logger needs to be created in EVERY class. This way, the logger will provide you information 26 | //about the class and the method that called it. If you make the logger live only in a single 27 | //class, you will be confused because all of the logging messages will come from that class in your text files 28 | // and this will be misleading. 29 | public void LogTestStep(ILogger logger, string message) 30 | { 31 | //make sure that your Info level messages are configured to go to the file and to the console 32 | logger.Info(message); 33 | ((IJavaScriptExecutor)Driver).ExecuteScript($"sauce:context={message}"); 34 | } 35 | } 36 | } 37 | -------------------------------------------------------------------------------- /SauceExamples/SeleniumNunit/SimpleExamples/ReusableTests.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Threading; 3 | using Common; 4 | using FluentAssertions; 5 | using OpenQA.Selenium; 6 | using OpenQA.Selenium.Support.UI; 7 | using ExpectedConditions = SeleniumExtras.WaitHelpers.ExpectedConditions; 8 | 9 | namespace Selenium3.Nunit.Scripts.SimpleExamples 10 | { 11 | public class ReusableTests 12 | { 13 | public IWebDriver Driver; 14 | 15 | public void OpenBlogPage() 16 | { 17 | Driver = new WebDriverFactory().CreateSauceDriver("BlogTest"); 18 | Driver.Navigate().GoToUrl("https://www.ultimateqa.com/blog"); 19 | Thread.Sleep(15000); 20 | var wait = new WebDriverWait(Driver, TimeSpan.FromSeconds(5)); 21 | wait.Until(ExpectedConditions.ElementIsVisible(By.Id("main-content"))) 22 | .Displayed.Should().BeTrue("The home page should load in less than 5 seconds."); 23 | ((IJavaScriptExecutor)Driver).ExecuteScript($"sauce:job-result=passed"); 24 | } 25 | } 26 | } 27 | -------------------------------------------------------------------------------- /SauceExamples/SeleniumNunit/SimpleExamples/SauceConnectTests.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using Common.SauceLabs; 3 | using NUnit.Framework; 4 | using NUnit.Framework.Interfaces; 5 | using OpenQA.Selenium; 6 | using OpenQA.Selenium.Remote; 7 | 8 | namespace Selenium3.Nunit.Scripts.SimpleExamples 9 | { 10 | [TestFixture] 11 | public class SauceConnectTests 12 | { 13 | IWebDriver _driver; 14 | 15 | [Test] 16 | public void SauceTestUsingTunnelIdentifier() 17 | { 18 | //TODO please supply your Sauce Labs user name in an environment variable 19 | var sauceUserName = Environment.GetEnvironmentVariable("SAUCE_USERNAME", EnvironmentVariableTarget.User); 20 | //TODO please supply your own Sauce Labs access Key in an environment variable 21 | var sauceAccessKey = Environment.GetEnvironmentVariable("SAUCE_ACCESS_KEY", EnvironmentVariableTarget.User); 22 | 23 | var desiredCaps = new DesiredCapabilities(); 24 | desiredCaps.SetCapability(CapabilityType.Version, "latest"); 25 | desiredCaps.SetCapability(CapabilityType.Platform, "Windows 10"); 26 | desiredCaps.SetCapability(CapabilityType.BrowserName, "chrome"); 27 | 28 | desiredCaps.SetCapability("username", sauceUserName); 29 | desiredCaps.SetCapability("accessKey", sauceAccessKey); 30 | desiredCaps.SetCapability("name", TestContext.CurrentContext.Test.Name); 31 | 32 | //This line of code lets Sauce Labs know which Sauce Connect tunnel to use for the test 33 | desiredCaps.SetCapability("tunnelIdentifier", "NikolaysTunnel"); 34 | //How long is a test allowed to run? 35 | desiredCaps.SetCapability("maxDuration", 3600); 36 | //Selenium crash might hang a command, this is the max time allowed to wait for a Selenium command 37 | desiredCaps.SetCapability("commandTimeout", 600); 38 | //How long can the browser wait before a new command? 39 | desiredCaps.SetCapability("idleTimeout", 1000); 40 | _driver = new RemoteWebDriver(new Uri(new SauceLabsEndpoint().SauceHubUrl), desiredCaps, TimeSpan.FromSeconds(600)); 41 | _driver.Navigate().GoToUrl("https://www.google.com"); 42 | Assert.Pass(); 43 | } 44 | [TearDown] 45 | public void CleanUpAfterEveryTestMethod() 46 | { 47 | var passed = TestContext.CurrentContext.Result.Outcome.Status == TestStatus.Passed; 48 | ((IJavaScriptExecutor)_driver).ExecuteScript("sauce:job-result=" + (passed ? "passed" : "failed")); 49 | _driver?.Quit(); 50 | } 51 | } 52 | } 53 | -------------------------------------------------------------------------------- /SauceExamples/SeleniumNunit/SimpleExamples/SimpleHeadlessTest.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Collections.Generic; 3 | using NUnit.Framework; 4 | using NUnit.Framework.Interfaces; 5 | using OpenQA.Selenium; 6 | using OpenQA.Selenium.Chrome; 7 | using OpenQA.Selenium.Remote; 8 | 9 | namespace Selenium3.Nunit.Scripts.SimpleExamples 10 | { 11 | [TestFixture] 12 | [Category("SimpleTest")] 13 | public class SimpleHeadlessTest 14 | { 15 | IWebDriver _driver; 16 | 17 | [Test] 18 | public void ChromeHeadlessW3C() 19 | { 20 | var sauceOptions = new Dictionary 21 | { 22 | ["username"] = Environment.GetEnvironmentVariable("SAUCE_USERNAME", EnvironmentVariableTarget.User), 23 | ["accessKey"] = Environment.GetEnvironmentVariable("SAUCE_ACCESS_KEY", EnvironmentVariableTarget.User) 24 | }; 25 | 26 | var chromeOptions = new ChromeOptions 27 | { 28 | BrowserVersion = "latest", 29 | PlatformName = "Linux", 30 | UseSpecCompliantProtocol = true 31 | }; 32 | sauceOptions.Add("name", TestContext.CurrentContext.Test.Name); 33 | chromeOptions.AddAdditionalCapability("sauce:options", sauceOptions, true); 34 | 35 | _driver = new RemoteWebDriver(new Uri("https://ondemand.us-east-1.saucelabs.com/wd/hub"), 36 | chromeOptions.ToCapabilities(), TimeSpan.FromSeconds(600)); 37 | _driver.Navigate().GoToUrl("https://www.saucedemo.com"); 38 | Assert.Pass(); 39 | } 40 | 41 | [TearDown] 42 | public void CleanUpAfterEveryTestMethod() 43 | { 44 | var passed = TestContext.CurrentContext.Result.Outcome.Status == TestStatus.Passed; 45 | ((IJavaScriptExecutor)_driver).ExecuteScript("sauce:job-result=" + (passed ? "passed" : "failed")); 46 | _driver?.Quit(); 47 | } 48 | } 49 | } 50 | -------------------------------------------------------------------------------- /SauceExamples/SeleniumNunit/SimpleExamples/SimpleSauceTest.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using NUnit.Framework; 3 | using NUnit.Framework.Interfaces; 4 | using OpenQA.Selenium; 5 | using OpenQA.Selenium.Chrome; 6 | using OpenQA.Selenium.Remote; 7 | 8 | namespace Selenium3.Nunit.Scripts.SimpleExamples 9 | { 10 | [TestFixture] 11 | [Category("SimpleTest")] 12 | public class SimpleSauceTest 13 | { 14 | IWebDriver _driver; 15 | [Test] 16 | public void SimpleTest() 17 | { 18 | //TODO please supply your Sauce Labs user name in an environment variable 19 | var sauceUserName = Environment.GetEnvironmentVariable( 20 | "SAUCE_USERNAME", EnvironmentVariableTarget.User); 21 | //TODO please supply your own Sauce Labs access Key in an environment variable 22 | var sauceAccessKey = Environment.GetEnvironmentVariable( 23 | "SAUCE_ACCESS_KEY", EnvironmentVariableTarget.User); 24 | 25 | ChromeOptions options = new ChromeOptions(); 26 | options.AddAdditionalCapability(CapabilityType.Version, "latest", true); 27 | options.AddAdditionalCapability(CapabilityType.Platform, "Windows 10", true); 28 | options.AddAdditionalCapability("username", sauceUserName, true); 29 | options.AddAdditionalCapability("accessKey", sauceAccessKey, true); 30 | options.AddAdditionalCapability("name", TestContext.CurrentContext.Test.Name, true); 31 | options.AddAdditionalCapability("build", "ShwabTeamName:" + DateTime.Now, true); 32 | 33 | 34 | _driver = new RemoteWebDriver(new Uri("https://ondemand.saucelabs.com/wd/hub"), options.ToCapabilities(), 35 | TimeSpan.FromSeconds(600)); 36 | _driver.Navigate().GoToUrl("https://www.google.com"); 37 | Assert.Pass(); 38 | } 39 | 40 | [TearDown] 41 | public void CleanUpAfterEveryTestMethod() 42 | { 43 | var passed = TestContext.CurrentContext.Result.Outcome.Status == TestStatus.Passed; 44 | ((IJavaScriptExecutor)_driver).ExecuteScript("sauce:job-result=" + (passed ? "passed" : "failed")); 45 | _driver?.Quit(); 46 | } 47 | } 48 | } 49 | -------------------------------------------------------------------------------- /SauceExamples/SeleniumNunit/Visual/README.md: -------------------------------------------------------------------------------- 1 | # Visual End-to-End Testing With Screener 2 | 3 | ## Getting Started 4 | 5 | * Open the solution in Visual Studio 6 | 7 | * Set environment variables for your Sauce username, accesskey, and Screener API Key 8 | 9 | ``` 10 | setx SAUCE_USERNAME "YOUR SAUCE USERNAME" 11 | setx SAUCE_ACCESS_KEY "YOUR KEY" 12 | setx SCREENER_API_KEY "YOUR SCREENER API KEY" 13 | ``` 14 | 15 | - Run tests in VisualTestSimple.cs 16 | -------------------------------------------------------------------------------- /SauceExamples/SeleniumNunit/packages.config: -------------------------------------------------------------------------------- 1 |  2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | 23 | 24 | 25 | 26 | 27 | 28 | 29 | 30 | 31 | 32 | 33 | -------------------------------------------------------------------------------- /SauceExamples/Set-RdcEnvironmentVariables.ps1: -------------------------------------------------------------------------------- 1 | Param( 2 | [string]$rdcVodQaNativeAppApiKey, 3 | [string]$rdcSauceDemoIosRdcApiKey, 4 | [string]$rdcSauceDemoAndroidAppApiKey 5 | ) 6 | 7 | Write-Output "sauce.rdc.VodQaNativeAppApiKey stored in Azure DevOps=>$rdcVodQaNativeAppApiKey" 8 | Write-Output "sauce.rdc.SauceDemoIosRdcApiKey stored in Azure DevOps=>$rdcSauceDemoIosRdcApiKey" 9 | Write-Output "sauce.rdc.SauceDemoAndroidAppApiKey stored in Azure DevOps=>$rdcSauceDemoAndroidAppApiKey" 10 | 11 | [Environment]::SetEnvironmentVariable("VODQC_RDC_API_KEY", "$rdcVodQaNativeAppApiKey", "User") 12 | [Environment]::SetEnvironmentVariable("SAUCE_DEMO_IOS_RDC_API_KEY", "$rdcSauceDemoIosRdcApiKey", "User") 13 | [Environment]::SetEnvironmentVariable("RDC_SAUCE_DEMO_ANDROID_KEY", "$rdcSauceDemoIosRdcApiKey", "User") 14 | -------------------------------------------------------------------------------- /SauceExamples/Web.Tests/Antipatterns/Parallelization/BrokenBaseTest.cs: -------------------------------------------------------------------------------- 1 | using System.Configuration; 2 | using Common; 3 | using Common.SauceLabs; 4 | using NUnit.Framework; 5 | using NUnit.Framework.Interfaces; 6 | using OpenQA.Selenium; 7 | 8 | namespace Selenium3.Nunit.Framework.Antipatterns.Parallelization 9 | { 10 | 11 | [TestFixture] 12 | public class BrokenBaseTest 13 | { 14 | private readonly string _browser; 15 | private readonly string _browserVersion; 16 | private readonly string _osPlatform; 17 | public SauceJavaScriptExecutor SauceReporter; 18 | private SauceLabsCapabilities SauceConfig { get; set; } 19 | public static IWebDriver Driver { get; set; } 20 | 21 | 22 | public BrokenBaseTest(string browser, string browserVersion, string osPlatform) 23 | { 24 | _browser = browser; 25 | _browserVersion = browserVersion; 26 | _osPlatform = osPlatform; 27 | } 28 | 29 | [SetUp] 30 | public void ExecuteBeforeEveryTestMethod() 31 | { 32 | SauceConfig = new SauceLabsCapabilities 33 | { 34 | IsDebuggingEnabled = bool.Parse(ConfigurationManager.AppSettings["isExtendedDebuggingEnabled"]), 35 | IsHeadless = false 36 | }; 37 | 38 | Driver = new WebDriverFactory(SauceConfig).CreateSauceDriver(_browser, _browserVersion, _osPlatform); 39 | SauceReporter = new SauceJavaScriptExecutor(Driver); 40 | SauceReporter.SetTestName(TestContext.CurrentContext.Test.Name); 41 | SauceReporter.SetBuildName("StaticParallelization"); 42 | } 43 | 44 | [TearDown] 45 | public void CleanUpAfterEveryTestMethod() 46 | { 47 | if (SauceConfig.IsUsingSauceLabs) ExecuteSauceCleanupSteps(); 48 | Driver?.Quit(); 49 | } 50 | 51 | private void ExecuteSauceCleanupSteps() 52 | { 53 | var isPassed = TestContext.CurrentContext.Result.Outcome.Status 54 | == TestStatus.Passed; 55 | SauceReporter.LogTestStatus(isPassed); 56 | //SetTestStatusUsingApi(isPassed); 57 | SauceReporter.LogMessage("Test finished execution"); 58 | SauceReporter.LogMessage(TestContext.CurrentContext.Result.Message); 59 | } 60 | 61 | 62 | 63 | } 64 | } -------------------------------------------------------------------------------- /SauceExamples/Web.Tests/Antipatterns/Parallelization/BrokenLoginFeature.cs: -------------------------------------------------------------------------------- 1 | using FluentAssertions; 2 | using NUnit.Framework; 3 | using Selenium3.Nunit.Framework.BestPractices.Pages; 4 | 5 | namespace Selenium3.Nunit.Framework.Antipatterns.Parallelization 6 | { 7 | [TestFixture] 8 | [TestFixtureSource(typeof(CrossBrowserData), 9 | nameof(CrossBrowserData.SimpleConfiguration))] 10 | [Parallelizable] 11 | public class BrokenLoginFeature : BrokenBaseTest 12 | { 13 | private SauceDemoLoginPage _loginPage; 14 | 15 | public BrokenLoginFeature(string browser, string version, string os) : 16 | base(browser, version, os) 17 | { 18 | 19 | } 20 | [SetUp] 21 | public void RunBeforeEveryTest() 22 | { 23 | _loginPage = new SauceDemoLoginPage(Driver); 24 | } 25 | 26 | [Test] 27 | public void LoginPageShouldLoad() 28 | { 29 | _loginPage.Open().IsLoaded.Should().BeTrue("the login page should load successfully."); 30 | } 31 | 32 | } 33 | } 34 | -------------------------------------------------------------------------------- /SauceExamples/Web.Tests/Antipatterns/Parallelization/LoginFeatureWorksInParallel.cs: -------------------------------------------------------------------------------- 1 | using FluentAssertions; 2 | using NUnit.Framework; 3 | using Selenium3.Nunit.Framework.BestPractices.Pages; 4 | 5 | namespace Selenium3.Nunit.Framework.Antipatterns.Parallelization 6 | { 7 | [TestFixture] 8 | [TestFixtureSource(typeof(CrossBrowserData), 9 | nameof(CrossBrowserData.SimpleConfiguration))] 10 | [Parallelizable] 11 | public class LoginFeatureWorksInParallel : WorkingBaseTest 12 | { 13 | private SauceDemoLoginPage _loginPage; 14 | 15 | public LoginFeatureWorksInParallel(string browser, string version, string os) : 16 | base(browser, version, os) 17 | { 18 | 19 | } 20 | [SetUp] 21 | public void RunBeforeEveryTest() 22 | { 23 | _loginPage = new SauceDemoLoginPage(Driver); 24 | } 25 | 26 | [Test] 27 | public void LoginPageShouldLoad() 28 | { 29 | _loginPage.Open().IsLoaded.Should().BeTrue("the login page should load successfully."); 30 | } 31 | 32 | } 33 | } 34 | -------------------------------------------------------------------------------- /SauceExamples/Web.Tests/Antipatterns/Parallelization/WorkingBaseTest.cs: -------------------------------------------------------------------------------- 1 | using System.Configuration; 2 | using Common; 3 | using Common.SauceLabs; 4 | using NUnit.Framework; 5 | using NUnit.Framework.Interfaces; 6 | using OpenQA.Selenium; 7 | 8 | namespace Selenium3.Nunit.Framework.Antipatterns.Parallelization 9 | { 10 | [TestFixture] 11 | public class WorkingBaseTest 12 | { 13 | private readonly string _browser; 14 | private readonly string _browserVersion; 15 | private readonly string _osPlatform; 16 | public SauceJavaScriptExecutor SauceReporter; 17 | private SauceLabsCapabilities SauceConfig { get; set; } 18 | 19 | public WorkingBaseTest(string browser, string browserVersion, string osPlatform) 20 | { 21 | _browser = browser; 22 | _browserVersion = browserVersion; 23 | _osPlatform = osPlatform; 24 | } 25 | 26 | [SetUp] 27 | public void ExecuteBeforeEveryTestMethod() 28 | { 29 | SauceConfig = new SauceLabsCapabilities 30 | { 31 | IsDebuggingEnabled = bool.Parse(ConfigurationManager.AppSettings["isExtendedDebuggingEnabled"]), 32 | IsHeadless = false 33 | }; 34 | 35 | Driver = new WebDriverFactory(SauceConfig).CreateSauceDriver(_browser, _browserVersion, _osPlatform); 36 | SauceReporter = new SauceJavaScriptExecutor(Driver); 37 | SauceReporter.SetTestName(TestContext.CurrentContext.Test.Name); 38 | SauceReporter.SetBuildName("ParallelizationWithoutStatic"); 39 | } 40 | 41 | [TearDown] 42 | public void CleanUpAfterEveryTestMethod() 43 | { 44 | if (SauceConfig.IsUsingSauceLabs) ExecuteSauceCleanupSteps(); 45 | Driver?.Quit(); 46 | } 47 | 48 | private void ExecuteSauceCleanupSteps() 49 | { 50 | var isPassed = TestContext.CurrentContext.Result.Outcome.Status 51 | == TestStatus.Passed; 52 | SauceReporter.LogTestStatus(isPassed); 53 | //SetTestStatusUsingApi(isPassed); 54 | SauceReporter.LogMessage("Test finished execution"); 55 | SauceReporter.LogMessage(TestContext.CurrentContext.Result.Message); 56 | } 57 | 58 | 59 | 60 | public IWebDriver Driver { get; set; } 61 | } 62 | } -------------------------------------------------------------------------------- /SauceExamples/Web.Tests/BestPractices/Elements/CartElement.cs: -------------------------------------------------------------------------------- 1 | using OpenQA.Selenium; 2 | using Selenium3.Nunit.Framework.BestPractices.Pages; 3 | 4 | namespace Selenium3.Nunit.Framework.BestPractices.Elements 5 | { 6 | public class CartComponent 7 | { 8 | private readonly IWebDriver _driver; 9 | private string CartItemCounterText 10 | { 11 | get 12 | { 13 | try 14 | { 15 | return _driver.FindElement(By.XPath("//*[@class='fa-layers-counter shopping_cart_badge']")).Text; 16 | } 17 | catch (NoSuchElementException) 18 | { 19 | return "0"; 20 | } 21 | } 22 | } 23 | public bool HasItems => int.Parse(CartItemCounterText) > 0; 24 | 25 | public int ItemCount => int.Parse(CartItemCounterText); 26 | 27 | public CartComponent(IWebDriver driver) 28 | { 29 | _driver = driver; 30 | } 31 | 32 | public CartComponent InjectUserWithItems() 33 | { 34 | ((IJavaScriptExecutor)_driver).ExecuteScript("window.sessionStorage.setItem('session-username', 'standard-user')"); 35 | ((IJavaScriptExecutor)_driver).ExecuteScript("window.sessionStorage.setItem('cart-contents', '[4,1]')"); 36 | _driver.Navigate().Refresh(); 37 | return this; 38 | } 39 | 40 | internal YourShoppingCartPage Click() 41 | { 42 | _driver.FindElement(By.Id("shopping_cart_container")).Click(); 43 | return new YourShoppingCartPage(_driver); 44 | } 45 | } 46 | } -------------------------------------------------------------------------------- /SauceExamples/Web.Tests/BestPractices/Pages/BasePage.cs: -------------------------------------------------------------------------------- 1 | using Common; 2 | using Common.SauceLabs; 3 | using OpenQA.Selenium; 4 | 5 | namespace Selenium3.Nunit.Framework.BestPractices.Pages 6 | { 7 | public class BasePage 8 | { 9 | public readonly IWebDriver _driver; 10 | private readonly string _baseUrl; 11 | 12 | public SauceJavaScriptExecutor SauceJsExecutor => 13 | new SauceJavaScriptExecutor(_driver); 14 | 15 | public Wait Wait => new Wait(_driver); 16 | public string BaseUrl => _baseUrl; 17 | 18 | public BasePage(IWebDriver driver) 19 | { 20 | _driver = driver; 21 | _baseUrl = "https://www.saucedemo.com"; 22 | } 23 | } 24 | } -------------------------------------------------------------------------------- /SauceExamples/Web.Tests/BestPractices/Pages/CheckoutCompletePage.cs: -------------------------------------------------------------------------------- 1 | using OpenQA.Selenium; 2 | 3 | namespace Selenium3.Nunit.Framework.BestPractices.Pages 4 | { 5 | public class CheckoutCompletePage 6 | { 7 | private readonly IWebDriver _driver; 8 | public bool IsCheckedOut => _driver.Url.Contains("checkout-complete.html"); 9 | 10 | 11 | public CheckoutCompletePage(IWebDriver driver) 12 | { 13 | _driver = driver; 14 | } 15 | } 16 | } -------------------------------------------------------------------------------- /SauceExamples/Web.Tests/BestPractices/Pages/CheckoutInformationPage.cs: -------------------------------------------------------------------------------- 1 | using OpenQA.Selenium; 2 | 3 | namespace Selenium3.Nunit.Framework.BestPractices.Pages 4 | { 5 | internal class CheckoutInformationPage : BasePage 6 | { 7 | public CheckoutInformationPage(IWebDriver driver) : base(driver) 8 | { 9 | } 10 | 11 | private By CartCheckoutButtonLocator => By.CssSelector("[class='btn_primary cart_button']"); 12 | 13 | public CheckoutOverviewPage FillOutPersonalInformation() 14 | { 15 | Wait.UntilIsVisibleById("first-name").SendKeys("firstName"); 16 | _driver.FindElement(By.Id("last-name")).SendKeys("lastName"); 17 | _driver.FindElement(By.Id("postal-code")).SendKeys("zip"); 18 | _driver.FindElement(CartCheckoutButtonLocator).Click(); 19 | return new CheckoutOverviewPage(_driver); 20 | } 21 | } 22 | } -------------------------------------------------------------------------------- /SauceExamples/Web.Tests/BestPractices/Pages/CheckoutOverviewPage.cs: -------------------------------------------------------------------------------- 1 | using OpenQA.Selenium; 2 | using Selenium3.Nunit.Framework.BestPractices.Elements; 3 | 4 | namespace Selenium3.Nunit.Framework.BestPractices.Pages 5 | { 6 | public class CheckoutOverviewPage : BasePage 7 | { 8 | public CheckoutOverviewPage(IWebDriver driver) : base(driver) 9 | { 10 | } 11 | public CartComponent Cart => new CartComponent(_driver); 12 | 13 | public OrderConfirmationPage FinishCheckout() 14 | { 15 | Wait.UntilIsVisibleByCss("[class='btn_action cart_button']").Click(); 16 | return new OrderConfirmationPage(_driver); 17 | } 18 | 19 | internal CheckoutOverviewPage Open() 20 | { 21 | //TODO duplication here with the URL. Also happening in YourShoppingCartPage 22 | _driver.Navigate().GoToUrl($"{BaseUrl}/checkout-step-two.html"); 23 | return this; 24 | } 25 | } 26 | } -------------------------------------------------------------------------------- /SauceExamples/Web.Tests/BestPractices/Pages/Item.cs: -------------------------------------------------------------------------------- 1 | namespace Selenium3.Nunit.Framework.BestPractices.Pages 2 | { 3 | public enum Item 4 | { 5 | Backpack 6 | } 7 | } -------------------------------------------------------------------------------- /SauceExamples/Web.Tests/BestPractices/Pages/OrderConfirmationPage.cs: -------------------------------------------------------------------------------- 1 | using OpenQA.Selenium; 2 | 3 | namespace Selenium3.Nunit.Framework.BestPractices.Pages 4 | { 5 | public class OrderConfirmationPage : BasePage 6 | { 7 | public OrderConfirmationPage(IWebDriver driver) : base(driver) 8 | { 9 | } 10 | 11 | public bool IsCheckoutComplete => 12 | Wait.UntilIsVisibleByClass("complete-header").Text == "THANK YOU FOR YOUR ORDER"; 13 | } 14 | } -------------------------------------------------------------------------------- /SauceExamples/Web.Tests/BestPractices/Pages/ProductsPage.cs: -------------------------------------------------------------------------------- 1 | using OpenQA.Selenium; 2 | using Selenium3.Nunit.Framework.BestPractices.Elements; 3 | 4 | namespace Selenium3.Nunit.Framework.BestPractices.Pages 5 | { 6 | public class ProductsPage : BasePage 7 | { 8 | private readonly string _pageUrlPart; 9 | 10 | public ProductsPage(IWebDriver driver) : base(driver) 11 | { 12 | _pageUrlPart = "inventory.html"; 13 | } 14 | 15 | public bool IsLoaded => Wait.UntilIsDisplayedById("inventory_filter_container"); 16 | 17 | private IWebElement LogoutLink => _driver.FindElement(By.Id("logout_sidebar_link")); 18 | 19 | private IWebElement HamburgerElement => _driver.FindElement(By.ClassName("bm-burger-button")); 20 | 21 | public int ProductCount => 22 | _driver.FindElements(By.ClassName("inventory_item")).Count; 23 | 24 | public CartComponent Cart => new CartComponent(_driver); 25 | 26 | public void Logout() 27 | { 28 | HamburgerElement.Click(); 29 | LogoutLink.Click(); 30 | } 31 | 32 | internal ProductsPage Open() 33 | { 34 | _driver.Navigate().GoToUrl($"{BaseUrl}/{_pageUrlPart}"); 35 | return this; 36 | } 37 | 38 | public void AddFirstProductToCart() 39 | { 40 | Wait.UntilIsVisibleByCss("button[class='btn_primary btn_inventory']").Click(); 41 | } 42 | } 43 | } -------------------------------------------------------------------------------- /SauceExamples/Web.Tests/BestPractices/Pages/SauceDemoLoginPage.cs: -------------------------------------------------------------------------------- 1 | using System.Collections.Generic; 2 | using System.Reflection; 3 | using Common; 4 | using OpenQA.Selenium; 5 | 6 | namespace Selenium3.Nunit.Framework.BestPractices.Pages 7 | { 8 | public class SauceDemoLoginPage : BasePage 9 | { 10 | public SauceDemoLoginPage(IWebDriver driver) : base(driver) 11 | { 12 | } 13 | 14 | private readonly By _loginButtonLocator = By.ClassName("btn_action"); 15 | public bool IsLoaded => new Wait(_driver, _loginButtonLocator).IsVisible(); 16 | public IWebElement PasswordField => _driver.FindElement(By.Id("password")); 17 | public IWebElement LoginButton => _driver.FindElement(_loginButtonLocator); 18 | private readonly By _usernameLocator = By.Id("user-name"); 19 | public IWebElement UsernameField => _driver.FindElement(_usernameLocator); 20 | 21 | public SauceDemoLoginPage Open() 22 | { 23 | _driver.Navigate().GoToUrl(BaseUrl); 24 | return this; 25 | } 26 | 27 | internal Dictionary GetPerformance() 28 | { 29 | var metrics = new Dictionary 30 | { 31 | ["type"] = "sauce:performance" 32 | }; 33 | return (Dictionary)((IJavaScriptExecutor)_driver).ExecuteScript("sauce:log", metrics); 34 | } 35 | 36 | public ProductsPage Login(string username, string password) 37 | { 38 | SauceJsExecutor.LogMessage( 39 | $"Start login with user=>{username} and pass=>{password}"); 40 | var usernameField = Wait.UntilIsVisible(_usernameLocator); 41 | usernameField.SendKeys(username); 42 | PasswordField.SendKeys(password); 43 | LoginButton.Click(); 44 | SauceJsExecutor.LogMessage($"{MethodBase.GetCurrentMethod().Name} success"); 45 | return new ProductsPage(_driver); 46 | } 47 | } 48 | } -------------------------------------------------------------------------------- /SauceExamples/Web.Tests/BestPractices/Pages/YourShoppingCartPage.cs: -------------------------------------------------------------------------------- 1 | using OpenQA.Selenium; 2 | using Selenium3.Nunit.Framework.BestPractices.Elements; 3 | 4 | namespace Selenium3.Nunit.Framework.BestPractices.Pages 5 | { 6 | internal class YourShoppingCartPage : BasePage 7 | { 8 | public YourShoppingCartPage(IWebDriver driver) : base(driver) 9 | { 10 | } 11 | 12 | public CartComponent Cart => new CartComponent(_driver); 13 | 14 | internal CheckoutInformationPage Checkout() 15 | { 16 | Wait.UntilIsVisibleByCss("a[class='btn_action checkout_button']").Click(); 17 | return new CheckoutInformationPage(_driver); 18 | } 19 | 20 | internal YourShoppingCartPage Open() 21 | { 22 | _driver.Navigate().GoToUrl($"{BaseUrl}/cart.html"); 23 | return this; 24 | } 25 | } 26 | } -------------------------------------------------------------------------------- /SauceExamples/Web.Tests/BestPractices/test/LoginFeature.cs: -------------------------------------------------------------------------------- 1 | using FluentAssertions; 2 | using NUnit.Framework; 3 | using Selenium3.Nunit.Framework.BestPractices.Pages; 4 | 5 | namespace Selenium3.Nunit.Framework.BestPractices.test 6 | { 7 | [TestFixture] 8 | [TestFixtureSource(typeof(CrossBrowserData), 9 | nameof(CrossBrowserData.LatestConfigurations))] 10 | [Parallelizable] 11 | public class LoginFeature : BaseTest 12 | { 13 | private SauceDemoLoginPage _loginPage; 14 | 15 | public LoginFeature(string browser, string version, string os) : 16 | base(browser, version, os) 17 | { 18 | } 19 | 20 | [Test] 21 | public void LoginPageShouldLoad() 22 | { 23 | _loginPage.Open().IsLoaded.Should().BeTrue("the login page should load successfully."); 24 | } 25 | [Test] 26 | public void ShouldBeAbleToLoginWithValidUser() 27 | { 28 | _loginPage.Open(); 29 | var productsPage = _loginPage.Login("standard_user", "secret_sauce"); 30 | productsPage.IsLoaded.Should().BeTrue("we successfully logged in and the home page should load."); 31 | } 32 | [Test] 33 | public void ShouldNotBeAbleToLoginWithLockedOutUser() 34 | { 35 | _loginPage.Open(); 36 | var productsPage = _loginPage.Login("locked_out_user", "secret_sauce"); 37 | productsPage.IsLoaded.Should().BeFalse("we used a locked out user who should not be able to login."); 38 | } 39 | [Test] 40 | public void ShouldBeAbleToLoginWithProblemUser() 41 | { 42 | _loginPage.Open(); 43 | var productsPage = _loginPage.Login("problem_user", "secret_sauce"); 44 | productsPage.IsLoaded.Should().BeTrue("we successfully logged in and the home page should load."); 45 | } 46 | [Test] 47 | public void ShouldNotLoginWithInvalidUserName() 48 | { 49 | _loginPage.Open(); 50 | var productsPage = _loginPage.Login("FAKE_USER_NAME", "secret_sauce"); 51 | productsPage.IsLoaded.Should().BeFalse("we used a invalid username who should not be able to login."); 52 | } 53 | [Test] 54 | public void ShouldNotLoginWithInvalidPassword() 55 | { 56 | _loginPage.Open(); 57 | var productsPage = _loginPage.Login("problem_user", "FAKE_PASSWORD"); 58 | productsPage.IsLoaded.Should().BeFalse("we used an invalid password, so the user should not be able to login"); 59 | } 60 | 61 | [SetUp] 62 | public void RunBeforeEveryTest() 63 | { 64 | _loginPage = new SauceDemoLoginPage(Driver); 65 | } 66 | } 67 | } 68 | -------------------------------------------------------------------------------- /SauceExamples/Web.Tests/BestPractices/test/LogoutFeature.cs: -------------------------------------------------------------------------------- 1 | using FluentAssertions; 2 | using NUnit.Framework; 3 | using Selenium3.Nunit.Framework.BestPractices.Pages; 4 | 5 | namespace Selenium3.Nunit.Framework.BestPractices.test 6 | { 7 | [TestFixture] 8 | [TestFixtureSource(typeof(CrossBrowserData), 9 | nameof(CrossBrowserData.LatestConfigurations))] 10 | [Parallelizable] 11 | public class LogoutFeature : BaseTest 12 | { 13 | public LogoutFeature(string browser, string version, string os) : 14 | base(browser, version, os) 15 | { 16 | } 17 | [Test] 18 | public void ShouldBeAbleToLogOut() 19 | { 20 | var loginPage = new SauceDemoLoginPage(Driver); 21 | loginPage.Open(); 22 | var productsPage = loginPage.Login("standard_user", "secret_sauce"); 23 | productsPage.Logout(); 24 | loginPage.IsLoaded.Should().BeTrue("we successfully logged out, so the login page should be visible"); 25 | } 26 | } 27 | } 28 | -------------------------------------------------------------------------------- /SauceExamples/Web.Tests/BestPractices/test/PerformanceTesting.cs: -------------------------------------------------------------------------------- 1 | using Common; 2 | using Common.SauceLabs; 3 | using NUnit.Framework; 4 | using OpenQA.Selenium; 5 | using Selenium3.Nunit.Framework.BestPractices.Pages; 6 | 7 | namespace Selenium3.Nunit.Framework.BestPractices.test 8 | { 9 | [TestFixture] 10 | [Parallelizable] 11 | [Category("Performance")] 12 | [Ignore("need to add the capability to set performance testing to true")] 13 | public class PerformanceTesting 14 | { 15 | private SauceDemoLoginPage _loginPage; 16 | 17 | [Test] 18 | public void LoginPageSpeedIndexIsWithin20Percent() 19 | { 20 | _loginPage.Open(); 21 | var performanceMetrics = _loginPage.GetPerformance(); 22 | 23 | Assert.That(performanceMetrics["speedIndex"], Is.EqualTo(415).Within(20).Percent); 24 | } 25 | [SetUp] 26 | public void RunBeforeEveryTest() 27 | { 28 | 29 | Driver = new WebDriverFactory().CreateSauceDriver("chrome", "latest", "windows 10"); 30 | _loginPage = new SauceDemoLoginPage(Driver); 31 | } 32 | 33 | public IWebDriver Driver { get; set; } 34 | } 35 | } 36 | -------------------------------------------------------------------------------- /SauceExamples/Web.Tests/BestPractices/test/ProductsPageFeature.cs: -------------------------------------------------------------------------------- 1 | using FluentAssertions; 2 | using NUnit.Framework; 3 | using Selenium3.Nunit.Framework.BestPractices.Pages; 4 | 5 | namespace Selenium3.Nunit.Framework.BestPractices.test 6 | { 7 | [TestFixture] 8 | [TestFixtureSource(typeof(CrossBrowserData), 9 | nameof(CrossBrowserData.LatestConfigurations))] 10 | [Parallelizable] 11 | public class ProductsPageFeature : BaseTest 12 | { 13 | public ProductsPageFeature(string browser, string version, string os) : 14 | base(browser, version, os) 15 | { 16 | } 17 | [Test] 18 | public void ShouldHaveCorrectNumberOfProducts() 19 | { 20 | //IMPORTANT - how did I bypass the login here? 21 | var productsPage = new ProductsPage(Driver).Open(); 22 | productsPage.ProductCount.Should().Be(6, 23 | "we logged in successfully and we should have 6 items on the page"); 24 | } 25 | } 26 | } 27 | -------------------------------------------------------------------------------- /SauceExamples/Web.Tests/BestPractices/test/ShoppingCartFeature.cs: -------------------------------------------------------------------------------- 1 | using FluentAssertions; 2 | using NUnit.Framework; 3 | using Selenium3.Nunit.Framework.BestPractices.Pages; 4 | 5 | namespace Selenium3.Nunit.Framework.BestPractices.test 6 | { 7 | [TestFixture] 8 | [Parallelizable] 9 | [TestFixtureSource(typeof(CrossBrowserData), 10 | nameof(CrossBrowserData.LatestConfigurations))] 11 | public class ShoppingCartFeature : BaseTest 12 | { 13 | public ShoppingCartFeature(string browser, string browserVersion, string osPlatform) : 14 | base(browser, browserVersion, osPlatform) 15 | { 16 | } 17 | 18 | [Test] 19 | public void ShouldBeAbleToCheckOutWithItems() 20 | { 21 | //Arrange 22 | var overviewPage = new CheckoutOverviewPage(Driver); 23 | overviewPage.Open(); 24 | //We don't need to actually use th UI to add items to the cart. 25 | //I'm injecting Javascript to control the state of the cart 26 | //bypassing a bunch of unecessary element interactions and creating something efficient and stable 27 | overviewPage.Cart.InjectUserWithItems(); 28 | //Act - very few UI interactions 29 | overviewPage.FinishCheckout(). 30 | IsCheckoutComplete.Should().BeTrue("we finished the checkout process"); //Assert 31 | } 32 | [Test] 33 | public void ShouldBeAbleToAddItemToCart() 34 | { 35 | //Arrange 36 | var productsPage = new ProductsPage(Driver); 37 | //Act 38 | productsPage.Open(); 39 | productsPage.AddFirstProductToCart(); 40 | //Assert 41 | productsPage.Cart.ItemCount.Should().Be(1, "we added a backpack to the cart"); 42 | } 43 | } 44 | } 45 | -------------------------------------------------------------------------------- /SauceExamples/Web.Tests/Properties/AssemblyInfo.cs: -------------------------------------------------------------------------------- 1 | using NUnit.Framework; 2 | using System.Reflection; 3 | using System.Runtime.InteropServices; 4 | 5 | [assembly: AssemblyTitle("Web.Tests")] 6 | [assembly: AssemblyDescription("")] 7 | [assembly: AssemblyConfiguration("")] 8 | [assembly: AssemblyCompany("")] 9 | [assembly: AssemblyProduct("Web.Tests")] 10 | [assembly: AssemblyCopyright("Copyright © 2018")] 11 | [assembly: AssemblyTrademark("")] 12 | [assembly: AssemblyCulture("")] 13 | 14 | [assembly: ComVisible(false)] 15 | 16 | [assembly: Guid("7b1c4ce7-45e7-436a-96d2-79493b1274ed")] 17 | 18 | // [assembly: AssemblyVersion("1.0.*")] 19 | [assembly: AssemblyVersion("1.0.0.0")] 20 | [assembly: AssemblyFileVersion("1.0.0.0")] 21 | 22 | //Docs on NUnit parallelization: https://github.com/nunit/docs/wiki/Framework-Parallel-Test-Execution 23 | [assembly: Parallelizable(ParallelScope.Fixtures)] 24 | //Set this value to the Maximum amount of VMs that you have in Sauce Labs 25 | [assembly: LevelOfParallelism(100)] -------------------------------------------------------------------------------- /SauceExamples/Web.Tests/app.config: -------------------------------------------------------------------------------- 1 |  2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | 23 | 24 | -------------------------------------------------------------------------------- /SauceExamples/Web.Tests/packages.config: -------------------------------------------------------------------------------- 1 |  2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | -------------------------------------------------------------------------------- /SeleniumExamples/MSTestExamples/AssemblyInfo.cs: -------------------------------------------------------------------------------- 1 | using Microsoft.VisualStudio.TestTools.UnitTesting; 2 | 3 | [assembly: Parallelize(Workers = 10, Scope = ExecutionScope.MethodLevel)] 4 | -------------------------------------------------------------------------------- /SeleniumExamples/MSTestExamples/MSTestExamples.csproj: -------------------------------------------------------------------------------- 1 |  2 | 3 | 4 | Exe 5 | net9.0 6 | enable 7 | enable 8 | MSTest 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | -------------------------------------------------------------------------------- /SeleniumExamples/MSTestExamples/TestBase.cs: -------------------------------------------------------------------------------- 1 | using Microsoft.VisualStudio.TestTools.UnitTesting; 2 | using OpenQA.Selenium; 3 | using OpenQA.Selenium.Chrome; 4 | using OpenQA.Selenium.Remote; 5 | 6 | namespace MSTest; 7 | 8 | public class TestBase 9 | { 10 | protected IWebDriver driver; 11 | private string sessionId; 12 | private string testName; 13 | private static readonly string BuildName; 14 | private static readonly string BuildIdentifier; 15 | 16 | static TestBase() 17 | { 18 | BuildName = "DotNet MSTest Examples"; 19 | string buildNumber = DateTimeOffset.UtcNow.ToUnixTimeMilliseconds().ToString(); 20 | BuildIdentifier = $"{BuildName}: {buildNumber}"; 21 | } 22 | 23 | protected void StartChromeSession(TestContext context) 24 | { 25 | testName = context.TestName; 26 | 27 | var options = new ChromeOptions(); 28 | options.AddUserProfilePreference("profile.password_manager_leak_detection", false); 29 | 30 | options.AddAdditionalOption("sauce:options", DefaultSauceOptions(context)); 31 | 32 | if (string.IsNullOrEmpty(options.PlatformName)) 33 | { 34 | options.PlatformName = "Windows 11"; 35 | } 36 | 37 | driver = new RemoteWebDriver(new Uri("https://ondemand.us-west-1.saucelabs.com/wd/hub"), options); 38 | sessionId = ((RemoteWebDriver)driver).SessionId.ToString(); 39 | } 40 | 41 | private static Dictionary DefaultSauceOptions(TestContext context) 42 | { 43 | return new Dictionary 44 | { 45 | ["username"] = Environment.GetEnvironmentVariable("SAUCE_USERNAME"), 46 | ["accessKey"] = Environment.GetEnvironmentVariable("SAUCE_ACCESS_KEY"), 47 | ["name"] = context.TestName, 48 | ["build"] = BuildIdentifier, 49 | ["seleniumVersion"] = "4.33.0" 50 | }; 51 | } 52 | 53 | protected void QuitSession(TestContext context) 54 | { 55 | try 56 | { 57 | string result = context.CurrentTestOutcome == UnitTestOutcome.Passed ? "passed" : "failed"; 58 | ((IJavaScriptExecutor)driver).ExecuteScript($"sauce:job-result={result}"); 59 | } 60 | catch (Exception e) 61 | { 62 | Console.WriteLine("Problem updating Sauce job result: " + e); 63 | } 64 | finally 65 | { 66 | PrintResults(); 67 | driver?.Quit(); 68 | } 69 | } 70 | 71 | private void PrintResults() 72 | { 73 | Console.WriteLine($"SauceOnDemandSessionID={sessionId} job-name={testName}"); 74 | Console.WriteLine($"Test Job Link: https://app.saucelabs.com/tests/{sessionId}"); 75 | } 76 | } -------------------------------------------------------------------------------- /SeleniumExamples/MSTestExamples/demo/AuthenticationTest.cs: -------------------------------------------------------------------------------- 1 | using Microsoft.VisualStudio.TestTools.UnitTesting; 2 | using OpenQA.Selenium; 3 | 4 | namespace MSTest.demo; 5 | 6 | [TestClass] 7 | public class AuthenticationTest : TestBase 8 | { 9 | public TestContext TestContext { get; set; } 10 | 11 | [TestInitialize] 12 | public void SetupTest() => StartChromeSession(TestContext); 13 | 14 | [TestCleanup] 15 | public void TeardownTest() => QuitSession(TestContext); 16 | 17 | [TestMethod] 18 | public void SignInUnSuccessful() 19 | { 20 | driver.Navigate().GoToUrl("https://www.saucedemo.com/"); 21 | 22 | driver.FindElement(By.CssSelector("input[data-test='username']")).SendKeys("locked_out_user"); 23 | driver.FindElement(By.CssSelector("input[data-test='password']")).SendKeys("secret_sauce"); 24 | driver.FindElement(By.CssSelector("input[data-test='login-button']")).Click(); 25 | 26 | var errorElement = driver.FindElement(By.CssSelector("[data-test='error']")); 27 | Assert.IsTrue( 28 | errorElement.Text.Contains("Sorry, this user has been locked out"), 29 | "Error message not found or incorrect" 30 | ); 31 | } 32 | 33 | [TestMethod] 34 | public void SignInSuccessful() 35 | { 36 | driver.Navigate().GoToUrl("https://www.saucedemo.com/"); 37 | 38 | driver.FindElement(By.CssSelector("input[data-test='username']")).SendKeys("standard_user"); 39 | driver.FindElement(By.CssSelector("input[data-test='password']")).SendKeys("secret_sauce"); 40 | driver.FindElement(By.CssSelector("input[data-test='login-button']")).Click(); 41 | 42 | Assert.AreEqual("https://www.saucedemo.com/inventory.html", driver.Url, "Login Not Successful"); 43 | } 44 | 45 | [TestMethod] 46 | public void Logout() 47 | { 48 | driver.Navigate().GoToUrl("https://www.saucedemo.com/"); 49 | 50 | driver.FindElement(By.CssSelector("input[data-test='username']")).SendKeys("standard_user"); 51 | driver.FindElement(By.CssSelector("input[data-test='password']")).SendKeys("secret_sauce"); 52 | driver.FindElement(By.CssSelector("input[data-test='login-button']")).Click(); 53 | 54 | driver.FindElement(By.Id("react-burger-menu-btn")).Click(); 55 | Thread.Sleep(1000); 56 | 57 | driver.FindElement(By.Id("logout_sidebar_link")).Click(); 58 | 59 | Assert.AreEqual("https://www.saucedemo.com/", driver.Url, "Logout Not Successful"); 60 | } 61 | } -------------------------------------------------------------------------------- /SeleniumExamples/NUnitExamples/AssemblyInfo.cs: -------------------------------------------------------------------------------- 1 | using NUnit.Framework; 2 | 3 | [assembly: LevelOfParallelism(10)] 4 | [assembly: Parallelizable(ParallelScope.All)] -------------------------------------------------------------------------------- /SeleniumExamples/NUnitExamples/NUnit.runsettings: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 5 | 6 | -------------------------------------------------------------------------------- /SeleniumExamples/NUnitExamples/NUnitExamples.csproj: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | Exe 5 | net9.0 6 | enable 7 | enable 8 | NUnitExamples 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | -------------------------------------------------------------------------------- /SeleniumExamples/NUnitExamples/TestBase.cs: -------------------------------------------------------------------------------- 1 | using NUnit.Framework; 2 | using NUnit.Framework.Interfaces; 3 | using OpenQA.Selenium; 4 | using OpenQA.Selenium.Chrome; 5 | using OpenQA.Selenium.Remote; 6 | 7 | namespace NUnitExamples; 8 | 9 | // Immutable build info for clarity 10 | public static class BuildInfo 11 | { 12 | public static readonly string Name = "DotNet NUnit Examples"; 13 | public static readonly string Identifier = $"{Name}: {DateTimeOffset.UtcNow.ToUnixTimeMilliseconds()}"; 14 | } 15 | 16 | [TestFixture] 17 | [FixtureLifeCycle(LifeCycle.InstancePerTestCase)] 18 | public abstract class TestBase 19 | { 20 | public required IWebDriver Driver { get; set; } 21 | 22 | [SetUp] 23 | public void BaseSetUp() 24 | { 25 | string testName = TestContext.CurrentContext.Test.Name; 26 | 27 | var options = new ChromeOptions(); 28 | options.AddUserProfilePreference("profile.password_manager_leak_detection", false); 29 | 30 | var sauceOptions = new Dictionary 31 | { 32 | ["username"] = Environment.GetEnvironmentVariable("SAUCE_USERNAME"), 33 | ["accessKey"] = Environment.GetEnvironmentVariable("SAUCE_ACCESS_KEY"), 34 | ["name"] = testName, 35 | ["build"] = BuildInfo.Identifier, 36 | ["seleniumVersion"] = "4.33.0" 37 | }; 38 | 39 | options.AddAdditionalOption("sauce:options", sauceOptions); 40 | options.PlatformName = "Windows 11"; 41 | 42 | Driver = new RemoteWebDriver(new Uri("https://ondemand.us-west-1.saucelabs.com/wd/hub"), options); 43 | } 44 | 45 | [TearDown] 46 | public void BaseTearDown() 47 | { 48 | try 49 | { 50 | string sessionId = ((RemoteWebDriver)Driver).SessionId.ToString(); 51 | string testName = TestContext.CurrentContext.Test.Name; 52 | string result = TestContext.CurrentContext.Result.Outcome.Status == TestStatus.Passed ? "passed" : "failed"; 53 | 54 | ((IJavaScriptExecutor)Driver).ExecuteScript($"sauce:job-result={result}"); 55 | 56 | Console.WriteLine($"SauceOnDemandSessionID={sessionId} job-name={testName}"); 57 | Console.WriteLine($"Test Job Link: https://app.saucelabs.com/tests/{sessionId}"); 58 | } 59 | catch (Exception e) 60 | { 61 | Console.WriteLine("Problem updating Sauce job result: " + e); 62 | } 63 | finally 64 | { 65 | try 66 | { 67 | Driver.Quit(); 68 | } 69 | catch (Exception e) 70 | { 71 | Console.WriteLine("Problem quitting driver: " + e); 72 | } 73 | } 74 | } 75 | } -------------------------------------------------------------------------------- /SeleniumExamples/NUnitExamples/demo/AuthenticationTest.cs: -------------------------------------------------------------------------------- 1 | using NUnit.Framework; 2 | using OpenQA.Selenium; 3 | 4 | namespace NUnitExamples.demo; 5 | 6 | [TestFixture] 7 | [Parallelizable(ParallelScope.All)] 8 | public class AuthenticationTest : TestBase 9 | { 10 | 11 | [Test] 12 | public void SignInUnSuccessful() 13 | { 14 | Driver.Navigate().GoToUrl("https://www.saucedemo.com/"); 15 | 16 | Driver.FindElement(By.CssSelector("input[data-test='username']")).SendKeys("locked_out_user"); 17 | Driver.FindElement(By.CssSelector("input[data-test='password']")).SendKeys("secret_sauce"); 18 | Driver.FindElement(By.CssSelector("input[data-test='login-button']")).Click(); 19 | 20 | var errorElement = Driver.FindElement(By.CssSelector("[data-test='error']")); 21 | Assert.That(errorElement.Text, Does.Contain("Sorry, this user has been locked out"), 22 | "Error message not found or incorrect"); 23 | } 24 | 25 | [Test] 26 | public void SignInSuccessful() 27 | { 28 | Driver.Navigate().GoToUrl("https://www.saucedemo.com/"); 29 | 30 | Driver.FindElement(By.CssSelector("input[data-test='username']")).SendKeys("standard_user"); 31 | Driver.FindElement(By.CssSelector("input[data-test='password']")).SendKeys("secret_sauce"); 32 | Driver.FindElement(By.CssSelector("input[data-test='login-button']")).Click(); 33 | 34 | Assert.That(Driver.Url, Is.EqualTo("https://www.saucedemo.com/inventory.html"), 35 | "Login Not Successful"); 36 | } 37 | 38 | [Test] 39 | public void Logout() 40 | { 41 | Driver.Navigate().GoToUrl("https://www.saucedemo.com/"); 42 | 43 | Driver.FindElement(By.CssSelector("input[data-test='username']")).SendKeys("standard_user"); 44 | Driver.FindElement(By.CssSelector("input[data-test='password']")).SendKeys("secret_sauce"); 45 | Driver.FindElement(By.CssSelector("input[data-test='login-button']")).Click(); 46 | 47 | Driver.FindElement(By.Id("react-burger-menu-btn")).Click(); 48 | Thread.Sleep(1000); // Not ideal, but matches the Java code 49 | 50 | Driver.FindElement(By.Id("logout_sidebar_link")).Click(); 51 | 52 | Assert.That(Driver.Url, Is.EqualTo("https://www.saucedemo.com/"), 53 | "Logout Not Successful"); 54 | } 55 | } -------------------------------------------------------------------------------- /SeleniumExamples/SeleniumExamples.sln: -------------------------------------------------------------------------------- 1 | 2 | Microsoft Visual Studio Solution File, Format Version 12.00 3 | Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "MSTestExamples", "MSTestExamples\MSTestExamples.csproj", "{BCEC0C39-57D6-4B87-9F84-9C45A209122C}" 4 | EndProject 5 | Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "NUnitExamples", "NUnitExamples\NUnitExamples.csproj", "{D8F5E4A2-B957-4E0D-A8F3-9C45A209122D}" 6 | EndProject 7 | Global 8 | GlobalSection(SolutionConfigurationPlatforms) = preSolution 9 | Debug|Any CPU = Debug|Any CPU 10 | Release|Any CPU = Release|Any CPU 11 | EndGlobalSection 12 | GlobalSection(ProjectConfigurationPlatforms) = postSolution 13 | {BCEC0C39-57D6-4B87-9F84-9C45A209122C}.Debug|Any CPU.ActiveCfg = Debug|Any CPU 14 | {BCEC0C39-57D6-4B87-9F84-9C45A209122C}.Debug|Any CPU.Build.0 = Debug|Any CPU 15 | {BCEC0C39-57D6-4B87-9F84-9C45A209122C}.Release|Any CPU.ActiveCfg = Release|Any CPU 16 | {BCEC0C39-57D6-4B87-9F84-9C45A209122C}.Release|Any CPU.Build.0 = Release|Any CPU 17 | {D8F5E4A2-B957-4E0D-A8F3-9C45A209122D}.Debug|Any CPU.ActiveCfg = Debug|Any CPU 18 | {D8F5E4A2-B957-4E0D-A8F3-9C45A209122D}.Debug|Any CPU.Build.0 = Debug|Any CPU 19 | {D8F5E4A2-B957-4E0D-A8F3-9C45A209122D}.Release|Any CPU.ActiveCfg = Release|Any CPU 20 | {D8F5E4A2-B957-4E0D-A8F3-9C45A209122D}.Release|Any CPU.Build.0 = Release|Any CPU 21 | EndGlobalSection 22 | EndGlobal 23 | -------------------------------------------------------------------------------- /Test-SauceConnectState.Tests.ps1: -------------------------------------------------------------------------------- 1 | $here = Split-Path -Parent $MyInvocation.MyCommand.Path 2 | $sut = (Split-Path -Leaf $MyInvocation.MyCommand.Path) -replace '\.Tests\.', '.' 3 | . "$here\$sut" 4 | 5 | Describe "Get-KgpConnectionStatus" { 6 | $fakeResponse = New-Object -TypeName psobject 7 | $fakeResponse | Add-Member -MemberType NoteProperty -Name StatusCode -Value 200 8 | 9 | Mock ConvertFrom-Json {} 10 | It "Should convert from json if tunnel is up" { 11 | $result = Get-KgpConnectionStatus $fakeResponse 12 | Assert-MockCalled ConvertFrom-Json -Times 1 13 | #Get-KgpConnectionStatus $fakeResponse.StatusCode | Should -Be $true 14 | } 15 | $fakeResponse.StatusCode = 404 16 | It "Should return false if tunnel is down" { 17 | Get-KgpConnectionStatus $fakeResponse | Should -Be $false 18 | } 19 | } 20 | Describe "Test-SauceConnectStatus" { 21 | Mock Get-KgpConnectionStatus {return $true} 22 | Mock Invoke-RestartOperations {} 23 | $result = Test-SauceConnectStatus -IsInfiniteLoop $false 24 | It "Should invoke restart operations when called" { 25 | Assert-MockCalled Invoke-RestartOperations -Times 1 26 | } 27 | It "Should invoke Get-KgpConnectionStatus when called" { 28 | Assert-MockCalled Get-KgpConnectionStatus -Times 1 29 | } 30 | } 31 | 32 | Describe "Restart-SauceConnect"{ 33 | $SauceConnectFilePath = "c:/fake\bin" 34 | $UserName = "nikolay" 35 | $AccessKey = "abc123" 36 | $TunnelIdentifier = "testTunnel" 37 | Mock Invoke-Expression{} 38 | It "Should form a valid string to execute"{ 39 | [string]$Command = Restart-SauceConnect $SauceConnectFilePath $UserName $AccessKey $TunnelIdentifier 40 | $Command | Should -Be "$($SauceConnectFilePath)\sc.exe -u $($UserName) -k $($AccessKey) -i $($TunnelIdentifier) --no-remove-colliding-tunnels -s" 41 | } 42 | It "Should contain bin folder in the path"{ 43 | [string]$Command = Restart-SauceConnect $SauceConnectFilePath $UserName $AccessKey $TunnelIdentifier 44 | $Command | Should -BeLike "*bin\sc.exe -u $($UserName) -k $($AccessKey) -i $($TunnelIdentifier) --no-remove-colliding-tunnels -s" 45 | } 46 | } -------------------------------------------------------------------------------- /Test-SauceConnectState.ps1: -------------------------------------------------------------------------------- 1 | $IntervalForRestart = 120 2 | $SauceConnectFilePath = "C:\Source\SauceLabs\sc-4.5.2-win32\bin" 3 | $UserName = [Environment]::GetEnvironmentVariable("SAUCE_USERNAME", "User") 4 | $AccessKey = [Environment]::GetEnvironmentVariable("SAUCE_ACCESS_KEY", "User") 5 | $TunnelIdentifier = "NikolaysTunnel" 6 | 7 | function Get-KgpConnectionStatus($Response) { 8 | if ($Response.StatusCode -eq 200) { 9 | $json = ConvertFrom-Json $Response.Content 10 | return $json.kgp.Connected 11 | } 12 | return $false 13 | } 14 | function Invoke-SauceConnectDebugUrl() { 15 | try { Invoke-WebRequest -Uri http://localhost:8888/debug/vars -Method get } 16 | catch { return $false } 17 | } 18 | function Test-SauceConnectStatus($IsInfiniteLoop = $true) { 19 | do { 20 | $IsConnected = Get-KgpConnectionStatus -Response (Invoke-SauceConnectDebugUrl) 21 | Invoke-RestartOperations -IsConnected $IsConnected 22 | } while ($IsInfiniteLoop) 23 | } 24 | function Invoke-RestartOperations($IsConnected) { 25 | if($IsConnected -ne $true) 26 | { 27 | Send-Notification 28 | Restart-SauceConnect -SauceConnectFilePath $SauceConnectFilePath -UserName $UserName ` 29 | -AccessKey $AccessKey -TunnelIdentifier $TunnelIdentifier 30 | Wait-SauceConnectToRestart -SecondsToWait $IntervalForRestart 31 | return 32 | } 33 | Start-Sleep -Seconds 15 34 | } 35 | function Restart-SauceConnect($SauceConnectFilePath, $UserName, $AccessKey, $TunnelIdentifier) { 36 | $startSauceCommand = "$($SauceConnectFilePath)\sc.exe -u $($UserName) -k $($AccessKey) -i $($TunnelIdentifier) --no-remove-colliding-tunnels -s" 37 | Invoke-Expression $startSauceCommand 38 | return [string]$startSauceCommand 39 | } 40 | function Send-Notification() { 41 | return Write-Host "Sauce Connect is down! Please restart." 42 | } 43 | function Wait-SauceConnectToRestart($SecondsToWait) { 44 | return Start-Sleep -Seconds $SecondsToWait 45 | } -------------------------------------------------------------------------------- /_config.yml: -------------------------------------------------------------------------------- 1 | theme: jekyll-theme-architect -------------------------------------------------------------------------------- /env-variables-example.yml: -------------------------------------------------------------------------------- 1 | # ASP.NET Core (.NET Framework) 2 | # Build and test ASP.NET Core projects targeting the full .NET Framework. 3 | # Add steps that publish symbols, save build artifacts, and more: 4 | # https://docs.microsoft.com/azure/devops/pipelines/languages/dotnet-core 5 | 6 | trigger: 7 | branches: 8 | include: 9 | - master 10 | exclude: 11 | - 2_hr_workshop 12 | 13 | pool: 14 | vmImage: 'windows-latest' 15 | 16 | variables: 17 | solution: '**/*.sln' 18 | buildPlatform: 'Any CPU' 19 | buildConfiguration: 'Release' 20 | # This makes the variables available to all of our tasks 21 | SAUCE_USERNAME: $(sauceUsername) 22 | SAUCE_ACCESS_KEY: $(sauceKey) 23 | 24 | steps: 25 | - task: NuGetToolInstaller@1 26 | 27 | - task: NuGetCommand@2 28 | inputs: 29 | restoreSolution: '$(solution)' 30 | 31 | - bash: echo $(SAUCE_USERNAME) 32 | - task: VSBuild@1 33 | inputs: 34 | solution: '$(solution)' 35 | msbuildArgs: '/p:DeployOnBuild=true /p:WebPublishMethod=Package /p:PackageAsSingleFile=true /p:SkipInvalidConfigurations=true /p:DesktopBuildPackageLocation="$(build.artifactStagingDirectory)\WebApp.zip" /p:DeployIisAppPath="Default Web Site"' 36 | platform: '$(buildPlatform)' 37 | configuration: '$(buildConfiguration)' 38 | 39 | - task: DotNetCoreCLI@2 40 | displayName: 'Run tests' 41 | inputs: 42 | command: test 43 | projects: '**/core.selenium*/*.csproj' 44 | arguments: '--configuration $(buildConfiguration)' 45 | env: 46 | SAUCE_USERNAME: $(sauceUsername) 47 | SAUCE_ACCESS_KEY: $(sauceKey) 48 | 49 | 50 | 51 | -------------------------------------------------------------------------------- /index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | Redirecting... 4 | 5 | 6 | -------------------------------------------------------------------------------- /setEnvironmentVariables.ps1: -------------------------------------------------------------------------------- 1 | 2 | Param( 3 | [string]$sauceUserName, 4 | [string]$sauceAccessKey, 5 | [string]$rdcVodQaNativeAppApiKey, 6 | [string]$rdcSauceDemoIosRdcApiKey 7 | ) 8 | Write-Output "sauce.userName value from ADO was passed as an Argument in the ADO Task called `$env:SAUCE_USERNAME` 9 | to sauceUserName variable in the Posh. This is the value found=>$sauceUserName" 10 | Write-Output "sauce.accessKey value from ADO was passed as an Argument in the ADO Task called `$env:SAUCE_ACCESS_KEY` 11 | to sauceAccessKey variable in the Posh. This is the value found=>$sauceAccessKey" 12 | Write-Output "sauce.rdc.VodQaNativeAppApiKey stored in Azure DevOps=>$rdcVodQaNativeAppApiKey" 13 | Write-Output "sauce.rdc.SauceDemoIosRdcApiKey stored in Azure DevOps=>$rdcSauceDemoIosRdcApiKey" 14 | 15 | [Environment]::SetEnvironmentVariable("SAUCE_USERNAME", "$sauceUserName", "User") 16 | [Environment]::SetEnvironmentVariable("SAUCE_ACCESS_KEY", "$sauceAccessKey", "User") 17 | [Environment]::SetEnvironmentVariable("VODQC_RDC_API_KEY", "$rdcVodQaNativeAppApiKey", "User") 18 | [Environment]::SetEnvironmentVariable("SAUCE_DEMO_IOS_RDC_API_KEY", "$rdcSauceDemoIosRdcApiKey", "User") 19 | -------------------------------------------------------------------------------- /workshop.yml: -------------------------------------------------------------------------------- 1 | # ASP.NET 2 | # Build and test ASP.NET projects. 3 | # Add steps that publish symbols, save build artifacts, deploy, and more: 4 | # https://docs.microsoft.com/azure/devops/pipelines/apps/aspnet/build-aspnet-4 5 | 6 | trigger: 7 | branches: 8 | include: 9 | - 2_hr_workshop 10 | 11 | pool: 12 | vmImage: 'windows-latest' 13 | 14 | variables: 15 | solution: '**/*.sln' 16 | buildPlatform: 'Any CPU' 17 | buildConfiguration: 'Release' 18 | # This makes the variables available to all of our tasks 19 | SAUCE_USERNAME: $(sauceUsername) 20 | SAUCE_ACCESS_KEY: $(sauceKey) 21 | 22 | steps: 23 | - task: UseDotNet@2 24 | inputs: 25 | version: '5.0.x' 26 | 27 | - task: DotNetCoreCLI@2 28 | displayName: 'dotnet build' 29 | inputs: 30 | command: 'build' 31 | projects: '**DotnetCore/Sauce.Demo/Core.Selenium.Examples/**.csproj' 32 | configuration: $(buildConfiguration) 33 | 34 | - bash: echo $(SAUCE_USERNAME) 35 | - task: DotNetCoreCLI@2 36 | displayName: 'Run desktop tests' 37 | inputs: 38 | command: test 39 | projects: '**DotnetCore/Sauce.Demo/Core.Selenium.Examples/**.csproj' 40 | arguments: '--configuration $(buildConfiguration) --filter TestCategory=desktop' 41 | # Can also reference env variables in a single task 42 | env: 43 | SAUCE_USERNAME: $(sauceUsername) 44 | SAUCE_ACCESS_KEY: $(sauceKey) 45 | --------------------------------------------------------------------------------