├── .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