diff --git a/Behavioral.Automation.Template/.template.config/template.json b/Behavioral.Automation.Template/.template.config/template.json new file mode 100644 index 00000000..0358ab26 --- /dev/null +++ b/Behavioral.Automation.Template/.template.config/template.json @@ -0,0 +1,25 @@ +{ + "$schema": "http://json.schemastore.org/template", + "author": "Quantori LLC", + "classifications": [ + "BDD", + "Automation" + ], + "identity": "BDDTemplate", + "name": "Template for Behavioral Automation Framework", + "sourceName": "Behavioral.Automation.Template", + "shortName": "bddautomation", + "tags": { + "language": "C#", + "type": "project" + }, + "sources": [ + { + "modifiers": [ + { + "exclude": [ ".vs/**", "**/**.feature.cs", "**.nuspec" ] + } + ] + } + ] +} \ No newline at end of file diff --git a/Behavioral.Automation.Template/Behavioral.Automation.Template.Bindings/AutomationConfig.json b/Behavioral.Automation.Template/Behavioral.Automation.Template.Bindings/AutomationConfig.json new file mode 100644 index 00000000..e2d76509 --- /dev/null +++ b/Behavioral.Automation.Template/Behavioral.Automation.Template.Bindings/AutomationConfig.json @@ -0,0 +1,14 @@ +{ + "BASE_URL": "http://localhost:4200/", + "TEST_EMAIL": "", + "TEST_PASSWORD": "", + "BASE_AUTH_URL": "", + "BROWSER_PARAMS": "--window-size=1920,1080", + "ACCESS_CLIPBOARD": false, + "DOWNLOAD_PATH": "", + "SEARCH_ATTRIBUTE": "id", + "BAUTH_LOGIN": "", + "BAUTH_PWD": "", + "BAUTH_IGNORE": "true", + "BROWSER_BINARY_LOCATION" : "" +} \ No newline at end of file diff --git a/Behavioral.Automation.Template/Behavioral.Automation.Template.Bindings/Behavioral.Automation.Template.Bindings.csproj b/Behavioral.Automation.Template/Behavioral.Automation.Template.Bindings/Behavioral.Automation.Template.Bindings.csproj new file mode 100644 index 00000000..5fcba859 --- /dev/null +++ b/Behavioral.Automation.Template/Behavioral.Automation.Template.Bindings/Behavioral.Automation.Template.Bindings.csproj @@ -0,0 +1,21 @@ + + + + net6.0 + false + Quantori Inc. + Demo project that can be used as example of test configuration. + https://github.com/quantori/Behavioral.Automation + + + + + + + + + Always + + + + diff --git a/Behavioral.Automation.Template/Behavioral.Automation.Template.Bindings/ElementStorage/UserInterfaceBuilder.cs b/Behavioral.Automation.Template/Behavioral.Automation.Template.Bindings/ElementStorage/UserInterfaceBuilder.cs new file mode 100644 index 00000000..2673f456 --- /dev/null +++ b/Behavioral.Automation.Template/Behavioral.Automation.Template.Bindings/ElementStorage/UserInterfaceBuilder.cs @@ -0,0 +1,29 @@ +using Behavioral.Automation.Services; +using Behavioral.Automation.Services.Mapping.Contract; + +namespace Behavioral.Automation.Template.Bindings.ElementStorage +{ + public class UserInterfaceBuilder : UserInterfaceBuilderBase + { + public UserInterfaceBuilder(IScopeMarkupMapper mapper) + : base(mapper) + { + + } + + public override void Build() + { + using (var mappingPipe = Mapper.GetGlobalMappingPipe()) + { + mappingPipe.Register("input").Alias("input") + .With("searchInput").As("Search"); + + mappingPipe.Register("input").Alias("button") + .With("searchButton").As("Magnifying glass"); + + mappingPipe.Register("h1") + .With("firstHeading").As("Page header"); + } + } + } +} diff --git a/Behavioral.Automation.Template/Behavioral.Automation.Template.Bindings/ElementWrappers/TextElementWrapper.cs b/Behavioral.Automation.Template/Behavioral.Automation.Template.Bindings/ElementWrappers/TextElementWrapper.cs new file mode 100644 index 00000000..bcdc8a9d --- /dev/null +++ b/Behavioral.Automation.Template/Behavioral.Automation.Template.Bindings/ElementWrappers/TextElementWrapper.cs @@ -0,0 +1,37 @@ +using System.Diagnostics.CodeAnalysis; +using Behavioral.Automation.Elements; +using Behavioral.Automation.FluentAssertions; +using Behavioral.Automation.Model; +using Behavioral.Automation.Services; +using OpenQA.Selenium; + +namespace Behavioral.Automation.Template.Bindings.ElementWrappers +{ + public sealed class TextElementWrapper : WebElementWrapper, ITextElementWrapper + { + public TextElementWrapper([NotNull] IWebElementWrapper wrapper, string caption, [NotNull] IDriverService driverService) + : base(() => wrapper.Element, caption, driverService) { } + + public void EnterString(string input) + { + Assert.ShouldBecome(() => Enabled, true, + new AssertionBehavior(AssertionType.Continuous, false), + $"{Caption} is not enabled"); + Element.SendKeys(input); + Driver.RemoveFocusFromActiveElement(); + } + + public void ClearInput() + { + Assert.ShouldBecome(() => Enabled, true, + new AssertionBehavior(AssertionType.Continuous, false), + $"{Caption} is not enabled"); + + while (Element.GetAttribute("value").Length > 0) + { + Element.SendKeys(Keys.Backspace); + } + Driver.RemoveFocusFromActiveElement(); + } + } +} \ No newline at end of file diff --git a/Behavioral.Automation.Template/Behavioral.Automation.Template.Bindings/ElementWrappers/WebElementWrapper.cs b/Behavioral.Automation.Template/Behavioral.Automation.Template.Bindings/ElementWrappers/WebElementWrapper.cs new file mode 100644 index 00000000..27e19e7b --- /dev/null +++ b/Behavioral.Automation.Template/Behavioral.Automation.Template.Bindings/ElementWrappers/WebElementWrapper.cs @@ -0,0 +1,122 @@ +using Behavioral.Automation.Elements; +using Behavioral.Automation.FluentAssertions; +using Behavioral.Automation.Services; +using OpenQA.Selenium; +using System; +using System.Collections.Generic; +using System.Diagnostics.CodeAnalysis; + +namespace Behavioral.Automation.Template.Bindings.ElementWrappers +{ + public class WebElementWrapper : IWebElementWrapper + { + private readonly Func _elementSelector; + private readonly IDriverService _driverService; + + public WebElementWrapper([NotNull] Func elementSelector, [NotNull] string caption, [NotNull] IDriverService driverService) + { + _elementSelector = elementSelector; + _driverService = driverService; + Caption = caption; + } + + public string Caption { get; } + + public IWebElement Element => _elementSelector(); + + public string Text => Element.Text; + + public string GetAttribute(string attribute) => Element.GetAttribute(attribute); + + public void Click() + { + MouseHover(); + Assert.ShouldGet(() => Enabled); + _driverService.MouseClick(); + } + + public void MouseHover() + { + Assert.ShouldBecome(() => Enabled, true, $"{Caption} is disabled"); + _driverService.ScrollTo(Element); + } + + public void SendKeys(string text) + { + Assert.ShouldBecome(() => Enabled, true, $"{Caption} is disabled"); + Element.SendKeys(text); + } + + public bool Displayed => Element != null && Element.Displayed; + + public bool Enabled => Displayed && Element.Enabled && AriaEnabled; + + public string Tooltip + { + get + { + var matTooltip = GetAttribute("matTooltip"); + if (matTooltip != null) + { + return matTooltip; + } + var ngReflectTip = GetAttribute("ng-reflect-message"); //some elements have their tooltips' texts stored inside 'ng-reflect-message' attribute + if (ngReflectTip != null) + { + return ngReflectTip; + } + + return GetAttribute("aria-label"); //some elements have their tooltips' texts stored inside 'aria-label' attribute + } + } + + public bool Stale + { + get + { + try + { + // Calling any method forces a staleness check + var elementEnabled = Element.Enabled; + return false; + } + catch (StaleElementReferenceException) + { + return true; + } + } + } + + public IEnumerable FindSubElements(By locator, string caption) + { + var elements = Assert.ShouldGet(() => Element.FindElements(locator)); + return ElementsToWrappers(elements, caption); + } + + private IEnumerable ElementsToWrappers(IEnumerable elements, string caption) + { + foreach (var element in elements) + { + var wrapper = new WebElementWrapper(() => element, caption, _driverService); + yield return wrapper; + } + } + + protected IDriverService Driver => _driverService; + + private bool AriaEnabled + { + get + { + switch (Element.GetAttribute("aria-disabled")) + { + case null: + case "false": + return true; + } + + return false; + } + } + } +} diff --git a/Behavioral.Automation.Template/Behavioral.Automation.Template.Bindings/Hooks/Bootstrapper.cs b/Behavioral.Automation.Template/Behavioral.Automation.Template.Bindings/Hooks/Bootstrapper.cs new file mode 100644 index 00000000..6c0dfc34 --- /dev/null +++ b/Behavioral.Automation.Template/Behavioral.Automation.Template.Bindings/Hooks/Bootstrapper.cs @@ -0,0 +1,42 @@ +using Behavioral.Automation.FluentAssertions; +using Behavioral.Automation.Services; +using Behavioral.Automation.Template.Bindings.ElementStorage; +using BoDi; +using TechTalk.SpecFlow; +using Behavioral.Automation; +using Behavioral.Automation.Template.Bindings.Services; + +namespace Behavioral.Automation.Template.Bindings.Hooks +{ + [Binding] + public class Bootstrapper + { + private readonly IObjectContainer _objectContainer; + private readonly ITestRunner _runner; + private readonly DemoTestServicesBuilder _servicesBuilder; + private readonly BrowserRunner _browserRunner; + + public Bootstrapper(IObjectContainer objectContainer, ITestRunner runner, BrowserRunner browserRunner) + { + _objectContainer = objectContainer; + _runner = runner; + _browserRunner = browserRunner; + _servicesBuilder = new DemoTestServicesBuilder(objectContainer, new TestServicesBuilder(_objectContainer)); + } + + [AfterScenario] + public void CloseBrowser() + { + _browserRunner.CloseBrowser(); + } + + [BeforeScenario(Order = 0)] + public void Bootstrap() + { + Assert.SetRunner(_runner); + _objectContainer.RegisterTypeAs(); + _servicesBuilder.Build(); + _browserRunner.OpenChrome(); + } + } +} diff --git a/Behavioral.Automation.Template/Behavioral.Automation.Template.Bindings/Services/DemoTestServicesBuilder.cs b/Behavioral.Automation.Template/Behavioral.Automation.Template.Bindings/Services/DemoTestServicesBuilder.cs new file mode 100644 index 00000000..59026254 --- /dev/null +++ b/Behavioral.Automation.Template/Behavioral.Automation.Template.Bindings/Services/DemoTestServicesBuilder.cs @@ -0,0 +1,22 @@ +using BoDi; +using Behavioral.Automation; + +namespace Behavioral.Automation.Template.Bindings.Services +{ + internal class DemoTestServicesBuilder + { + private readonly IObjectContainer _objectContainer; + private readonly TestServicesBuilder _servicesBuilder; + + internal DemoTestServicesBuilder(IObjectContainer objectContainer, TestServicesBuilder servicesBuilder) + { + _objectContainer = objectContainer; + _servicesBuilder = servicesBuilder; + } + + internal void Build() + { + _servicesBuilder.Build(); + } + } +} \ No newline at end of file diff --git a/Behavioral.Automation.Template/Behavioral.Automation.Template.Bindings/StepArgumentTransformations/ElementTransformations.cs b/Behavioral.Automation.Template/Behavioral.Automation.Template.Bindings/StepArgumentTransformations/ElementTransformations.cs new file mode 100644 index 00000000..cc181a23 --- /dev/null +++ b/Behavioral.Automation.Template/Behavioral.Automation.Template.Bindings/StepArgumentTransformations/ElementTransformations.cs @@ -0,0 +1,37 @@ +using Behavioral.Automation.Template.Bindings.ElementWrappers; +using Behavioral.Automation.Elements; +using Behavioral.Automation.Services; +using JetBrains.Annotations; +using TechTalk.SpecFlow; + +namespace Behavioral.Automation.Template.Bindings.StepArgumentTransformations +{ + [Binding] + class ElementTransformations + { + private readonly IDriverService _driverService; + private readonly IElementSelectionService _selectionService; + + public ElementTransformations( + [NotNull] IDriverService driverService, + [NotNull] IElementSelectionService selectionService) + { + _driverService = driverService; + _selectionService = selectionService; + } + + [StepArgumentTransformation] + public IWebElementWrapper FindElement([NotNull] string caption) + { + return new WebElementWrapper(() => _selectionService.Find(caption), + caption, + _driverService); + } + + [StepArgumentTransformation("(.*)")] + public ITextElementWrapper FindTextElement([NotNull] IWebElementWrapper element) + { + return new TextElementWrapper(element, element.Caption, _driverService); + } + } +} diff --git a/Behavioral.Automation.Template/Behavioral.Automation.Template.Scenarios/Behavioral.Automation.Template.Scenarios.csproj b/Behavioral.Automation.Template/Behavioral.Automation.Template.Scenarios/Behavioral.Automation.Template.Scenarios.csproj new file mode 100644 index 00000000..be043f26 --- /dev/null +++ b/Behavioral.Automation.Template/Behavioral.Automation.Template.Scenarios/Behavioral.Automation.Template.Scenarios.csproj @@ -0,0 +1,29 @@ + + + + net6.0 + Quantori Inc. + Specflow scenarios for demonstration of Behavioral.Automation framework features and testing. + Quantori Inc. + https://github.com/quantori/Behavioral.Automation + + + + + + + + + + + + + Always + + + + + + + + \ No newline at end of file diff --git a/Behavioral.Automation.Template/Behavioral.Automation.Template.Scenarios/Features/Examples.feature b/Behavioral.Automation.Template/Behavioral.Automation.Template.Scenarios/Features/Examples.feature new file mode 100644 index 00000000..65166345 --- /dev/null +++ b/Behavioral.Automation.Template/Behavioral.Automation.Template.Scenarios/Features/Examples.feature @@ -0,0 +1,13 @@ +Feature: Examples + +@Automated +Scenario: Open Google page + When user opens URL "https://www.google.com/" + Then page title should become "Google" + +@Automated +Scenario: Find something on Wikipedia + When user opens URL "https://en.wikipedia.org/" + And user enters "French bulldog" into "Search" input + And user clicks on "Magnifying glass" button + Then the "Page header" text should become "French Bulldog" \ No newline at end of file diff --git a/Behavioral.Automation.Template/Behavioral.Automation.Template.Scenarios/Properties/AssemblyInfo.cs b/Behavioral.Automation.Template/Behavioral.Automation.Template.Scenarios/Properties/AssemblyInfo.cs new file mode 100644 index 00000000..98c5f084 --- /dev/null +++ b/Behavioral.Automation.Template/Behavioral.Automation.Template.Scenarios/Properties/AssemblyInfo.cs @@ -0,0 +1,6 @@ +using NUnit.Framework; + +[assembly: Parallelizable(ParallelScope.Fixtures)] + +//edit if more threads are needed +[assembly: LevelOfParallelism(1)] \ No newline at end of file diff --git a/Behavioral.Automation.Template/Behavioral.Automation.Template.Scenarios/specflow.json b/Behavioral.Automation.Template/Behavioral.Automation.Template.Scenarios/specflow.json new file mode 100644 index 00000000..797f0043 --- /dev/null +++ b/Behavioral.Automation.Template/Behavioral.Automation.Template.Scenarios/specflow.json @@ -0,0 +1,19 @@ +{ + "bindingCulture": { + "language": "en-us" + }, + "language": { + "feature": "en-us" + }, + "runtime": { + "missingOrPendingStepsOutcome": "Error" + }, + "stepAssemblies": [ + { + "assembly": "Behavioral.Automation.Template.Bindings" + }, + { + "assembly": "Behavioral.Automation" + } + ] +} diff --git a/Behavioral.Automation.Template/Behavioral.Automation.Template.sln b/Behavioral.Automation.Template/Behavioral.Automation.Template.sln new file mode 100644 index 00000000..8e1669d3 --- /dev/null +++ b/Behavioral.Automation.Template/Behavioral.Automation.Template.sln @@ -0,0 +1,31 @@ + +Microsoft Visual Studio Solution File, Format Version 12.00 +# Visual Studio Version 16 +VisualStudioVersion = 16.0.30114.105 +MinimumVisualStudioVersion = 10.0.40219.1 +Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Behavioral.Automation.Template.Bindings", "Behavioral.Automation.Template.Bindings\Behavioral.Automation.Template.Bindings.csproj", "{97A76E21-F1E4-49D8-B419-BAFD89B565BD}" +EndProject +Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Behavioral.Automation.Template.Scenarios", "Behavioral.Automation.Template.Scenarios\Behavioral.Automation.Template.Scenarios.csproj", "{0958EBF8-72A6-45DA-A552-C9B18F6A1695}" +EndProject +Global + GlobalSection(SolutionConfigurationPlatforms) = preSolution + Debug|Any CPU = Debug|Any CPU + Release|Any CPU = Release|Any CPU + EndGlobalSection + GlobalSection(ProjectConfigurationPlatforms) = postSolution + {97A76E21-F1E4-49D8-B419-BAFD89B565BD}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {97A76E21-F1E4-49D8-B419-BAFD89B565BD}.Debug|Any CPU.Build.0 = Debug|Any CPU + {97A76E21-F1E4-49D8-B419-BAFD89B565BD}.Release|Any CPU.ActiveCfg = Release|Any CPU + {97A76E21-F1E4-49D8-B419-BAFD89B565BD}.Release|Any CPU.Build.0 = Release|Any CPU + {0958EBF8-72A6-45DA-A552-C9B18F6A1695}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {0958EBF8-72A6-45DA-A552-C9B18F6A1695}.Debug|Any CPU.Build.0 = Debug|Any CPU + {0958EBF8-72A6-45DA-A552-C9B18F6A1695}.Release|Any CPU.ActiveCfg = Release|Any CPU + {0958EBF8-72A6-45DA-A552-C9B18F6A1695}.Release|Any CPU.Build.0 = Release|Any CPU + EndGlobalSection + GlobalSection(SolutionProperties) = preSolution + HideSolutionNode = FALSE + EndGlobalSection + GlobalSection(ExtensibilityGlobals) = postSolution + SolutionGuid = {762FF955-CF9D-4647-87C5-7F974941573C} + EndGlobalSection +EndGlobal diff --git a/Behavioral.Automation.Template/Behavioral.Automation.nuspec b/Behavioral.Automation.Template/Behavioral.Automation.nuspec new file mode 100644 index 00000000..0c93f776 --- /dev/null +++ b/Behavioral.Automation.Template/Behavioral.Automation.nuspec @@ -0,0 +1,19 @@ + + + + Behavioral.Automation.Template + 1.7.0 + + Template for easy start with Behavioral.Automation framework + + Quantori LLC + + + + readme.md + + + + + + \ No newline at end of file diff --git a/Behavioral.Automation.Template/readme.md b/Behavioral.Automation.Template/readme.md new file mode 100644 index 00000000..3a083e2f --- /dev/null +++ b/Behavioral.Automation.Template/readme.md @@ -0,0 +1,13 @@ +# Quantori Behavioral Automation Testing System +Copyright (c) 2022 Quantori. + +Quantori Behavioral Automation is an open-source framework for UI testing automation based on Selenium and Specflow which incorporates good portability and ability to be integrated into a custom web-application. Quantori Behavioral Automation is designed to be used by both manual and automation QA engineers in projects which are built based on Behaviour Driven Development approach. + +## KEY FEATURES +* Allows to create project for BDD Automation needs using Behavioral.Automation framework + +## Build instructions +TBD + +## License +Quantori Behavioral Automation Testing System is released under [Apache License, Version 2.0](LICENSE)