From 58a9d22a3a2cb2fe5fd492e17f9d76b2ff1c1277 Mon Sep 17 00:00:00 2001 From: OlegMozhey Date: Sat, 20 Apr 2024 20:39:21 +0400 Subject: [PATCH 1/2] Added initial code --- .../ElementTransformations.cs | 12 ++++++++ .../Interfaces/ILabelElement.cs | 7 +++++ .../StepDefinitions/LabelStepDefinitions.cs | 29 +++++++++++++++++++ .../Selectors/LabelSelector.cs | 8 +++++ .../UITests/Features/Tests.feature | 2 ++ src/BlazorApp/Pages/Counter.razor | 2 +- src/BlazorApp/Pages/Index.razor | 2 +- 7 files changed, 60 insertions(+), 2 deletions(-) create mode 100644 Behavioral.Automation.AsyncAbstractions.UI/Interfaces/ILabelElement.cs create mode 100644 Behavioral.Automation.AsyncAbstractions.UI/StepDefinitions/LabelStepDefinitions.cs create mode 100644 Behavioral.Automation.Playwright/Behavioral.Automation.Playwright/Selectors/LabelSelector.cs diff --git a/Behavioral.Automation.AsyncAbstractions.UI/BasicImplementations/ElementTransformations.cs b/Behavioral.Automation.AsyncAbstractions.UI/BasicImplementations/ElementTransformations.cs index 71931941..245537f3 100644 --- a/Behavioral.Automation.AsyncAbstractions.UI/BasicImplementations/ElementTransformations.cs +++ b/Behavioral.Automation.AsyncAbstractions.UI/BasicImplementations/ElementTransformations.cs @@ -31,4 +31,16 @@ public IButtonElement GetButtonElement(string caption) var element = _webElementStorageService.Get(caption + "Button"); return element; } + + /// + /// Transforms a step argument into a label element based on the caption. + /// + /// The caption of the label element. + /// The label element matching the caption. + [StepArgumentTransformation] + public ILabelElement GetLabelElement(string caption) + { + var element = _webElementStorageService.Get(caption + "Label"); + return element; + } } \ No newline at end of file diff --git a/Behavioral.Automation.AsyncAbstractions.UI/Interfaces/ILabelElement.cs b/Behavioral.Automation.AsyncAbstractions.UI/Interfaces/ILabelElement.cs new file mode 100644 index 00000000..0e27d499 --- /dev/null +++ b/Behavioral.Automation.AsyncAbstractions.UI/Interfaces/ILabelElement.cs @@ -0,0 +1,7 @@ +namespace Behavioral.Automation.AsyncAbstractions.UI.Interfaces; + +public interface ILabelElement : IWebElement +{ + public Task ShouldHaveTextAsync(string text); + public Task ShouldNotHaveTextAsync(string text); +} \ No newline at end of file diff --git a/Behavioral.Automation.AsyncAbstractions.UI/StepDefinitions/LabelStepDefinitions.cs b/Behavioral.Automation.AsyncAbstractions.UI/StepDefinitions/LabelStepDefinitions.cs new file mode 100644 index 00000000..7dd63835 --- /dev/null +++ b/Behavioral.Automation.AsyncAbstractions.UI/StepDefinitions/LabelStepDefinitions.cs @@ -0,0 +1,29 @@ +using Behavioral.Automation.AsyncAbstractions.UI.Interfaces; +using TechTalk.SpecFlow; + +namespace Behavioral.Automation.AsyncAbstractions.UI.StepDefinitions; + +[Binding] +public class LabelStepDefinitions +{ + /// + /// Check that element's text is equal to the expected one + /// + /// Tested label object + /// Assertion behavior + /// Expected value + /// Then "Test" element text should be "expected text" + [Given("the \"(.*?)\" label text (is|is not) \"(.*)\"")] + [Then("the \"(.*?)\" label text should (become|become not) \"(.*)\"")] + public async Task CheckSelectedText(ILabelElement label, string type, string value) + { + if (type.Equals("is") || type.Equals("become")) + { + await label.ShouldHaveTextAsync(value); + } + else + { + await label.ShouldNotHaveTextAsync(value); + } + } +} \ No newline at end of file diff --git a/Behavioral.Automation.Playwright/Behavioral.Automation.Playwright/Selectors/LabelSelector.cs b/Behavioral.Automation.Playwright/Behavioral.Automation.Playwright/Selectors/LabelSelector.cs new file mode 100644 index 00000000..641f2b55 --- /dev/null +++ b/Behavioral.Automation.Playwright/Behavioral.Automation.Playwright/Selectors/LabelSelector.cs @@ -0,0 +1,8 @@ +using Behavioral.Automation.AsyncAbstractions.UI.BasicImplementations; + +namespace Behavioral.Automation.Playwright.Selectors; + +public class LabelSelector : ElementSelector +{ + +} \ No newline at end of file diff --git a/Behavioral.Automation.Playwright/UITests/Features/Tests.feature b/Behavioral.Automation.Playwright/UITests/Features/Tests.feature index c30cdc86..9eb9d8aa 100644 --- a/Behavioral.Automation.Playwright/UITests/Features/Tests.feature +++ b/Behavioral.Automation.Playwright/UITests/Features/Tests.feature @@ -2,7 +2,9 @@ Scenario: Check button click Given application URL is opened + And the "Count quantity" label text is "Current count: 0" When user clicks the "Increment count" button + Then the "Count quantity" label text should become "Current count: 1" Scenario: Visibility binding check Given application URL is opened diff --git a/src/BlazorApp/Pages/Counter.razor b/src/BlazorApp/Pages/Counter.razor index acf2173d..ed4c57b5 100644 --- a/src/BlazorApp/Pages/Counter.razor +++ b/src/BlazorApp/Pages/Counter.razor @@ -4,7 +4,7 @@

Counter

-

Current count: @currentCount

+

Current count: @currentCount

diff --git a/src/BlazorApp/Pages/Index.razor b/src/BlazorApp/Pages/Index.razor index 03a70e8e..d7d32859 100644 --- a/src/BlazorApp/Pages/Index.razor +++ b/src/BlazorApp/Pages/Index.razor @@ -10,4 +10,4 @@ Welcome to your new app. - + From 4c7e8b04b1166a441c283d4cdc6d05dc567ee4b5 Mon Sep 17 00:00:00 2001 From: OlegMozhey Date: Sat, 20 Apr 2024 21:10:33 +0400 Subject: [PATCH 2/2] Added label binding and small adjustments --- .../StepDefinitions/LabelStepDefinitions.cs | 6 +++++ .../Pages/MainPage.cs | 9 ++----- .../Services/WebElementStorageService.cs | 7 ++++-- .../WebElementsWrappers/LabelElement.cs | 24 +++++++++++++++++++ .../PlayWrightWebElement.cs | 20 ++++++++++++---- .../UITests/Configurations/Configurations.cs | 1 + .../UITests/Features/Tests.feature | 2 +- src/BlazorApp/Pages/Counter.razor | 6 ++--- 8 files changed, 58 insertions(+), 17 deletions(-) create mode 100644 Behavioral.Automation.Playwright/Behavioral.Automation.Playwright/WebElementsWrappers/LabelElement.cs diff --git a/Behavioral.Automation.AsyncAbstractions.UI/StepDefinitions/LabelStepDefinitions.cs b/Behavioral.Automation.AsyncAbstractions.UI/StepDefinitions/LabelStepDefinitions.cs index 7dd63835..f5948785 100644 --- a/Behavioral.Automation.AsyncAbstractions.UI/StepDefinitions/LabelStepDefinitions.cs +++ b/Behavioral.Automation.AsyncAbstractions.UI/StepDefinitions/LabelStepDefinitions.cs @@ -26,4 +26,10 @@ public async Task CheckSelectedText(ILabelElement label, string type, string val await label.ShouldNotHaveTextAsync(value); } } + + [Then(@"the ""(.*)"" label should become visible")] + public async Task CheckLabelVisibility(ILabelElement label) + { + await label.ShouldBecomeVisibleAsync(); + } } \ No newline at end of file diff --git a/Behavioral.Automation.Playwright/Behavioral.Automation.Playwright/Pages/MainPage.cs b/Behavioral.Automation.Playwright/Behavioral.Automation.Playwright/Pages/MainPage.cs index 42875b58..1f2ebb43 100644 --- a/Behavioral.Automation.Playwright/Behavioral.Automation.Playwright/Pages/MainPage.cs +++ b/Behavioral.Automation.Playwright/Behavioral.Automation.Playwright/Pages/MainPage.cs @@ -1,15 +1,10 @@ -using Behavioral.Automation.Configs; -using Behavioral.Automation.Playwright.Configs; using Behavioral.Automation.Playwright.Selectors; -using Behavioral.Automation.Playwright.Services.ElementSelectors; namespace Behavioral.Automation.Playwright.Pages; class MainPageExample : ISelectorStorage { - private static readonly string Id = ConfigManager.GetConfig().SearchAttribute; - public ButtonSelector IncrementCountButton = new() {XpathSelector = "//button[@data-automation-id='increment-count-button']"}; - - public ElementSelector DemoLabel = new() {IdSelector = "label-simple-text"}; + public LabelSelector DemoLabel = new() {IdSelector = "label-simple-text"}; + public LabelSelector CountQuantityLabel = new LabelSelector() {IdSelector = "count-quantity-label"}; } \ No newline at end of file diff --git a/Behavioral.Automation.Playwright/Behavioral.Automation.Playwright/Services/WebElementStorageService.cs b/Behavioral.Automation.Playwright/Behavioral.Automation.Playwright/Services/WebElementStorageService.cs index 8381da80..0b34968e 100644 --- a/Behavioral.Automation.Playwright/Behavioral.Automation.Playwright/Services/WebElementStorageService.cs +++ b/Behavioral.Automation.Playwright/Behavioral.Automation.Playwright/Services/WebElementStorageService.cs @@ -20,15 +20,18 @@ public WebElementStorageService(WebContext webContext, IObjectContainer objectCo _objectContainer = objectContainer; } - //TODO: Impl factory public T Get(string elementName) where T : IWebElement { var pages = GetAllPagesWithElements(); var elementSelector = GetElementSelector(pages, elementName); // Select proper realisation for element according to registered class in DI framework: + // TODO: add validation. Throw meaningful error if realization is not registered var classType = IWebElementStorageService.RegisteredElements[typeof(T)]; - var element = (IWebElement) Activator.CreateInstance(classType, _webContext, elementSelector); + var referenceToANewWebElement = Activator.CreateInstance(classType, _webContext, elementSelector); + // TODO: Add more meaningful exception message: + if (referenceToANewWebElement == null) throw new Exception("Can't create instance of a Web Element"); + var element = (IWebElement) referenceToANewWebElement; element.Description = elementName; return (T) element; } diff --git a/Behavioral.Automation.Playwright/Behavioral.Automation.Playwright/WebElementsWrappers/LabelElement.cs b/Behavioral.Automation.Playwright/Behavioral.Automation.Playwright/WebElementsWrappers/LabelElement.cs new file mode 100644 index 00000000..f9929f81 --- /dev/null +++ b/Behavioral.Automation.Playwright/Behavioral.Automation.Playwright/WebElementsWrappers/LabelElement.cs @@ -0,0 +1,24 @@ +using System.Threading.Tasks; +using Behavioral.Automation.AsyncAbstractions.UI.BasicImplementations; +using Behavioral.Automation.AsyncAbstractions.UI.Interfaces; +using Behavioral.Automation.Playwright.Selectors; +using Microsoft.Playwright; + +namespace Behavioral.Automation.Playwright.WebElementsWrappers; + +public class LabelElement : PlaywrightWebElement, ILabelElement +{ + public LabelElement(WebContext webContext, LabelSelector selector) : base(webContext, selector) + { + } + + public async Task ShouldHaveTextAsync(string text) + { + await Assertions.Expect(Locator).ToHaveTextAsync(text); + } + + public async Task ShouldNotHaveTextAsync(string text) + { + await Assertions.Expect(Locator).Not.ToHaveTextAsync(text); + } +} \ No newline at end of file diff --git a/Behavioral.Automation.Playwright/Behavioral.Automation.Playwright/WebElementsWrappers/PlayWrightWebElement.cs b/Behavioral.Automation.Playwright/Behavioral.Automation.Playwright/WebElementsWrappers/PlayWrightWebElement.cs index 1d567ae2..b74d8940 100644 --- a/Behavioral.Automation.Playwright/Behavioral.Automation.Playwright/WebElementsWrappers/PlayWrightWebElement.cs +++ b/Behavioral.Automation.Playwright/Behavioral.Automation.Playwright/WebElementsWrappers/PlayWrightWebElement.cs @@ -2,6 +2,8 @@ using System.Threading.Tasks; using Behavioral.Automation.AsyncAbstractions.UI.BasicImplementations; using Behavioral.Automation.AsyncAbstractions.UI.Interfaces; +using Behavioral.Automation.Configs; +using Behavioral.Automation.Playwright.Configs; using Microsoft.Playwright; namespace Behavioral.Automation.Playwright.WebElementsWrappers; @@ -11,6 +13,8 @@ public abstract class PlaywrightWebElement : IWebElement public WebContext WebContext { get; } public ElementSelector ElementSelector { get; } public string? Description { get; set; } + private static readonly string Id = ConfigManager.GetConfig().SearchAttribute; + public async Task ShouldBecomeVisibleAsync() { await Assertions.Expect(Locator).ToBeVisibleAsync(); @@ -28,10 +32,18 @@ public ILocator Locator { if (WebContext is null) throw new NullReferenceException("Please set web context."); // Locator for Playwright can be retrieved from Page element: - var selector = (ElementSelector.XpathSelector != null) - ? ElementSelector.XpathSelector - : ElementSelector.IdSelector; - return ((Page) WebContext.Page).GetPlaywrightPage().Locator(selector); + if (ElementSelector.XpathSelector != null) + { + return ((Page) WebContext.Page).GetPlaywrightPage().Locator(ElementSelector.XpathSelector); + } + + if (ElementSelector.IdSelector != null) + { + return ((Page) WebContext.Page).GetPlaywrightPage().Locator($"//*[@{Id}='{ElementSelector.IdSelector}']"); + } + + // TODO: Think about moving validation into element factory method or in element constructor + throw new Exception("Please provide XpathSelector or IdSelector"); } } } \ No newline at end of file diff --git a/Behavioral.Automation.Playwright/UITests/Configurations/Configurations.cs b/Behavioral.Automation.Playwright/UITests/Configurations/Configurations.cs index 302472c9..5fd95749 100644 --- a/Behavioral.Automation.Playwright/UITests/Configurations/Configurations.cs +++ b/Behavioral.Automation.Playwright/UITests/Configurations/Configurations.cs @@ -23,6 +23,7 @@ public Configurations(ObjectContainer objectContainer) public static void ConfigureUiImplementations() { IWebElementStorageService.RegisterWebElementImplementationAs(); + IWebElementStorageService.RegisterWebElementImplementationAs(); } /// diff --git a/Behavioral.Automation.Playwright/UITests/Features/Tests.feature b/Behavioral.Automation.Playwright/UITests/Features/Tests.feature index 9eb9d8aa..1e65d2a3 100644 --- a/Behavioral.Automation.Playwright/UITests/Features/Tests.feature +++ b/Behavioral.Automation.Playwright/UITests/Features/Tests.feature @@ -8,4 +8,4 @@ Scenario: Visibility binding check Given application URL is opened - Then the "Demo label" should be visible \ No newline at end of file + Then the "Demo" label should become visible \ No newline at end of file diff --git a/src/BlazorApp/Pages/Counter.razor b/src/BlazorApp/Pages/Counter.razor index ed4c57b5..b6746a75 100644 --- a/src/BlazorApp/Pages/Counter.razor +++ b/src/BlazorApp/Pages/Counter.razor @@ -4,18 +4,18 @@

Counter

-

Current count: @currentCount

+

Current count: @_currentCount

@code { - private int currentCount = 0; + private int _currentCount = 0; [Parameter] public int IncrementAmount { get; set; } = 1; private void IncrementCount() { - currentCount += IncrementAmount; + _currentCount += IncrementAmount; } }