diff --git a/docs/articles/available-packages.md b/docs/articles/archive/available-packages.md similarity index 100% rename from docs/articles/available-packages.md rename to docs/articles/archive/available-packages.md diff --git a/docs/articles/best-practices.md b/docs/articles/archive/best-practices.md similarity index 100% rename from docs/articles/best-practices.md rename to docs/articles/archive/best-practices.md diff --git a/docs/articles/building-platform-wrappers.md b/docs/articles/archive/building-platform-wrappers.md similarity index 100% rename from docs/articles/building-platform-wrappers.md rename to docs/articles/archive/building-platform-wrappers.md diff --git a/docs/articles/building-with-legerity.md b/docs/articles/archive/building-with-legerity.md similarity index 100% rename from docs/articles/building-with-legerity.md rename to docs/articles/archive/building-with-legerity.md diff --git a/docs/articles/archive/features/android.md b/docs/articles/archive/features/android.md new file mode 100644 index 00000000..6e8dcb6c --- /dev/null +++ b/docs/articles/archive/features/android.md @@ -0,0 +1,23 @@ +--- +uid: android-element-wrappers +title: Using the Android element wrappers +--- + +# Using the Android element wrappers + +The goal of the platform element wrappers is to provide an easy set of elements that surface up properties and actions of the actual controls within the UI to make it easier for you to write tests that interact with them. + +These Android element wrappers are designed to be used with applications built for the Android platform using out-of-the-box SDK control. + +## Available element wrappers + +- [Button](xref:Legerity.Android.Elements.Core.Button) +- [CheckBox](xref:Legerity.Android.Elements.Core.CheckBox) +- [DatePicker](xref:Legerity.Android.Elements.Core.DatePicker) +- [EditText](xref:Legerity.Android.Elements.Core.EditText) +- [RadioButton](xref:Legerity.Android.Elements.Core.RadioButton) +- [Spinner](xref:Legerity.Android.Elements.Core.Spinner) +- [Switch](xref:Legerity.Android.Elements.Core.Switch) +- [TextView](xref:Legerity.Android.Elements.Core.TextView) +- [ToggleButton](xref:Legerity.Android.Elements.Core.ToggleButton) +- [View](xref:Legerity.Android.Elements.Core.View) diff --git a/docs/articles/features/ios.md b/docs/articles/archive/features/ios.md similarity index 100% rename from docs/articles/features/ios.md rename to docs/articles/archive/features/ios.md diff --git a/docs/articles/features/made-windows.md b/docs/articles/archive/features/made-windows.md similarity index 73% rename from docs/articles/features/made-windows.md rename to docs/articles/archive/features/made-windows.md index 6858db99..2d0a125c 100644 --- a/docs/articles/features/made-windows.md +++ b/docs/articles/archive/features/made-windows.md @@ -11,5 +11,5 @@ These Windows element wrappers are designed to be used with applications built w ## Available element wrappers -- [DropDownList](https://github.com/MADE-Apps/legerity/blob/main/src/Legerity.MADE/DropDownList.cs) -- [InputValidator](https://github.com/MADE-Apps/legerity/blob/main/src/Legerity.MADE/InputValidator.cs) +- [DropDownList](xref:Legerity.Windows.Elements.MADE.DropDownList) +- [InputValidator](xref:Legerity.Windows.Elements.MADE.InputValidator) diff --git a/docs/articles/features/telerik-for-uwp.md b/docs/articles/archive/features/telerik-for-uwp.md similarity index 56% rename from docs/articles/features/telerik-for-uwp.md rename to docs/articles/archive/features/telerik-for-uwp.md index 7bea5edf..23defa7b 100644 --- a/docs/articles/features/telerik-for-uwp.md +++ b/docs/articles/archive/features/telerik-for-uwp.md @@ -11,7 +11,7 @@ These Windows element wrappers are designed to be used with applications built w ## Available element wrappers -- [RadAutoCompleteBox](https://github.com/MADE-Apps/legerity/blob/main/src/Legerity.Telerik.Uwp/RadAutoCompleteBox.cs) -- [RadBulletGraph](https://github.com/MADE-Apps/legerity/blob/main/src/Legerity.Telerik.Uwp/RadBulletGraph.cs) -- [RadBusyIndicator](https://github.com/MADE-Apps/legerity/blob/main/src/Legerity.Telerik.Uwp/RadBusyIndicator.cs) -- [RadNumericBox](https://github.com/MADE-Apps/legerity/blob/main/src/Legerity.Telerik.Uwp/RadNumericBox.cs) +- [RadAutoCompleteBox](xref:Legerity.Windows.Elements.Telerik.RadAutoCompleteBox) +- [RadBulletGraph](xref:Legerity.Windows.Elements.Telerik.RadBulletGraph) +- [RadBusyIndicator](xref:Legerity.Windows.Elements.Telerik.RadBusyIndicator) +- [RadNumericBox](xref:Legerity.Windows.Elements.Telerik.RadNumericBox) diff --git a/docs/articles/features/web-authentication.md b/docs/articles/archive/features/web-authentication.md similarity index 100% rename from docs/articles/features/web-authentication.md rename to docs/articles/archive/features/web-authentication.md diff --git a/docs/articles/features/web.md b/docs/articles/archive/features/web.md similarity index 100% rename from docs/articles/features/web.md rename to docs/articles/archive/features/web.md diff --git a/docs/articles/features/windows-community-toolkit.md b/docs/articles/archive/features/windows-community-toolkit.md similarity index 100% rename from docs/articles/features/windows-community-toolkit.md rename to docs/articles/archive/features/windows-community-toolkit.md diff --git a/docs/articles/features/windows-ui.md b/docs/articles/archive/features/windows-ui.md similarity index 100% rename from docs/articles/features/windows-ui.md rename to docs/articles/archive/features/windows-ui.md diff --git a/docs/articles/features/windows.md b/docs/articles/archive/features/windows.md similarity index 100% rename from docs/articles/features/windows.md rename to docs/articles/archive/features/windows.md diff --git a/docs/articles/features/android.md b/docs/articles/features/android.md deleted file mode 100644 index 9e85c463..00000000 --- a/docs/articles/features/android.md +++ /dev/null @@ -1,23 +0,0 @@ ---- -uid: android-element-wrappers -title: Using the Android element wrappers ---- - -# Using the Android element wrappers - -The goal of the platform element wrappers is to provide an easy set of elements that surface up properties and actions of the actual controls within the UI to make it easier for you to write tests that interact with them. - -These Android element wrappers are designed to be used with applications built for the Android platform using out-of-the-box SDK control. - -## Available element wrappers - -- [Button](https://github.com/MADE-Apps/legerity/blob/main/src/Legerity.Android/Elements/Core/Button.cs) -- [CheckBox](https://github.com/MADE-Apps/legerity/blob/main/src/Legerity.Android/Elements/Core/CheckBox.cs) -- [DatePicker](https://github.com/MADE-Apps/legerity/blob/main/src/Legerity.Android/Elements/Core/DatePicker.cs) -- [EditText](https://github.com/MADE-Apps/legerity/blob/main/src/Legerity.Android/Elements/Core/EditText.cs) -- [RadioButton](https://github.com/MADE-Apps/legerity/blob/main/src/Legerity.Android/Elements/Core/RadioButton.cs) -- [Spinner](https://github.com/MADE-Apps/legerity/blob/main/src/Legerity.Android/Elements/Core/Spinner.cs) -- [Switch](https://github.com/MADE-Apps/legerity/blob/main/src/Legerity.Android/Elements/Core/Switch.cs) -- [TextView](https://github.com/MADE-Apps/legerity/blob/main/src/Legerity.Android/Elements/Core/TextView.cs) -- [ToggleButton](https://github.com/MADE-Apps/legerity/blob/main/src/Legerity.Android/Elements/Core/ToggleButton.cs) -- [View](https://github.com/MADE-Apps/legerity/blob/main/src/Legerity.Android/Elements/Core/View.cs) diff --git a/docs/articles/getting-started.md b/docs/articles/getting-started.md new file mode 100644 index 00000000..5e2b6ff6 --- /dev/null +++ b/docs/articles/getting-started.md @@ -0,0 +1,36 @@ +--- +uid: getting-started +title: Getting Started +--- + +# Getting started + +Getting started with Legerity is quick and easy! Legerity is distributed via NuGet.org, allowing you to install via a package manager. + +## Supported platforms + +Legerity packages target [.NET Standard 2.0](https://learn.microsoft.com/en-us/dotnet/standard/net-standard?tabs=net-standard-2-0#select-net-standard-version). This means that you can create UI tests with [.NET](https://learn.microsoft.com/en-us/dotnet/fundamentals/implementations#net-5-and-later-versions), .NET Core, [.NET Framework](https://learn.microsoft.com/en-us/dotnet/fundamentals/implementations#net-framework), and [Mono](https://learn.microsoft.com/en-us/dotnet/fundamentals/implementations#mono)! + +## Installing + +Releases of Legerity are published to [NuGet.org](https://www.nuget.org/packages?q=legerity) and can be installed via your preferred package manager. + +To quickly get started with a specific platform, select one of the following quick starts: + +- **[Windows](xref:quick_starts_windows)**: Create UI tests for Windows apps using [WinAppDriver](https://github.com/microsoft/WinAppDriver) +- **[Android](xref:quick_starts_android)**: Take advantage of the [Appium](https://appium.io/) ecosystem to create UI tests for Android apps +- **[iOS](xref:quick_starts_ios)**: Build UI tests for iOS apps with the power of [Appium](https://appium.io/) +- **[Web](xref:quick_starts_web)**: Use your favourite browser to create UI tests for web apps using web drivers +- **[Cross-platform](xref:quick_starts_cross_platform)**: Create easy-to-maintain UI tests across multiple platforms with a single test + +## Note on terminology + +Throughout the documentation, you may see terms that are specific to Legerity. + +A _page_ or _[page object](xref:using_legerity_page_objects)_ is a class that represents a single page in your app. This is typically a class that inherits from `BasePage` in Legerity and is used to interact with the UI elements on that page. + +An _[element wrapper](xref:using_legerity_element_wrappers)_ is a class that represents a single UI element, e.g. an input control. It is a wrapper around the `IWebElement` interface from Selenium, allowing Legerity to provide bespoke interaction methods for the represented UI element. This is typically a class that implements the `IElementWrapper` interface in Legerity and is used to interact with the UI element. + +## Let's get started + +If you're not sure which platform to start with, we recommend starting with the [Web](xref:quick_starts_web) quick start. This will give you a great overview of Legerity, how to create UI tests, and how to run them. diff --git a/docs/articles/getting-started/quick-starts.md b/docs/articles/getting-started/quick-starts.md new file mode 100644 index 00000000..db01c733 --- /dev/null +++ b/docs/articles/getting-started/quick-starts.md @@ -0,0 +1,17 @@ +--- +uid: getting_started_quick_starts +title: Quick Starts +--- + +# Quick starts + +These quick starts provide very minimal examples for getting your first test running on specific platforms to help you get started with using Legerity. + +> [!NOTE] +> If you'd like to dive into the details, explore our articles on [using Legerity](xref:using_legerity). + +- [Windows](xref:quick_starts_windows) +- [Android](xref:quick_starts_android) +- [iOS](xref:quick_starts_ios) +- [Web](xref:quick_starts_web) +- [Cross-platform](xref:quick_starts_cross_platform) diff --git a/docs/articles/getting-started/quick-starts/android.md b/docs/articles/getting-started/quick-starts/android.md new file mode 100644 index 00000000..187e8ae3 --- /dev/null +++ b/docs/articles/getting-started/quick-starts/android.md @@ -0,0 +1,71 @@ +--- +uid: quick_starts_android +title: Android Quick Start +--- + +# Android + +> This quick start will get you up and running with writing UI tests for an Android application using [Appium](https://appium.io/) and Legerity. + +## Prerequisites + +This quick start requires the following: + +- An understanding of Selenium and working with the [WebDriver](https://www.selenium.dev/documentation/webdriver/) APIs in .NET +- An understanding of Appium and [working with the capabilities for Android](https://appium.io/docs/en/drivers/android-uiautomator2/) +- A functioning installation of the [.NET runtime and SDK](https://dotnet.microsoft.com/en-us/download) + +## Install Legerity templates + +Legerity includes project templates to simplify the creation of new UI test projects. To install the templates, run the following command: + +```powershell +dotnet new -i Legerity.Templates +``` + +When creating a project, the template will automatically add to an existing solution file if it can locate one, otherwise you will have to add it manually. + +## Create a new Android UI test project with NUnit + +To create a new Android UI test project with NUnit in your existing repository, created a new project folder and from within it, run the following command: + +```powershell +dotnet new legerity-android +``` + +This will create a new Android UI test project with the following structure: + +```text +MyProject +├── Elements +├── Pages +│ └── SamplePage.cs +├── Tests +│ └── SampleTests.cs +├── BaseTestClass.cs +├── GlobalUsing.cs +├── MyProject.csproj +``` + +The project will include dependencies for NUnit, Appium, and Legerity for Android. + +The `BaseTestClass` class is a simple abstraction used for all of your test classes, based on the Legerity `LegerityTestClass`. The [base test class](xref:using_legerity_test_classes#the-base-test-class) is a great way to centralize common logic for your tests, to abstract the boilerplate code away from your tests, such as managing the application drivers. + +> [!NOTE] +> The `BaseTestClass` in this template shows how to configure the application to launch and the activity you want to launch to. This should be updated to launch your own application via the `AndroidApplication` and `AndroidApplicationActivity` constants. + +The `SamplePage` and `SampleTests` classes are used to show the basic structure of a [page object](xref:using_legerity_page_objects) and [test class](xref:using_legerity_test_classes). In this template, they highlight finding a button by ID and verifying it is shown. + +> [!NOTE] +> The `SamplePage` and `SampleTests` classes are intended to be used as a guide for structuring tests. These should be removed and replaced with your own tests and page objects. + +## Dive into more + +Now that you have your test project up, you can dive deeper into: + +- [Using the base test class](xref:using_legerity_base_test_class) +- [Creating page objects](xref:using_legerity_page_objects) +- [Creating test classes](xref:using_legerity_test_classes) +- [Locating elements with Legerity by locators](xref:using_legerity_by_locators) +- [Using and creating element wrappers](xref:using_legerity_element_wrappers) +- [Using custom conditions to wait for elements](xref:using_legerity_wait_conditions) diff --git a/docs/articles/getting-started/quick-starts/cross-platform.md b/docs/articles/getting-started/quick-starts/cross-platform.md new file mode 100644 index 00000000..80e1bdd4 --- /dev/null +++ b/docs/articles/getting-started/quick-starts/cross-platform.md @@ -0,0 +1,85 @@ +--- +uid: quick_starts_cross_platform +title: Cross Platform Quick Start +--- + +# Cross Platform + +> This quick start will get you up and running with writing cross-platform UI tests for Android, iOS, Windows, and Web using Legerity. The approach is cross-platform framework agnostic, so whether you've built your app with Xamarin, Flutter, React Native, or any other cross-platform framework, you can use Legerity to write cross-platform UI tests for it. + +## Prerequisites + +This quick start requires the following: + +- An understanding of Selenium and working with the [WebDriver](https://www.selenium.dev/documentation/webdriver/) APIs in .NET +- An understanding of [Appium](https://appium.io/) +- A functioning installation of the [.NET runtime and SDK](https://dotnet.microsoft.com/en-us/download) +- For iOS, a functioning installation of [Xcode](https://developer.apple.com/xcode/) on macOS with simulators installed +- For Windows, a Windows 10 or Windows 11 device with the [WinAppDriver](https://github.com/microsoft/WinAppDriver) installed + +## Install Legerity templates + +Legerity includes project templates to simplify the creation of new UI test projects. To install the templates, run the following command: + +```powershell +dotnet new -i Legerity.Templates +``` + +When creating a project, the template will automatically add to an existing solution file if it can locate one, otherwise you will have to add it manually. + +## Create a new cross-platform UI test project with NUnit + +To create a new cross-platform UI test project with NUnit in your existing repository, created a new project folder and from within it, run the following command: + +```powershell +dotnet new legerity-xplat +``` + +This will create a new cross-platform UI test project with the following structure: + +```text +MyProject +├── Elements +├── Pages +│ └── SamplePage.cs +├── Tests +│ └── SampleTests.cs +├── BaseTestClass.cs +├── GlobalUsing.cs +├── MyProject.csproj +``` + +The project will include dependencies for NUnit, Appium, and Legerity for Windows (including WinUI), Android, iOS, and Web. + +> [!NOTE] +> The ChromeDriver is used to demonstrate the use of the Web driver. You can run your UI tests on as many browsers as you like by installing the relevant driver NuGet package into the project and providing additional `PlatformOptions` to your `BaseTestClass`. +> Legerity supports Chrome, Firefox, Opera, Safari, Edge, and Internet Explorer. + +The `BaseTestClass` class is a simple abstraction used for all of your test classes, based on the Legerity `LegerityTestClass`. The [base test class](xref:using_legerity_test_classes#the-base-test-class) is a great way to centralize common logic for your tests, to abstract the boilerplate code away from your tests, such as managing the application drivers. + +The `PlatformOptions` property is used to configure all the various cross-platform drivers for the applications you want to test. You configure as many, or as few, platforms as you desire. + +> [!NOTE] +> In this template, the `PlatformOptions` property is configured to launch cross-platform UI tests for Android, iOS, Windows, and Web. + +The `SamplePage` and `SampleTests` classes are used to show the basic structure of a [page object](xref:using_legerity_page_objects) and [test class](xref:using_legerity_test_classes). + +In this template, they highlight how to use the `App` property of the `BasePage` configured during instantiation to find a UI element specific to a platform. + +This approach works well for applications that have a similar user experience across each platform. + +This same approach can be used on the [element wrappers](xref:using_legerity_element_wrappers) to abstract the platform-specific logic away from your tests, ensuring an easier interface when using them in your page objects and tests. + +> [!NOTE] +> The `SamplePage` and `SampleTests` classes are intended to be used as a guide for structuring tests. These should be removed and replaced with your own tests and page objects. + +## Dive into more + +Now that you have your test project up and running, you can dive deeper into: + +- [Using the base test class](xref:using_legerity_base_test_class) +- [Creating page objects](xref:using_legerity_page_objects) +- [Creating test classes](xref:using_legerity_test_classes) +- [Locating elements with Legerity by locators](xref:using_legerity_by_locators) +- [Using and creating element wrappers](xref:using_legerity_element_wrappers) +- [Using custom conditions to wait for elements](xref:using_legerity_wait_conditions) diff --git a/docs/articles/getting-started/quick-starts/ios.md b/docs/articles/getting-started/quick-starts/ios.md new file mode 100644 index 00000000..bf7fb198 --- /dev/null +++ b/docs/articles/getting-started/quick-starts/ios.md @@ -0,0 +1,75 @@ +--- +uid: quick_starts_ios +title: iOS Quick Start +--- + +# iOS + +> This quick start will get you up and running with writing UI tests for an iOS application using [Appium](https://appium.io/) and Legerity. + +> [!NOTE] +> It is recommended that a macOS device is used for this quick start. + +## Prerequisites + +This quick start requires the following: + +- An understanding of Selenium and working with the [WebDriver](https://www.selenium.dev/documentation/webdriver/) APIs in .NET +- An understanding of Appium and [working with the capabilities for iOS](https://appium.io/docs/en/drivers/ios-xcuitest/index.html) +- A functioning installation of the [.NET runtime and SDK](https://dotnet.microsoft.com/en-us/download) +- A functioning installation of [Xcode](https://developer.apple.com/xcode/) on macOS with simulators installed + +## Install Legerity templates + +Legerity includes project templates to simplify the creation of new UI test projects. To install the templates, run the following command: + +```powershell +dotnet new -i Legerity.Templates +``` + +When creating a project, the template will automatically add to an existing solution file if it can locate one, otherwise you will have to add it manually. + +## Create a new iOS UI test project with NUnit + +To create a new iOS UI test project with NUnit in your existing repository, created a new project folder and from within it, run the following command: + +```powershell +dotnet new legerity-ios +``` + +This will create a new iOS UI test project with the following structure: + +```text +MyProject +├── Elements +├── Pages +│ └── SamplePage.cs +├── Tests +│ └── SampleTests.cs +├── BaseTestClass.cs +├── GlobalUsing.cs +├── MyProject.csproj +``` + +The project will include dependencies for NUnit, Appium, and Legerity for iOS. + +The `BaseTestClass` class is a simple abstraction used for all of your test classes, based on the Legerity `LegerityTestClass`. The [base test class](xref:using_legerity_test_classes#the-base-test-class) is a great way to centralize common logic for your tests, to abstract the boilerplate code away from your tests, such as managing the application drivers. + +> [!NOTE] +> The `BaseTestClass` in this template shows how to configure the application to launch on an iPhone SE (3rd generation) Simulator. This should be updated to launch your own application via the `IOSApplication` constant and the device configuration in the `PlatformOptions`. + +The `SamplePage` and `SampleTests` classes are used to show the basic structure of a [page object](xref:using_legerity_page_objects) and [test class](xref:using_legerity_test_classes). In this template, they highlight finding a button by name and verifying it is shown. + +> [!NOTE] +> The `SamplePage` and `SampleTests` classes are intended to be used as a guide for structuring tests. These should be removed and replaced with your own tests and page objects. + +## Dive into more + +Now that you have your test project up, you can dive deeper into: + +- [Using the base test class](xref:using_legerity_base_test_class) +- [Creating page objects](xref:using_legerity_page_objects) +- [Creating test classes](xref:using_legerity_test_classes) +- [Locating elements with Legerity by locators](xref:using_legerity_by_locators) +- [Using and creating element wrappers](xref:using_legerity_element_wrappers) +- [Using custom conditions to wait for elements](xref:using_legerity_wait_conditions) diff --git a/docs/articles/getting-started/quick-starts/web.md b/docs/articles/getting-started/quick-starts/web.md new file mode 100644 index 00000000..a38b90d9 --- /dev/null +++ b/docs/articles/getting-started/quick-starts/web.md @@ -0,0 +1,84 @@ +--- +uid: quick_starts_web +title: Web Quick Start +--- + +# Web + +> This quick start will get you up and running with writing UI tests for a Web application using the [ChromeDriver](https://chromedriver.chromium.org/downloads) and Legerity. + +## Prerequisites + +This quick start requires the following: + +- An understanding of Selenium and working with the [WebDriver](https://www.selenium.dev/documentation/webdriver/) APIs in .NET +- A functioning installation of the [.NET runtime and SDK](https://dotnet.microsoft.com/en-us/download) + +## Install Legerity templates + +Legerity includes project templates to simplify the creation of new UI test projects. To install the templates, run the following command: + +```powershell +dotnet new -i Legerity.Templates +``` + +When creating a project, the template will automatically add to an existing solution file if it can locate one, otherwise you will have to add it manually. + +## Create a new Web UI test project with NUnit + +To create a new Web UI test project with NUnit in your existing repository, created a new project folder and from within it, run the following command: + +```powershell +dotnet new legerity-web +``` + +This will create a new Web UI test project with the following structure: + +```text +MyProject +├── Elements +├── Pages +│ └── SamplePage.cs +├── Tests +│ └── SampleTests.cs +├── BaseTestClass.cs +├── GlobalUsing.cs +├── MyProject.csproj +``` + +The project will include dependencies for NUnit, Selenium, ChromeDriver, and Legerity for Web. + +> [!NOTE] +> The ChromeDriver is used to demonstrate the use of the Web driver. You can run your UI tests on as many browsers as you like by installing the relevant driver NuGet package into the project and providing additional `PlatformOptions` to your `BaseTestClass`. +> Legerity supports Chrome, Firefox, Opera, Safari, Edge, and Internet Explorer. + +The `BaseTestClass` class is a simple abstraction used for all of your test classes, based on the Legerity `LegerityTestClass`. The [base test class](xref:using_legerity_test_classes#the-base-test-class) is a great way to centralize common logic for your tests, to abstract the boilerplate code away from your tests, such as managing the application drivers. + +> [!NOTE] +> The `BaseTestClass` in this template is currently configured to launch `https://www.example.com`. This should be updated to launch your own application via the `WebApplication` constant. + +The `SamplePage` and `SampleTests` classes are used to show the basic structure of a [page object](xref:using_legerity_page_objects) and [test class](xref:using_legerity_test_classes). In this template, they highlight locating the main `h1` tag and verifying the element is shown. + +> [!NOTE] +> The `SamplePage` and `SampleTests` classes are intended to be used as a guide for structuring tests. These should be removed and replaced with your own tests and page objects. + +### Run the tests + +To run the tests, you can use the following command from the UI test project: + +```powershell +dotnet test +``` + +Chrome should launch at the expected URL and the tests should pass. + +## Dive into more + +Now that you have your test project up and running, you can dive deeper into: + +- [Using the base test class](xref:using_legerity_base_test_class) +- [Creating page objects](xref:using_legerity_page_objects) +- [Creating test classes](xref:using_legerity_test_classes) +- [Locating elements with Legerity by locators](xref:using_legerity_by_locators) +- [Using and creating element wrappers](xref:using_legerity_element_wrappers) +- [Using custom conditions to wait for elements](xref:using_legerity_wait_conditions) diff --git a/docs/articles/getting-started/quick-starts/windows.md b/docs/articles/getting-started/quick-starts/windows.md new file mode 100644 index 00000000..1d83b66f --- /dev/null +++ b/docs/articles/getting-started/quick-starts/windows.md @@ -0,0 +1,81 @@ +--- +uid: quick_starts_windows +title: Windows Quick Start +--- + +# Windows + +> This quick start will get you up and running with writing UI tests for a Windows (UWP) application using the [WinAppDriver](https://github.com/microsoft/WinAppDriver) and Legerity. + +## Prerequisites + +This quick start requires the following: + +- An understanding of Selenium and working with the [WebDriver](https://www.selenium.dev/documentation/webdriver/) APIs in .NET +- A functioning installation of the [.NET runtime and SDK](https://dotnet.microsoft.com/en-us/download) +- A Windows 10 or Windows 11 device with the [WinAppDriver](https://github.com/microsoft/WinAppDriver) installed + +## Install Legerity templates + +Legerity includes project templates to simplify the creation of new UI test projects. To install the templates, run the following command: + +```powershell +dotnet new -i Legerity.Templates +``` + +When creating a project, the template will automatically add to an existing solution file if it can locate one, otherwise you will have to add it manually. + +## Create a new Windows UI test project with NUnit + +To create a new Windows UI test project with NUnit in your existing repository, created a new project folder and from within it, run the following command: + +```powershell +dotnet new legerity-windows +``` + +This will create a new Windows UI test project with the following structure: + +```text +MyProject +├── Elements +├── Pages +│ └── SamplePage.cs +├── Tests +│ └── SampleTests.cs +├── BaseTestClass.cs +├── GlobalUsing.cs +├── MyProject.csproj +``` + +The project will include dependencies for NUnit, Appium, and Legerity for Windows (including WinUI). + +The `BaseTestClass` class is a simple abstraction used for all of your test classes, based on the Legerity `LegerityTestClass`. The [base test class](xref:using_legerity_test_classes#the-base-test-class) is a great way to centralize common logic for your tests, to abstract the boilerplate code away from your tests, such as managing the application drivers. + +> [!NOTE] +> The `BaseTestClass` in this template is currently configured to launch the Clock application on Windows 11. This should be updated to launch your own application via the `WindowsApplication` constant. + +The `SamplePage` and `SampleTests` classes are used to show the basic structure of a [page object](xref:using_legerity_page_objects) and [test class](xref:using_legerity_test_classes). In this template, they highlight the launch and verification of the landing page of the Clock application on Windows 11. + +> [!NOTE] +> The `SamplePage` and `SampleTests` classes are intended to be used as a guide for structuring tests. These should be removed and replaced with your own tests and page objects. + +### Run the tests + +To run the tests, you can use the following command from the UI test project: + +```powershell +dotnet test +``` + +The Clock application should launch and the tests should pass. + +## Dive into more + +Now that you have your test project up and running, you can dive deeper into: + +- [Using the base test class](xref:using_legerity_base_test_class) +- [Creating page objects](xref:using_legerity_page_objects) +- [Creating test classes](xref:using_legerity_test_classes) +- [Locating elements with Legerity by locators](xref:using_legerity_by_locators) +- [Using and creating element wrappers](xref:using_legerity_element_wrappers) +- [Using custom conditions to wait for elements](xref:using_legerity_wait_conditions) diff --git a/docs/articles/intro.md b/docs/articles/intro.md index 1dc9a2ae..b5f52087 100644 --- a/docs/articles/intro.md +++ b/docs/articles/intro.md @@ -1,6 +1,6 @@ --- -uid: legerity-intro -title: Overview +uid: intro +title: Introduction --- Legerity project banner @@ -41,10 +41,10 @@ There are many tools that are available to help you understand the layout of you Here are helpful links to each platform where you can find more information on the right tooling for the app's you're testing. -- [Windows](features/windows.md) -- [Android](features/android.md) -- [iOS](features/ios.md) -- [Web](features/web.md) +- [Windows](xref:quick_starts_windows) +- [Android](xref:quick_starts_android) +- [iOS](xref:quick_starts_ios) +- [Web](xref:quick_starts_web) ### How do you keep Legerity going? diff --git a/docs/articles/toc.yml b/docs/articles/toc.yml index 54c677a0..e5278ee8 100644 --- a/docs/articles/toc.yml +++ b/docs/articles/toc.yml @@ -1,40 +1,40 @@ -- name: Overview - topicHref: intro.md -- name: Get started +- name: Introduction + href: intro.md +- name: Getting started + href: getting-started.md + expanded: true items: - - name: Installing Legerity packages - href: available-packages.md -- name: Building UI tests with Legerity - href: building-with-legerity.md -- name: Best practices for UI tests - href: best-practices.md -- name: Building platform element wrappers - href: building-platform-wrappers.md -- name: Available element wrappers - items: - - name: For Windows apps - items: - - name: Overview - href: features/windows.md - - name: WinUI - href: features/windows-ui.md - - name: Windows Community Toolkit - href: features/windows-community-toolkit.md - - name: MADE.NET - href: features/made-windows.md - - name: Telerik for UWP - href: features/telerik-for-uwp.md - - name: For Android apps - items: - - name: Overview - href: features/android.md - - name: For iOS apps + - name: Quick starts + href: getting-started/quick-starts.md + expanded: true items: - - name: Overview - href: features/ios.md - - name: For Web apps - items: - - name: Overview - href: features/web.md - - name: Authentication Helpers - href: features/web-authentication.md + - name: Windows + href: getting-started/quick-starts/windows.md + - name: Android + href: getting-started/quick-starts/android.md + - name: iOS + href: getting-started/quick-starts/ios.md + - name: Web + href: getting-started/quick-starts/web.md + - name: Cross platform + href: getting-started/quick-starts/cross-platform.md +- name: Using Legerity + href: using-legerity.md + expanded: true + items: + - name: Installation + href: using-legerity/installation.md + - name: Test Frameworks + href: using-legerity/test-frameworks.md + - name: Test Classes + href: using-legerity/test-classes.md + - name: App Manager + href: using-legerity/app-manager.md + - name: Element Wrappers + href: using-legerity/element-wrappers.md + - name: Page Objects + href: using-legerity/page-objects.md + - name: By Locators + href: using-legerity/by-locators.md + - name: Wait Conditions + href: using-legerity/wait-conditions.md diff --git a/docs/articles/using-legerity.md b/docs/articles/using-legerity.md new file mode 100644 index 00000000..5d88a5bc --- /dev/null +++ b/docs/articles/using-legerity.md @@ -0,0 +1,20 @@ +--- +uid: using_legerity +title: Using Legerity +--- + +# Using Legerity + +To use and understand Legerity, this documentation has been organized to start with the basics and then build up to more advanced topics. + +> [!NOTE] +> To get started quickly, you can explore our [quick starts](xref:getting_started_quick_starts) to use our UI test project templates to get up and running with Legerity. + +- [Installation](xref:using_legerity_installation) +- [Test Frameworks](xref:using_legerity_test_frameworks) +- [Test Classes](xref:using_legerity_test_classes) +- [App Manager](xref:using_legerity_app_manager) +- [Element Wrappers](xref:using_legerity_element_wrappers) +- [Page Objects](xref:using_legerity_page_objects) +- [By Locators](xref:using_legerity_by_locators) +- [Wait Conditions](xref:using_legerity_wait_conditions) diff --git a/docs/articles/using-legerity/app-manager.md b/docs/articles/using-legerity/app-manager.md new file mode 100644 index 00000000..c5018e61 --- /dev/null +++ b/docs/articles/using-legerity/app-manager.md @@ -0,0 +1,153 @@ +--- +uid: using_legerity_app_manager +title: App Manager +--- + +# App manager + +The [`AppManager`](xref:Legerity.AppManager) is a lightweight wrapper for a Selenium application driver that allows you to start your applications using a Legerity [`AppManagerOptions`](xref:Legerity.AppManagerOptions) configuration object. + +## Understanding the app manager options + +Each platform has its own `AppManagerOptions` implementation that each include custom properties specific to them. For example, the [`AndroidAppManagerOptions`](xref:Legerity.Android.AndroidAppManagerOptions) class includes the `AppActivity` property that is specific to Android. + +Here's a complete list of the `AppManagerOptions` implementations: + +- [`AndroidAppManagerOptions`](xref:Legerity.Android.AndroidAppManagerOptions) +- [`IOSAppManagerOptions`](xref:Legerity.IOS.IOSAppManagerOptions) +- [`WindowsAppManagerOptions`](xref:Legerity.Windows.WindowsAppManagerOptions) +- [`WebAppManagerOptions`](xref:Legerity.Web.WebAppManagerOptions) + +`AppManagerOptions` and [`AppiumManagerOptions`](xref:AppiumManagerOptions) are also available for use if you want to use the base implementations to create your own custom implementations for other platforms, as well as customising for your own needs. + +## Configuring the app manager to start your app + +The `AppManager` is straightforward to configure and use. You simply construct your `AppManagerOptions` object and pass it through to the `AppManager.StartApp` method. This will return your application driver that you can use to interact with your application. + +```csharp +public class MyTests +{ + [Test] + public void MyTest() + { + AppManagerOptions options = new AndroidAppManagerOptions + { + AppId = AndroidApplication, + AppActivity = AndroidApplicationActivity, + DriverUri = "http://localhost:4723/wd/hub", + LaunchAppiumServer = false, + ImplicitWait = ImplicitWait, + }; + + var app = AppManager.StartApp(options); + } +} +``` + +> [!NOTE] +> When not running parallel tests, the `AppManager` also exposes an `App` property that will return the current application driver. This is useful when you want to interact with the application driver directly. + +### Running parallel app tests + +When running parallel tests, you will need to ensure that each test is using its own application driver. The `AppManager` handles this for you under the hood by creating new instances of an application driver for each call of the `StartApp` method. + +The multiple launched application drivers are managed under the `StartedApps` collection on the `AppManager` class. + +> [!NOTE] +> In your tests, you shouldn't need to interact with the `StartedApps` collection directly, as the `AppManager` will handle this for you. However, it is exposed if there is a custom scenario that you need to handle. + +### Waiting until the app is ready + +When starting your application, you may want to wait until the application is ready before interacting with it. A good use case for this scenario is when you want to wait until a loading screen in your app has finished before interacting with it. + +To do this, when you start your application, you can pass through a `Func` to the `StartApp` method that will be verified until it returns `true` or the timeout is reached. + +```csharp + +public class MyTests +{ + [Test] + public void MyTest() + { + AppManagerOptions options = new AndroidAppManagerOptions + { + AppId = AndroidApplication, + AppActivity = AndroidApplicationActivity, + DriverUri = "http://localhost:4723/wd/hub", + LaunchAppiumServer = false, + ImplicitWait = ImplicitWait + }; + + AppManager.StartApp(options, (driver) => + { + return driver.FindElement(By.Id("loading")).Displayed; + }, TimeSpan.FromSeconds(5)); + } +} +``` + +> [!NOTE] +> The `StartApp` method also has one final optional parameter that allows you to retry the wait condition for a configurable number of times before accepting the condition as failed. + +## Stopping the app + +When you have finished with your application, you can stop it using the `AppManager.StopApp` method. This will stop the driver, close the application, and has the ability to also stop the Appium server if it was launched by the [`AppiumManagerOptions`](xref:Legerity.AppiumManagerOptions). + +```csharp +public class MyTests +{ + [Test] + public void MyTest() + { + AppManagerOptions options = new AndroidAppManagerOptions + { + AppId = AndroidApplication, + AppActivity = AndroidApplicationActivity, + DriverUri = "http://localhost:4723/wd/hub", + LaunchAppiumServer = false, + ImplicitWait = ImplicitWait + }; + + var app = AppManager.StartApp(options); + + // Do some stuff with the app + + AppManager.StopApp(app); + } +} +``` + +> [!NOTE] +> When not running parallel tests, the `AppManager` has a parameterless `StopApp` method that will stop the current application driver. + +There is also a `StopApps` method that will stop all of the started application drivers. This is useful if you're running parallel tests and want to perform a cleanup of all of the started application drivers as part of a final teardown method. + +```csharp +public class MyTests +{ + [Test] + public void MyTest() + { + AppManagerOptions options = new AndroidAppManagerOptions + { + AppId = AndroidApplication, + AppActivity = AndroidApplicationActivity, + DriverUri = "http://localhost:4723/wd/hub", + LaunchAppiumServer = false, + ImplicitWait = ImplicitWait + }; + + var app = AppManager.StartApp(options); + + // Do some stuff with the app + + AppManager.StopApp(app); + } + + [TearDown] + public void TearDown() + { + AppManager.StopApps(); + } +} +``` diff --git a/docs/articles/using-legerity/by-locators.md b/docs/articles/using-legerity/by-locators.md new file mode 100644 index 00000000..1fd0f844 --- /dev/null +++ b/docs/articles/using-legerity/by-locators.md @@ -0,0 +1,77 @@ +--- +uid: using_legerity_by_locators +title: By Locators +--- + +# By locators + +Locators are a fundamental page of automated UI testing, allow you to find elements within your application using a variety of different strategies, such as XPath, CSS selectors, or accessibility identifiers. + +Built on top of the capabilities of Selenium, Legerity provides a set of extra common and platform specific `By` locators that you can use to find elements within your application. + +You can find more information about the available locators in the following API references: + +- [Common By locators](xref:Legerity.ByExtras) +- [Windows By locators](xref:Legerity.Windows.WindowsByExtras) +- [iOS By locators](xref:Legerity.IOS.IOSByExtras) +- [Android By locators](xref:Legerity.Android.AndroidByExtras) +- [Web By locators](xref:Legerity.Web.WebByExtras) + +## Finding elements by text + +The Core package provides `ByExtras.Text` and `ByExtras.PartialText` locators that can be used to find elements on the page by their text content. These can be used across any app platform! + +```csharp +RemoteWebElement element1 = app.FindElement(ByExtras.Text("My text")); +RemoteWebElement element2 = app.FindElement(ByExtras.PartialText("text")); +``` + +> [!NOTE] +> These locators should only be used when you are sure that the text you are searching for is unique on the page. If there are multiple elements with the same text, you may get unexpected results. + +## Finding elements by label on iOS + +The iOS package provides `ByExtras.Label` and `ByExtras.PartialLabel` locators that can be used to find elements on the page by their label content for iOS applications. + +```csharp +RemoteWebElement element1 = app.FindElement(ByExtras.Label("My label")); +RemoteWebElement element2 = app.FindElement(ByExtras.PartialLabel("label")); +``` + +## Finding elements by content description on Android + +The Android package provides `ByExtras.ContentDescription` and `ByExtras.PartialContentDescription` locators that can be used to find elements on the page by their content description for Android applications. This is particularly useful for certain elements that put their text content in the content description. + +```csharp +RemoteWebElement element1 = app.FindElement(ByExtras.ContentDescription("My content description")); +RemoteWebElement element2 = app.FindElement(ByExtras.PartialContentDescription("description")); +``` + +## Finding elements by value on iOS + +There are occasions where you may want to find an element by its value, such as a text field or a slider. The iOS package provides `ByExtras.Value` and `ByExtras.PartialValue` locators that can be used to find elements on the page by their value content for iOS applications. + +```csharp +RemoteWebElement element1 = app.FindElement(ByExtras.Value("50")); +RemoteWebElement element2 = app.FindElement(ByExtras.PartialValue("%")); +``` + +## Finding web `Input` element by type + +Finding elements by their type is a common requirement when testing web applications. The Web package provides `ByExtras.InputType` locators that can be used to find input elements on the page by their type, such as `email` or `password`. + +```csharp +RemoteWebElement emailElement = app.FindElement(ByExtras.InputType("email")); +RemoteWebElement passwordElement = app.FindElement(ByExtras.InputType("password")); +``` + +## Finding Windows elements by `AutomationId` + +Windows UI elements often have a unique `AutomationId` property that can be used to identify elements on the page. The Windows package provides `ByExtras.AutomationId` locators that can be used to find elements on the page by this property. + +```csharp +RemoteWebElement element = app.FindElement(ByExtras.AutomationId("MyAutomationId")); +``` + +> [!NOTE] +> This locator exists due to the way that the Windows Application Driver works when finding elements by ID. diff --git a/docs/articles/using-legerity/element-wrappers.md b/docs/articles/using-legerity/element-wrappers.md new file mode 100644 index 00000000..b0a27037 --- /dev/null +++ b/docs/articles/using-legerity/element-wrappers.md @@ -0,0 +1,135 @@ +--- +uid: using_legerity_element_wrappers +title: Element Wrappers +--- + +# Element wrappers + +Element wrappers are a unique feature of Legerity that allow you to "wrap" an application driver element with a custom class that mimics the functionality and exposed properties of the actual UI element. As they are a wrapper, they also expose the original `RemoteWebElement` that they are wrapping so you can still access the underlying functionality. + +This makes it much easier for you to find your elements in your tests and interact with them, without having to worry about the underlying implementation of the UI element. + +And because element wrappers start their life as a `RemoteWebElement` created from an application driver instance, the wrapper instance will always be in sync with the creating driver. + +Legerity provides a set of core element wrappers for Windows, Android, iOS, and the Web, but you can also create your own custom element wrappers for your own custom controls. They have a base type, [`ElementWrapper`](xref:Legerity.ElementWrapper`1), that provides a set of common functionality that can be used across all of your elements. + +You can discover the platform specific element wrappers in the API references for each platform: + +- [Android](xref:Legerity.Android.Elements.Core) +- [iOS](xref:Legerity.IOS.Elements.Core) +- [Windows](xref:Legerity.Windows.Elements.Core) + - [WinUI](xref:Legerity.Windows.Elements.WinUI) + - [Windows Community Toolkit](xref:Legerity.Windows.Elements.WCT) + - [Telerik UI for UWP](xref:Legerity.Windows.Elements.Telerik) + - [MADE.NET UI](xref:Legerity.Windows.Elements.MADE) +- [Web](xref:Legerity.Web.Elements.Core) + +## Finding elements with element wrappers + +If you're familiar with the methods for finding elements with an application driver, you'll know exactly how to find them with an element wrapper. + +Each element wrapper has been created using [implicit operators](https://docs.microsoft.com/en-us/dotnet/csharp/language-reference/operators/user-defined-conversion-operators) that allow you to find elements and simply cast them to the element wrapper type. + +For this example, we'll use the [`Slider`](xref:Legerity.Windows.Elements.Core.Slider) element wrapper from the Windows platform. + +```csharp +Slider slider = app.FindElement(By.Id("MySlider")); +``` + +We use the `FindElement` method on the application driver to find an element. By default, this would return a `RemoteWebElement` or platform alternative. + +However, we can assign this to a `Button` element wrapper instead, which will allow us to access all of the additional functionality that is exposed by `Button`. + +## Interacting with elements + +Once you have found an element, you can interact with it using the exposed properties and methods. + +We'll continue with our example using the `Slider` element, and see how we can use the element wrapper to make selecting a value on the slider easier. + +```csharp +Slider slider = app.FindElement(By.Id("MySlider")); +slider.SetValue(3); +``` + +Here's what that would look like if we didn't use the element wrapper: + +```csharp +RemoteWebElement slider = app.FindElement(By.Id("MySlider")); +slider.Click(); // Select the element for interaction +var currentSliderValue = double.Parse(slider.GetAttribute("RangeValue.Value")); +while(Math.Abs(currentSliderValue - 3) > double.Epsilon) +{ + slider.SendKeys(currentSliderValue < 3 ? Keys.ArrowRight : Keys.ArrowLeft); + currentSliderValue = double.Parse(slider.GetAttribute("RangeValue.Value")); +} +``` + +> [!NOTE] +> The actual `Slider` implementation for setting the value also takes into account the minimum and maximum values of the slider. This is not shown in the example above which would make the implementation more complex. + +As you can see, the element wrapper makes it much easier to interact with the element, and also makes the code much more maintainable and readable. + +## Finding elements withing element wrappers + +Element wrappers also expose a `FindElement` method that allows you to find elements within the element wrapper. This is a common scenario where you have a complex UI element that contains other elements. + +By taking advantage of element wrappers, those elements within elements can also be wrapped with element wrappers, making it easier to find and interact with them too. + +```csharp +Slider slider = app.FindElement(By.Id("MySlider")); +SliderThumb thumb = slider.FindElement(By.ClassName("SliderThumb")); +``` + +## Creating custom element wrappers + +You can create your own custom element wrappers for your own custom controls. This is done by creating a class that inherits from an element wrapper base type or another custom element wrapper. + +Your core element wrapper base types are: + +- [`ElementWrapper`](xref:Legerity.ElementWrapper`1) +- [`AndroidElementWrapper`](xref:Legerity.Android.Elements.AndroidElementWrapper) +- [`IOSElementWrapper`](xref:Legerity.IOS.Elements.IOSElementWrapper) +- [`WindowsElementWrapper`](xref:Legerity.Windows.Elements.WindowsElementWrapper) +- [`WebElementWrapper`](xref:Legerity.Web.Elements.WebElementWrapper) + +Using one of these base types will allow you to inherit the functionality of the base type for that platform, and hooks into all the additional features offered by Legerity to support `ElementWrapper` types. + +For example, if you wanted to create a custom element wrapper for a custom control on the Windows platform, you would create a class that inherits from `WindowsElementWrapper`. + +Here's an example of a custom element wrapper for a text box control that exposes the text value with the ability to set it. + +```csharp +public class TextBox : WindowsElementWrapper +{ + public TextBox(RemoteWebElement element) + : base(element) + { + } + + public virtual string Text => this.Element.GetAttribute("Value.Value"); + + public static implicit operator TextBox(WindowsElement element) + { + return new TextBox(element); + } + + public static implicit operator TextBox(AppiumWebElement element) + { + return new TextBox(element as WindowsElement); + } + + public void SetText(string text) + { + this.Click(); + this.Element.Clear(); + this.Element.SendKeys(text); + } +} +``` + +This custom element wrapper can then be used in your tests in the same way as the built-in element wrappers. + +```csharp +TextBox textBox = app.FindElement(By.Id("MyTextBox")); +textBox.SetText("Hello World!"); +``` diff --git a/docs/articles/using-legerity/installation.md b/docs/articles/using-legerity/installation.md new file mode 100644 index 00000000..6279f6cb --- /dev/null +++ b/docs/articles/using-legerity/installation.md @@ -0,0 +1,79 @@ +--- +uid: using_legerity_installation +title: Installation +--- + +# Installation + +Legerity is available as both individual NuGet packages you can install for Windows, Android, iOS, and Web, or as a single package that includes all of the Legerity packages. + +We also include project templates that you can use to quickly get up and running with writing UI tests with Legerity pre-installed. + +## UI test project templates + +Legerity includes project templates to simplify the creation of new UI test projects. To install the templates, run the following command: + +```powershell +dotnet new -i Legerity.Templates +``` + +Once installed, typing `dotnet new list Legerity` will list all of the available Legerity templates including: + +- Legerity UI Test project for Android (`legerity-android`) +- Legerity UI Test project for Cross Platform apps (`legerity-xplat`) +- Legerity UI Test project for iOS (`legerity-ios`) +- Legerity UI Test project for Web (`legerity-web`) +- Legerity UI Test project for Windows (`legerity-windows`) + +You can then create a new project by running the following command: + +```powershell +dotnet new +``` + +When creating a project, the template will automatically add to an existing solution file if it can locate one, otherwise you will have to add it manually. + +For more information on how to use these, check out our [quick starts](xref:getting_started_quick_starts). + +## Legerity packages + +To install any of the Legerity packages outlined below, you can use the following command: + +```powershell +dotnet add package +``` + +Or you can install the package via the NuGet package manager in your IDE of choice. + +### Core packages + +The following core platform NuGet packages are available: + +- [Legerity](https://www.nuget.org/packages/Legerity/) - The meta-package for Legerity that includes all of the platform-specific packages +- [Legerity.Core](https://www.nuget.org/packages/Legerity.Core/) - The core package for Legerity containing the core functionality for managing apps, pages, and elements required for all platforms +- [Legerity.Android](https://www.nuget.org/packages/Legerity.Android/) - The Android platform package for Legerity containing Android specific element wrappers, extensions, and helpers +- [Legerity.IOS](https://www.nuget.org/packages/Legerity.IOS/) - The iOS platform package for Legerity containing iOS specific element wrappers, extensions, and helpers +- [Legerity.Web](https://www.nuget.org/packages/Legerity.Web/) - The Web platform package for Legerity containing Web specific element wrappers, extensions, and helpers +- [Legerity.Windows](https://www.nuget.org/packages/Legerity.Windows/) - The Windows platform package for Legerity containing Windows specific element wrappers, extensions, and helpers +- [Legerity.WinUI](https://www.nuget.org/packages/Legerity.WinUI/) - The WinUI extension package for Legerity for Windows containing WinUI specific element wrappers + +### Additional packages + +Legerity also offers a number of additional packages that can be used to extend the functionality of platforms and available features including: + +- [Legerity.MADE](https://www.nuget.org/packages/Legerity.MADE/) - The [MADE.NET UI](https://github.com/MADE-Apps/MADE.NET-UI) extension package for Legerity for Windows containing MADE specific element wrappers +- [Legerity.Telerik.Uwp](https://www.nuget.org/packages/Legerity.Telerik.Uwp/) - The [Telerik UI for UWP](https://www.telerik.com/universal-windows-platform-ui) extension package for Legerity for Windows containing Telerik specific element wrappers +- [Legerity.WCT](https://www.nuget.org/packages/Legerity.WCT/) - The [Windows Community Toolkit](https://github.com/CommunityToolkit/WindowsCommunityToolkit) extension package for Legerity for Windows containing Windows Community Toolkit specific element wrappers +- [Legerity.Web.Authentication](https://www.nuget.org/packages/Legerity.Web.Authentication/) - The Web Authentication extension package for Legerity for Web containing page objects for authenticating with identity providers such as Azure AD, Google, and Facebook + +## Page object generation tool + +Legerity also offers a [page object generation tool](https://github.com/MADE-Apps/legerity/tree/main/tools/Legerity.PageObjectGenerator) that can be used to generate page objects for your apps. This tool works by pointing it at your application source pages and it will generate the page objects for you based on the elements it finds. + +To install the templates, run the following command: + +```powershell +dotnet tool install -g Legerity.PageObjectGenerator +``` + +You can find more detail on how to use the tool in the [page object generation](xref:using_legerity_page_objects#the-page-object-generator) section. diff --git a/docs/articles/using-legerity/page-objects.md b/docs/articles/using-legerity/page-objects.md new file mode 100644 index 00000000..c24d5729 --- /dev/null +++ b/docs/articles/using-legerity/page-objects.md @@ -0,0 +1,156 @@ +--- +uid: using_legerity_page_objects +title: Page Objects +--- + +# Page objects + +Page objects are a great way to abstract away the UI test code that is required to perform interactions with a specific page in your application. + +The page object is a standard design pattern in UI test automation for improving the maintainability of your tests by reducing the duplication of interaction code. + +When you use page objects across all your tests, you can start to write the tests in a more declarative way, which makes them easier to read and understand. + +Plus, if your UI changes, you only need to update the page object and not all your tests. + +## Understanding the Base Page object in Legerity + +Legerity offers a [`BasePage`](xref:Legerity.Pages.BasePage) class as a part of the Core package and provides a starting point for creating pages objects for your application pages. + +The base page wraps your application driver, and provides easy-to-use methods for finding elements and performing interactions with them. + +> [!NOTE] +> When paired with [element wrappers](xref:using_legerity_element_wrappers), you can start to build a page object model that is even easier to maintain with the additional abstraction of element interactions. + +## Creating a page object + +Before diving into a page object, let's first look at what a test looks like without a page object. + +```csharp +public class LoginTests : LegerityTestClass +{ + [Test] + public void ShouldLoginSuccessfully() + { + this.App.FindElement(By.Id("usernameInput")).SendKeys("username"); + this.App.FindElement(By.Id("passwordInput")).SendKeys("password"); + this.App.FindElement(By.Id("loginButton")).Click(); + + Assert.IsTrue(this.App.FindElement(By.Id("loggedInText")).Displayed); + } +} +``` + +Although concise, there are a few challenges with this approach. + +1. Login may be a common action in your application, so you may want to reuse this code across multiple tests. The current format would require you to copy and paste this code into each test. +2. This test combines both the location of the element with the interaction. If you need to change the interaction model or locator, you would need to update all the tests that use this code. +3. Reading the test code to understand what is happening is not as straight forward as it could be, as from a top level, you need to understand the interaction model and the element locator. + +With Legerity's page object model, you can start to abstract away the interaction code and make it easier to reuse across multiple tests. + +```csharp +public class LoginPage : BasePage +{ + private readonly By loginButtonLocator = By.Id("loginButton"); + + public LoginPage(RemoteWebDriver app) + : base(app) + { + } + + protected override By Trait => loginButtonLocator; + + public RemoteWebElement UsernameInput => this.App.FindElement(By.Id("usernameInput")); + public RemoteWebElement PasswordInput => this.App.FindElement(By.Id("passwordInput")); + public RemoteWebElement LoginButton => this.App.FindElement(loginButtonLocator); + + public HomePage Login(string username, string password) + { + this.UsernameInput.SendKeys(username); + this.PasswordInput.SendKeys(password); + this.LoginButton.Click(); + + return new HomePage(this.App); + } +} + +public class HomePage : BasePage +{ + private readonly By loggedInTextLocator = By.Id("loggedInText"); + + public HomePage(RemoteWebDriver app) + : base(app) + { + } + + protected override By Trait => loggedInTextLocator; + + public RemoteWebElement LoggedInText => this.App.FindElement(loggedInTextLocator); + + public bool IsLoggedIn() + { + return this.LoggedInText.Displayed; + } +} + +public class LoginTests : LegerityTestClass +{ + [Test] + public void ShouldNavigateToHomePageAfterLogin() + { + HomePage homePage = new LoginPage(this.App).Login("username", "password"); + Assert.IsTrue(homePage.IsLoggedIn()); + } +} +``` + +With this page object, you can start to reuse it across your tests using the abstracted interaction logic. + +> [!NOTE] +> In the example page object, page interactions return a page object. This allows you to chain interactions together, which can make it easier to follow the flow of a test. You can even use page object methods to chain page-to-page navigation within your tests. + +You can also start to see how the page object can be used to improve the readability of your tests. It allows you to focus on writing tests that meet your functional requirements, rather than the implementation details of how to interact with the UI. + +### The page object trait + +The page object `Trait` is a property that is used to determine if the page is in a valid state by locating an element that is **always** present on the page. + +When you create a page object, you will need to implement the `Trait` property to provide a locator that can be used to determine if the page is in a valid state. + +This `Trait` is used during the construction of the page object using the `WaitTimeout` that you can also configured in the constructor. By default, the `WaitTimeout` is set to 2 seconds. + +## Combining page objects with element wrappers + +Where complex interaction logic is required for elements on a page, you can start to use [element wrappers](xref:using_legerity_element_wrappers) to abstract away this too. + +This can help to reduce the amount of code that you need to write in your page objects, and also make it easier to maintain them. + +```csharp +public class AddPersonPage : BasePage +{ + public AddPersonPage(RemoteWebDriver app) + : base(app) + { + } + + protected override By Trait => ByExtras.Text("Add Person"); + + public TextInput NameInput => this.App.FindElement(By.Id("nameInput")); + public Select GenderSelect => this.App.FindElement(By.Id("genderSelect")); + public DateInput DateOfBirthInput => this.App.FindElement(By.Id("dateOfBirthInput")); + public Button SaveButton => this.App.FindElement(By.Id("saveButton")); + + public PersonDetailPage AddPerson(string name, string gender, DateTime dateOfBirth) + { + this.NameInput.SetText(name); + this.GenderSelect.SelectOptionByDisplayValue(gender); + this.DateOfBirthInput.SetDate(dateOfBirth); + this.SaveButton.Click(); + + return new PersonDetailPage(this.App); + } +} +``` + +In this example, all the underlying common interaction for elements like `Select` and `DateInput` are abstracted away in the element wrapper, so you only need to write the logic for interacting with the elements themselves. diff --git a/docs/articles/using-legerity/test-classes.md b/docs/articles/using-legerity/test-classes.md new file mode 100644 index 00000000..6ca19c6c --- /dev/null +++ b/docs/articles/using-legerity/test-classes.md @@ -0,0 +1,53 @@ +--- +uid: using_legerity_test_classes +title: Test Classes +--- + +# Test classes + +Writing UI tests should be simple and easy. + +Legerity exposes an [`AppManager`](xref:using_legerity_app_manager) that is a lightweight wrapper for the application driver that allows you to start your applications using a configuration object. + +Using your [testing framework](xref:using_legerity_test_frameworks) of choice, getting your application started in the appropriate Selenium driver is as simple as calling `AppManager.StartApp()` passing through an `AppManagerOptions` configuration object for the platform you want to run your tests on. + +## Using the Legerity base test class + +To make this easier for you, Legerity provides a `LegerityTestClass` class that you can inherit from in your test classes to start the application. + +The base test class is designed to manage the `AppManager` lifecycle for you, so you only have to call the `StartApp` and `StopApp` methods from the setup and teardown methods of your test class. + +This is made possible by the [`AppManagerOptions`](xref:Legerity.AppManagerOptions) object that you pass through to the [`LegerityTestClass`](xref:Legerity.LegerityTestClass) constructor. + +```csharp +public class MyTests : LegerityTestClass +{ + public MyTests() + : base(new AndroidAppManagerOptions + { + AppId = AndroidApplication, + AppActivity = AndroidApplicationActivity, + DriverUri = "http://localhost:4723/wd/hub", + LaunchAppiumServer = false, + ImplicitWait = ImplicitWait, + }) + { + } + + [SetUp] + public void SetUp() + { + this.StartApp(); + } + + [TearDown] + public void TearDown() + { + this.StopApp(); + } +} +``` + +Taking advantage of the constructor in the `LegerityTestClass` class, you can also use techniques with your testing framework of choice to pass through configuration values to your test classes as a source, allowing you to run the same tests across multiple browsers or platforms. + +To understand more about the `AppManagerOptions` object, read through our [App Manager](xref:using_legerity_app_manager#configuring-the-app-manager) documentation. diff --git a/docs/articles/using-legerity/test-frameworks.md b/docs/articles/using-legerity/test-frameworks.md new file mode 100644 index 00000000..8d9023af --- /dev/null +++ b/docs/articles/using-legerity/test-frameworks.md @@ -0,0 +1,44 @@ +--- +uid: using_legerity_test_frameworks +title: Test Frameworks +--- + +# Test frameworks + +Legerity is agnostic of .NET testing frameworks (e.g. NUnit, xUnit, MSTest, etc.) so you can work with what you know. + +However, there are a few things to consider when using Legerity with your preferred test framework. + +## Running UI tests on multiple platforms + +The Legerity framework is capable of running the same UI tests across multiple platforms, including multiple browsers. + +### Multiple platform test support with NUnit + +In NUnit, this can be achieved using a `TestFixtureSource` attribute to specify the `AppManagerOptions` to the constructor of the `LegerityTestClass` class. + +If you're using the [Legerity templates](xref:getting_started_quick_starts), you can see an example of this in the `PlatformOptions` property of the `BaseTestClass` class. + +```csharp +[TestFixtureSource(nameof(PlatformOptions))] +public class MyTests : BaseTestClass +{ + public MyTests(AppManagerOptions options) : base(options) + { + } + + protected static IEnumerable PlatformOptions => new List + { + new AndroidAppManagerOptions + { + AppId = AndroidApplication, + AppActivity = AndroidApplicationActivity, + DriverUri = "http://localhost:4723/wd/hub", + LaunchAppiumServer = false, + ImplicitWait = ImplicitWait, + } + }; +} +``` + +Adding the additional platforms to the `PlatformOptions` property automatically ensures that tests you've written in the test class are run on each using [Legerity's `AppManager`](xref:using_legerity_app_manager) to manage the app lifecycle. diff --git a/docs/articles/using-legerity/wait-conditions.md b/docs/articles/using-legerity/wait-conditions.md new file mode 100644 index 00000000..22e25fc1 --- /dev/null +++ b/docs/articles/using-legerity/wait-conditions.md @@ -0,0 +1,141 @@ +--- +uid: using_legerity_wait_conditions +title: Wait Conditions +--- + +# Wait conditions + +When you are writing UI tests, you will often need to wait for a specific condition to be met before continuing with your test. + +Selenium, by default, provide mechanisms to do this using your application driver. However, the approach to implementing them requires you to write a lot of boilerplate code to wait for a condition to be met. + +Legerity provides a set of extensions for the application driver, [page objects](xref:using_legerity_page_objects), and [element wrappers](xref:using_legerity_element_wrappers) that you can use to easily wait for a condition to be met. In addition, we have custom defined wait conditions that you can use with these methods to make the process even easier. + +## Wait for a condition to be met + +When you want to wait for a condition to be met on an application driver, [page object](xref:using_legerity_page_objects), or [element wrapper](xref:using_legerity_element_wrappers), you can use the `WaitUntil` or `TryWaitUntil` extension methods. + +These method takes in a condition delegate that returns a boolean value indicating whether the condition has been met. Depending on the object being extended, the delegate will provide you with the instance of it so you can perform your condition check. + + It also has optional parameters for a timeout and the number of retries to attempt before accepting the condition has not been met. + +> [!NOTE] +> If the timeout is not configured, the condition will be checked immediately, and if it fails, the method will fail, throwing an exception. + +```csharp +[Test] +public void ShouldWaitForConditionToBeMetOnAppDriver() +{ + this.App.WaitUntil( + driver => driver.Title == "Bing", + TimeSpan.FromSeconds(5), + 3); +} + +[Test] +public void ShouldWaitForConditionToBeMetOnPageObject() +{ + new HomePage(this.App).WaitUntil( + page => page.SearchBox.Displayed, + TimeSpan.FromSeconds(5), + 3); +} + +[Test] +public void ShouldWaitForConditionToBeMetOnElementWrapper() +{ + var inputText = "Hello, World"; + + TextBox input = this.App.FindElement(By.Id("input")); + input.SetText(inputText); + + input.WaitUntil( + element => element.Text == inputText, + TimeSpan.FromSeconds(5), + 3); +} +``` + +## Using Legerity wait conditions + +Legerity provides a set of [custom wait conditions](xref:Legerity.Helpers.WaitUntilConditions) that you can use with the `WaitUntil` and `TryWaitUntil` extension methods. + +These conditions are designed to make it easier to wait for a specific condition to be met, such as checking the title of the app, checking the URL, or checking if elements exist. + +### Checking if the driver title is correct + +You can use the `WaitUntilConditions.TitleIs` or `WaitUntilConditions.TitleContains` conditions to wait for the driver title to be the expected value. + +```csharp +[Test] +public void ShouldWaitForTitleToBeCorrect() +{ + this.App.WaitUntil( + WaitUntilConditions.TitleIs("Bing"), + TimeSpan.FromSeconds(5), + 3); +} + +[Test] +public void ShouldWaitForTitleToContainText() +{ + this.App.WaitUntil( + WaitUntilConditions.TitleContains("Bing"), + TimeSpan.FromSeconds(5), + 3); +} +``` + +### Checking if the driver URL is correct + +You can use the `WaitUntilConditions.UrlIs` or `WaitUntilConditions.UrlContains` conditions to wait for the driver URL to be the expected value. + +```csharp +[Test] +public void ShouldWaitForUrlToBeCorrect() +{ + this.App.WaitUntil( + WaitUntilConditions.UrlIs("https://www.bing.com/"), + TimeSpan.FromSeconds(5), + 3); +} + +[Test] +public void ShouldWaitForUrlToContainText() +{ + this.App.WaitUntil( + WaitUntilConditions.UrlContains("bing.com"), + TimeSpan.FromSeconds(5), + 3); +} +``` + +### Checking if an element exists with a specific locator for a driver + +You can use the `WaitUntilConditions.ElementExists` condition to wait for an element to exist with a specific locator. + +```csharp +[Test] +public void ShouldWaitForElementToExist() +{ + this.App.WaitUntil( + WaitUntilConditions.ElementExists(By.Id("input")), + TimeSpan.FromSeconds(5), + 3); +} +``` + +### Checking if an element exists with a specific locator for a page object + +You can use the `WaitUntilConditions.ElementExistsInPage` condition to wait for an element to exist with a specific locator. + +```csharp +[Test] +public void ShouldWaitForElementToExistInPage() +{ + new HomePage(this.App).WaitUntil( + WaitUntilConditions.ElementExistsInPage(By.Id("input")), + TimeSpan.FromSeconds(5), + 3); +} +``` diff --git a/docs/images/icons/element-wrapper.png b/docs/images/icons/element-wrapper.png new file mode 100644 index 00000000..b6d3da45 Binary files /dev/null and b/docs/images/icons/element-wrapper.png differ diff --git a/docs/images/icons/extensible.png b/docs/images/icons/extensible.png new file mode 100644 index 00000000..9f89e493 Binary files /dev/null and b/docs/images/icons/extensible.png differ diff --git a/docs/images/icons/github.png b/docs/images/icons/github.png new file mode 100644 index 00000000..65553daf Binary files /dev/null and b/docs/images/icons/github.png differ diff --git a/docs/images/icons/page-object.png b/docs/images/icons/page-object.png new file mode 100644 index 00000000..2fee3846 Binary files /dev/null and b/docs/images/icons/page-object.png differ diff --git a/docs/images/icons/support.png b/docs/images/icons/support.png new file mode 100644 index 00000000..bd4d31ba Binary files /dev/null and b/docs/images/icons/support.png differ diff --git a/docs/images/icons/testing.png b/docs/images/icons/testing.png new file mode 100644 index 00000000..5c63ea18 Binary files /dev/null and b/docs/images/icons/testing.png differ diff --git a/docs/index.md b/docs/index.md index 64a318e6..a73d2719 100644 --- a/docs/index.md +++ b/docs/index.md @@ -1,76 +1,195 @@ --- uid: front-page -title: Legerity Documentation +title: Legerity UI Test Framework --- -Legerity project banner +Legerity project banner -
-
-

Why Legerity?

-

-Do you struggle to get started with UI tests? Think it takes too long to develop and maintain them? -

-

-That's why I built Legerity. From the frustrations of building maintainable UI test suites, Legerity is a framework built on Selenium that makes UI testing easy for you by abstracting common UI interactions. -

- +
+

+ Build maintainable UI tests quickly for Windows, Android, iOS, and Web apps +

-[Learn more](articles/intro.md) +
+ Legerity is an automated UI testing framework for building maintainable tests quickly for native and web applications using .NET. +
- -
-
-

Available packages

-

-Take advantage of what is available for Legerity in your UI test projects. -

-

-You'll discover what packages are available for you from NuGet.org, plus how you can use them. -

- - -[Discover Legerity](articles/available-packages.md) +
+ + +[Explore the docs](xref:intro) +
+ +
+ + Info + +
+
+ +
+
+ + Build with + +
+ +
+
+
+
+ +
+
+ + App support + +
+
+ + Windows, Android, iOS, Web + +
+
+
+
+
+ +
+
+ + Browser support + +
+
+ + Chrome, Firefox, Opera, Safari, Edge, Internet Explorer + +
+
+
+
+
-
-
-

Contributing

-

-Do you have a suite of controls you want support for? -

-

-Legerity is available as an open-source project on GitHub! Dive into the codebase and bring your improvements. -

- +--- + +
+
+
+ An icon representing the page object pattern +

+ Create tests with page objects +

+
+ Legerity provides a pattern for creating lightweight, extensible page objects representing your application pages +
+
+ + +[Learn more](xref:using_legerity_page_objects) + + +
+
+
+ An icon representing an element wrapper +

+ Simplify interaction with element wrappers +

+
+ Easy-to-use element wrappers for common platform controls make it easier to find and interact with your app +
+
+ + +[Learn more](xref:using_legerity_element_wrappers) -[Get the source](https://github.com/MADE-Apps/legerity) + +
+
+
+
+
+ An icon representing test framework support +

+ Test framework agnostic +

+
+ Legerity is agnostic of .NET testing frameworks (e.g. NUnit, xUnit, MSTest, etc.) so you can work with what you know +
+
+ + +[Learn more](xref:using_legerity_test_frameworks) + + +
+
+
+ An icon representing extensibility +

+ Extensible by design +

+
+ Build custom element wrappers using the existing Legerity components for your own UI controls across platforms +
+
+ + +[Learn more](xref:using_legerity_element_wrappers) +
+
+
- diff --git a/docs/templates/material/partials/head.tmpl.partial b/docs/templates/material/partials/head.tmpl.partial index c05e8c1b..15f07cdc 100644 --- a/docs/templates/material/partials/head.tmpl.partial +++ b/docs/templates/material/partials/head.tmpl.partial @@ -11,8 +11,10 @@ + + - + {{#_noindex}}{{/_noindex}} diff --git a/docs/templates/material/styles/main.css b/docs/templates/material/styles/main.css index 6a985f77..ac09a0bf 100644 --- a/docs/templates/material/styles/main.css +++ b/docs/templates/material/styles/main.css @@ -2,13 +2,15 @@ :root { --header-bg-color: #fff; --header-ft-color: #0d4ff7a1; - --highlight-light: #5e92f3; - --highlight-dark: #002171; + --highlight-light: #98BAE1; + --highlight-dark: #3673BA; --accent-dim: #e0e0e0; --accent-super-dim: #f3f3f3; --font-color: #34393e; - --card-box-shadow: 0 1px 2px 0 rgba(61, 65, 68, 0.06), 0 1px 3px 1px rgba(61, 65, 68, 0.16); - --search-box-shadow: 0 1px 2px 0 rgba(41, 45, 48, 0.36), 0 1px 3px 1px rgba(41, 45, 48, 0.46); + --card-box-shadow: 0 1px 2px 0 rgba(61, 65, 68, 0.06), + 0 1px 3px 1px rgba(61, 65, 68, 0.16); + --search-box-shadow: 0 1px 2px 0 rgba(41, 45, 48, 0.36), + 0 1px 3px 1px rgba(41, 45, 48, 0.46); --transition: 350ms; } @@ -152,7 +154,7 @@ article h4 { border: 0; border-radius: 4px; box-shadow: var(--search-box-shadow); - transition:var(--transition); + transition: var(--transition); } .navbar-form .form-control:hover { @@ -161,7 +163,8 @@ article h4 { /* NAVBAR TOGGLED (small screens) */ -.navbar-inverse .navbar-collapse, .navbar-inverse .navbar-form { +.navbar-inverse .navbar-collapse, +.navbar-inverse .navbar-form { border: none; } .navbar-inverse .navbar-toggle { @@ -200,7 +203,7 @@ article h4 { padding: 5px; margin: 0; box-shadow: var(--card-box-shadow); - transition:var(--transition); + transition: var(--transition); } .toc-filter:hover { @@ -306,30 +309,186 @@ pre { max-width: 700px; } +/* STYLE FOR BUTTONS */ + .button { display: inline-block; } .button a { - display: inline-block; - padding: 8px 16px; - box-shadow: 0 2px 4px 0 rgba(31, 31, 31, 0.25); - border-radius: 8px; - transition: color 0.2s ease-in-out, - background-color 0.2s ease-in-out; + display: inline-block; + padding: 12px 20px; + box-shadow: 0 2px 4px 0 rgba(31, 31, 31, 0.25); + border-radius: 3em; + transition: color 0.2s ease-in-out, background-color 0.2s ease-in-out; +} + +.accent-button a { + background-color: var(--header-bg-color); + color: var(--header-ft-color); } .sponsor-button .octicon { vertical-align: text-top; - overflow: visible; - transition: transform .15s cubic-bezier(.2,0,.13,2); + overflow: visible; + transition: transform 0.15s cubic-bezier(0.2, 0, 0.13, 2); } .text-pink { - color: #ea4aaa; + color: #ea4aaa; } .octicon { - display: inline-block; - fill: currentcolor; + display: inline-block; + fill: currentcolor; +} + +/* SITE STYLES */ + +hr { + margin-top: 0; + margin-bottom: 7rem; +} + +.article { + margin-top: 120px !important; +} + +.highlight-section { + max-width: 640px; + margin: 0 auto 8px; + display: block; +} + +.hero-layout { + text-align: center; +} + +.hero-image { + width: 100%; +} + +.hero-title { + font-size: 48px; + line-height: 52px; + margin-bottom: 1rem; +} + +.hero-subtitle { + font-size: 20px; + line-height: 32px; + margin-bottom: 1.5rem; +} + +.small-heading { + font-weight: 900; + letter-spacing: 0.35rem; + text-transform: uppercase; + font-size: 12px; + margin-bottom: 1rem; + display: block; +} + +.stat { + color: var(--font-color); + text-decoration: none; + padding: 0; + display: block; + margin-right: 8px; +} + +a.stat { + color: var(--header-bg-color); +} + +.stats-container > * { + flex: 1 1 0; + margin-bottom: 1.25rem; +} + +.stat-container { + text-align: left; + vertical-align: top; + border-radius: 4px; + padding: 0; +} + +.stat-header { + font-weight: 700; + font-size: 14px; + line-height: 16px; + margin-bottom: 2px; +} + +.stat-content { + color: var(--font-color); + font-size: 12px; + line-height: 14px; + clear: both; +} + +.md-header { + font-size: 32px; + line-height: 40px; + margin-bottom: 0.75rem; + font-weight: 800; +} + +.md-content { + font-size: 20px; + line-height: 32px; + color: var(--font-color); +} + +.sm-header { + font-size: 20px; + line-height: 24px; + margin-bottom: 0.5rem; +} + +.highlight-section .sm-header { + margin-top: 0; +} + +.sm-content { + font-size: 16px; + line-height: 1.5; + color: var(--font-color); +} + +.grid-item { + flex: 0 1 50%; + padding: 4rem 70px 4rem; +} + +.grid-item img { + max-height: 120px; + max-width: 120px; +} + +.grid-col { + margin: 0; + padding: 0; +} + +.home-row { + margin-bottom: 7rem; +} + +.home-row-centered-sm { + text-align: center; +} + +@media (min-width: 992px) { + .grid-border-bottom { + border-bottom: var(--light-border); + } + + .grid-border-right { + border-right: var(--light-border); + } + + .home-row-centered-sm { + text-align: unset; + } } diff --git a/samples/AndroidCoreSamples/AndroidCoreSamples.csproj b/samples/AndroidCoreSamples/AndroidCoreSamples.csproj index 288c0fc6..11816779 100644 --- a/samples/AndroidCoreSamples/AndroidCoreSamples.csproj +++ b/samples/AndroidCoreSamples/AndroidCoreSamples.csproj @@ -8,9 +8,9 @@ - - - + + + diff --git a/samples/MADESampleApp/MADESampleApp.csproj b/samples/MADESampleApp/MADESampleApp.csproj index 10273e10..42cfed9a 100644 --- a/samples/MADESampleApp/MADESampleApp.csproj +++ b/samples/MADESampleApp/MADESampleApp.csproj @@ -8,9 +8,9 @@ - - - + + + diff --git a/samples/TelerikUwpSdkSample/TelerikUwpSdkSample.csproj b/samples/TelerikUwpSdkSample/TelerikUwpSdkSample.csproj index c6d01575..376d9e27 100644 --- a/samples/TelerikUwpSdkSample/TelerikUwpSdkSample.csproj +++ b/samples/TelerikUwpSdkSample/TelerikUwpSdkSample.csproj @@ -8,9 +8,9 @@ - - - + + + diff --git a/samples/W3SchoolsWebTests/W3SchoolsWebTests.csproj b/samples/W3SchoolsWebTests/W3SchoolsWebTests.csproj index 2787312c..bb9df575 100644 --- a/samples/W3SchoolsWebTests/W3SchoolsWebTests.csproj +++ b/samples/W3SchoolsWebTests/W3SchoolsWebTests.csproj @@ -8,10 +8,10 @@ - + - + diff --git a/samples/WebTests/WebTests.csproj b/samples/WebTests/WebTests.csproj index ba246dd0..f1662e10 100644 --- a/samples/WebTests/WebTests.csproj +++ b/samples/WebTests/WebTests.csproj @@ -8,10 +8,10 @@ - + - + diff --git a/samples/WindowsAlarmsAndClock/WindowsAlarmsAndClock.csproj b/samples/WindowsAlarmsAndClock/WindowsAlarmsAndClock.csproj index da59250c..4ed18c2b 100644 --- a/samples/WindowsAlarmsAndClock/WindowsAlarmsAndClock.csproj +++ b/samples/WindowsAlarmsAndClock/WindowsAlarmsAndClock.csproj @@ -8,9 +8,9 @@ - - - + + + diff --git a/samples/WindowsCommunityToolkitSampleApp/WindowsCommunityToolkitSampleApp.csproj b/samples/WindowsCommunityToolkitSampleApp/WindowsCommunityToolkitSampleApp.csproj index 29f9e689..d61c86fa 100644 --- a/samples/WindowsCommunityToolkitSampleApp/WindowsCommunityToolkitSampleApp.csproj +++ b/samples/WindowsCommunityToolkitSampleApp/WindowsCommunityToolkitSampleApp.csproj @@ -8,9 +8,9 @@ - - - + + + diff --git a/samples/XamlControlsGallery/XamlControlsGallery.csproj b/samples/XamlControlsGallery/XamlControlsGallery.csproj index abfc575a..3667eafa 100644 --- a/samples/XamlControlsGallery/XamlControlsGallery.csproj +++ b/samples/XamlControlsGallery/XamlControlsGallery.csproj @@ -8,9 +8,9 @@ - - - + + +