diff --git a/CHANGELOG.md b/CHANGELOG.md index ae09b04..5a45265 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -9,6 +9,12 @@ - deprecate the majority of `Selene.*` (except S, SS) when providing alternative API via `Browser.*` +## 1.0.0-alpha14 (to be released on 2024.08.28) + +- add Configuration.LogOuterHtmlOnFailure (false by default) + - to disable previously mandatory logging of outer html of elements in error messages + that was the reason of failures when working with Appium + ## 1.0.0-alpha13 (released on 2024.02.13) - fix element.GetAttribute to call webelement.GetAttribute instead of webelement.GetDomAttribute @@ -49,20 +55,23 @@ - added SeleneElement.GetShadowRoot as wrapper over WebElement.GetShadowRoot ## 1.0.0-alpha09 (to be released on 2021.05.19) + - improved error messages for cases of inner element search - like error on S(".parent").Find(".child").Click() when .parent is absent or not visible - FIXED experimental Configuration._HookWaitAction application to Should methods on SeleneElement and SeleneCollection (was not working, just being skipped) ## 1.0.0-alpha08 (released on 2021.05.18) + - added waiting to SeleneElementJsExtensions: - JsClick - JsType - - JsSetValue + - JsSetValue ## 1.0.0-alpha07 (released on 2021.05.13) -- improved error messsages - - now condition in Should method will be rendered like: + +- improved error messsages + - now condition in Should method will be rendered like: `... .Should(Be.Visible)` over just `... .Visible` - deprecated SeleneElement#config, use SeleneElement#Config instead (same for SeleneCollection) - yet be attentive... the fate of keeping SeleneElement#Config as public is also vague... @@ -70,7 +79,7 @@ - prefixed with underscore implies that this feature is kind of "publically available private property that might be changed/renamed/removed/etc in future;)", so use it on your own risk!!! - by default equals to null, making internally call waiting algorithm for actions as it is, without additional customization - specified to something like: - ``` + ```cs Configuration._HookWaitAction = (entityObject, describeComputation, wait) => { Console.WriteLine($"STARTED WAITING FOR: {entityObject}.{describeComputation()}"); try diff --git a/LICENSE b/LICENSE index 33ca3aa..33559f4 100644 --- a/LICENSE +++ b/LICENSE @@ -1,6 +1,6 @@ MIT License -Copyright (c) 2015-2021 Iakiv Kramarenko +Copyright (c) 2015 Iakiv Kramarenko Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal diff --git a/NSelene/Conditions/Attribute.cs b/NSelene/Conditions/Attribute.cs index 19cb3fb..b299033 100644 --- a/NSelene/Conditions/Attribute.cs +++ b/NSelene/Conditions/Attribute.cs @@ -31,7 +31,11 @@ public override void Invoke(SeleneElement entity) ? $"Actual {this.name}: Null (attribute is absent)\n" : $"Actual {this.name}: «{maybeActual}»\n" ) - + $"Actual webelement: {webelement.GetAttribute("outerHTML")}" + + ( + entity.Config.LogOuterHtmlOnFailure ?? false + ? $"Actual webelement: {webelement.GetAttribute("outerHTML")}" + : "" + ) ); } } diff --git a/NSelene/Conditions/AttributeWithValueMatching.cs b/NSelene/Conditions/AttributeWithValueMatching.cs index 8a44e6f..895978c 100644 --- a/NSelene/Conditions/AttributeWithValueMatching.cs +++ b/NSelene/Conditions/AttributeWithValueMatching.cs @@ -32,7 +32,11 @@ public override void Invoke(SeleneElement entity) ? $"Actual {this.name}: Null (attribute is absent)\n" : $"Actual {this.name}: «{maybeActual}»\n" ) - + $"Actual webelement: {webelement.GetAttribute("outerHTML")}" + + ( + entity.Config.LogOuterHtmlOnFailure ?? false + ? $"Actual webelement: {webelement.GetAttribute("outerHTML")}" + : "" + ) ); } } diff --git a/NSelene/Conditions/Enabled.cs b/NSelene/Conditions/Enabled.cs index d37317f..b85aa99 100644 --- a/NSelene/Conditions/Enabled.cs +++ b/NSelene/Conditions/Enabled.cs @@ -10,8 +10,12 @@ public override void Invoke(SeleneElement entity) if (!webelement.Enabled) { throw new ConditionNotMatchedException(() => - "Found element is not enabled: " - + webelement.GetAttribute("outerHTML") + "Found element is not enabled" + + ( + (entity.Config.LogOuterHtmlOnFailure ?? false) + ? $": {webelement.GetAttribute("outerHTML")}" + : "" + ) ); } } diff --git a/NSelene/Conditions/Selected.cs b/NSelene/Conditions/Selected.cs index 4ba4e93..147a2f4 100644 --- a/NSelene/Conditions/Selected.cs +++ b/NSelene/Conditions/Selected.cs @@ -10,8 +10,12 @@ public override void Invoke(SeleneElement entity) if (!webelement.Selected) { throw new ConditionNotMatchedException(() => - "Found element is not selected: " - + webelement.GetAttribute("outerHTML") + "Found element is not selected" + + ( + (entity.Config.LogOuterHtmlOnFailure ?? false) + ? $": {webelement.GetAttribute("outerHTML")}" + : "" + ) ); } } diff --git a/NSelene/Conditions/Text.cs b/NSelene/Conditions/Text.cs index e28d7d9..1cca59e 100644 --- a/NSelene/Conditions/Text.cs +++ b/NSelene/Conditions/Text.cs @@ -26,7 +26,11 @@ public override void Invoke(SeleneElement entity) { throw new ConditionNotMatchedException(() => $"Actual text: «{actual}»\n" - + $"Actual webelement: {webelement.GetAttribute("outerHTML")}" + + ( + entity.Config.LogOuterHtmlOnFailure ?? false + ? $"Actual webelement: {webelement.GetAttribute("outerHTML")}" + : "" + ) ); } } @@ -54,7 +58,11 @@ public override void Invoke(SeleneElement entity) { throw new ConditionNotMatchedException(() => $"Actual text: «{actual}»\n" - + $"Actual webelement: {webelement.GetAttribute("outerHTML")}" + + ( + entity.Config.LogOuterHtmlOnFailure ?? false + ? $"Actual webelement: {webelement.GetAttribute("outerHTML")}" + : "" + ) ); } } diff --git a/NSelene/Conditions/Title.cs b/NSelene/Conditions/Title.cs index 74fb92c..b91cf00 100644 --- a/NSelene/Conditions/Title.cs +++ b/NSelene/Conditions/Title.cs @@ -9,7 +9,6 @@ namespace Conditions public class Title : Condition { private string expected; - private bool result; public Title (string expected) { @@ -35,7 +34,6 @@ public override void Invoke(IWebDriver entity) public class TitleContaining : Condition { private string expected; - private bool result; public TitleContaining (string expected) { diff --git a/NSelene/Conditions/Url.cs b/NSelene/Conditions/Url.cs index 1abfc9f..ae84ed5 100644 --- a/NSelene/Conditions/Url.cs +++ b/NSelene/Conditions/Url.cs @@ -9,7 +9,6 @@ namespace Conditions public class Url : Condition { private string expected; - private bool result; public Url (string expected) { @@ -35,7 +34,6 @@ public override void Invoke(IWebDriver entity) public class UrlContaining : Condition { private string expected; - private bool result; public UrlContaining (string expected) { diff --git a/NSelene/Conditions/Visible.cs b/NSelene/Conditions/Visible.cs index e8a99de..e2da3c7 100644 --- a/NSelene/Conditions/Visible.cs +++ b/NSelene/Conditions/Visible.cs @@ -7,11 +7,14 @@ public class Visible : Condition public override void Invoke(SeleneElement entity) { var webelement = entity.ActualWebElement; - if (!webelement.Displayed) + if (!webelement.Displayed) { - throw new ConditionNotMatchedException(() => - "Found element is not visible: " - + webelement.GetAttribute("outerHTML") + throw new ConditionNotMatchedException(() => + "Found element is not visible" + ( + (entity.Config.LogOuterHtmlOnFailure ?? false) + ? $": {webelement.GetAttribute("outerHTML")}" + : "" + ) ); } } @@ -25,12 +28,12 @@ public override void Invoke(SeleneElement entity) public static partial class Be { - public static Conditions.Condition Visible + public static Conditions.Condition Visible => new Conditions.Visible(); static partial class Not { - public static Conditions.Condition Visible + public static Conditions.Condition Visible => Be.Visible.Not; } } diff --git a/NSelene/Configuration.cs b/NSelene/Configuration.cs index ba77afb..98258e7 100644 --- a/NSelene/Configuration.cs +++ b/NSelene/Configuration.cs @@ -29,6 +29,7 @@ public interface _SeleneSettings_ bool? TypeByJs { get; set; } bool? ClickByJs { get; set; } bool? WaitForNoOverlapFoundByJs { get; set; } + bool? LogOuterHtmlOnFailure { get; set; } Action, Action> _HookWaitAction { get; set; } string BaseUrl { get; set; } } @@ -153,6 +154,19 @@ IWebDriver _SeleneSettings_.Driver } } + private Ref _refLogOuterHtmlOnFailure = new Ref(); + bool? _SeleneSettings_.LogOuterHtmlOnFailure + { + get + { + return this._refLogOuterHtmlOnFailure.Value; + } + set + { + this._refLogOuterHtmlOnFailure.Value = value; + } + } + private Ref, Action>> _ref_HookWaitAction;// = new Ref, Action>>(); Action, Action> _SeleneSettings_._HookWaitAction { @@ -182,6 +196,7 @@ private Configuration( Ref refTypeByJs, Ref refClickByJs, Ref refWaitForNoOverlapFoundByJs, + Ref refLogOuterHtmlOnFailure, Ref, Action>> _ref_HookWaitAction, Ref refBaseUrl) { @@ -192,6 +207,7 @@ private Configuration( _refTypeByJs = refTypeByJs ?? new Ref(); _refClickByJs = refClickByJs ?? new Ref(); _refWaitForNoOverlapFoundByJs = refWaitForNoOverlapFoundByJs ?? new Ref(); + _refLogOuterHtmlOnFailure = refLogOuterHtmlOnFailure ?? new Ref(); this._ref_HookWaitAction = _ref_HookWaitAction ?? new Ref, Action>>(); _refBaseUrl = refBaseUrl ?? new Ref(); } @@ -208,6 +224,7 @@ internal Configuration() refTypeByJs: null, refClickByJs: null, refWaitForNoOverlapFoundByJs: null, + refLogOuterHtmlOnFailure: null, _ref_HookWaitAction: null, refBaseUrl: null ) {} @@ -220,6 +237,7 @@ public static _SeleneSettings_ _New_( bool typeByJs = false, bool clickByJs = false, bool waitForNoOverlapFoundByJs = false, + bool logOuterHtmlOnFailure = false, Action, Action> _hookWaitAction = null, string baseUrl = "" ) @@ -233,6 +251,7 @@ public static _SeleneSettings_ _New_( next.TypeByJs = typeByJs; next.ClickByJs = clickByJs; next.WaitForNoOverlapFoundByJs = waitForNoOverlapFoundByJs; + next.LogOuterHtmlOnFailure = logOuterHtmlOnFailure; next._HookWaitAction = _hookWaitAction; next.BaseUrl = baseUrl; @@ -270,6 +289,10 @@ internal static _SeleneSettings_ Shared getter: () => Configuration.WaitForNoOverlapFoundByJs, setter: value => Configuration.WaitForNoOverlapFoundByJs = value ?? false ), + refLogOuterHtmlOnFailure: new Ref( + getter: () => Configuration.LogOuterHtmlOnFailure, + setter: value => Configuration.LogOuterHtmlOnFailure = value ?? false + ), _ref_HookWaitAction: new Ref, Action>>( getter: () => Configuration._HookWaitAction, setter: value => Configuration._HookWaitAction = value @@ -291,6 +314,7 @@ public static _SeleneSettings_ _With_( bool? typeByJs = null, bool? clickByJs = null, bool? waitForNoOverlapFoundByJs = null, + bool? logOuterHtmlOnFailure = null, Action, Action> _hookWaitAction = null, string baseUrl = null ) @@ -304,6 +328,7 @@ public static _SeleneSettings_ _With_( next.TypeByJs = typeByJs; next.ClickByJs = clickByJs; next.WaitForNoOverlapFoundByJs = waitForNoOverlapFoundByJs; + next.LogOuterHtmlOnFailure = logOuterHtmlOnFailure; next._HookWaitAction = _hookWaitAction; next.BaseUrl = baseUrl; @@ -336,6 +361,9 @@ _SeleneSettings_ overrides refWaitForNoOverlapFoundByJs: overrides.WaitForNoOverlapFoundByJs == null ? this._refWaitForNoOverlapFoundByJs : new Ref(overrides.WaitForNoOverlapFoundByJs), + refLogOuterHtmlOnFailure: overrides.LogOuterHtmlOnFailure == null + ? this._refLogOuterHtmlOnFailure + : new Ref(overrides.LogOuterHtmlOnFailure), _ref_HookWaitAction: overrides._HookWaitAction == null ? this._ref_HookWaitAction : new Ref, Action>>(overrides._HookWaitAction), @@ -449,6 +477,19 @@ public static bool WaitForNoOverlapFoundByJs } } + private static ThreadLocal _LogOuterHtmlOnFailure = new ThreadLocal(); + public static bool LogOuterHtmlOnFailure + { + get + { + return Configuration._LogOuterHtmlOnFailure.Value ?? false; + } + set + { + Configuration._LogOuterHtmlOnFailure.Value = value; + } + } + private static ThreadLocal, Action>> __HookWaitAction = new ThreadLocal, Action>>(); public static Action, Action> _HookWaitAction { diff --git a/NSelene/NSelene.csproj b/NSelene/NSelene.csproj index 3d61eaa..40a4369 100644 --- a/NSelene/NSelene.csproj +++ b/NSelene/NSelene.csproj @@ -3,10 +3,10 @@ netstandard2.0 NSelene - 1.0.0-alpha13 + 1.0.0-alpha14 yashaka MIT - Copyright (c) 2015-2021 Iakiv Kramarenko + Copyright (c) 2015 Iakiv Kramarenko https://github.com/yashaka/NSelene NSelene - User-oriented Web UI browser tests in .NET (Selenide port from Java) selenium;webdriver;wrapper;web;browser;test;automation;autotest;selenide;selene @@ -23,8 +23,9 @@ -- fix element.GetAttribute to call webelement.GetAttribute instead of webelement.GetDomAttribute - - correspondingly the behavior of element.Value is also fixed, becaused is based on element.GetAttribute +- add Configuration.LogOuterHtmlOnFailure (false by default) + - to disable previously mandatory logging of outer html of elements in error messages + that was the reason of failures when working with Appium true diff --git a/NSelene/SeleneCollection.cs b/NSelene/SeleneCollection.cs index 93c9e23..c0a0c3e 100644 --- a/NSelene/SeleneCollection.cs +++ b/NSelene/SeleneCollection.cs @@ -81,6 +81,7 @@ public SeleneCollection With( bool? typeByJs = null, bool? clickByJs = null, bool? waitForNoOverlapFoundByJs = null, + bool? logOuterHtmlOnFailure = null, Action, Action> _hookWaitAction = null ) { @@ -93,6 +94,7 @@ public SeleneCollection With( customized.TypeByJs = typeByJs; customized.ClickByJs = clickByJs; customized.WaitForNoOverlapFoundByJs = waitForNoOverlapFoundByJs; + customized.LogOuterHtmlOnFailure = logOuterHtmlOnFailure; customized._HookWaitAction = _hookWaitAction; /* same but another style and not so obvious with harder override logic: diff --git a/NSelene/SeleneDriver.cs b/NSelene/SeleneDriver.cs index 9506e9f..349d25f 100644 --- a/NSelene/SeleneDriver.cs +++ b/NSelene/SeleneDriver.cs @@ -2,6 +2,7 @@ using System.Collections.Generic; using System.Collections.ObjectModel; using System.Threading; +using System.Threading.Tasks; using NSelene.Conditions; using OpenQA.Selenium; using OpenQA.Selenium.Interactions; @@ -121,6 +122,7 @@ public SeleneDriver With( bool? typeByJs = null, bool? clickByJs = null, bool? waitForNoOverlapFoundByJs = null, + bool? logOuterHtmlOnFailure = null, Action, Action> _hookWaitAction = null ) { @@ -133,6 +135,7 @@ public SeleneDriver With( customized.TypeByJs = typeByJs; customized.ClickByJs = clickByJs; customized.WaitForNoOverlapFoundByJs = waitForNoOverlapFoundByJs; + customized.LogOuterHtmlOnFailure = logOuterHtmlOnFailure; customized._HookWaitAction = _hookWaitAction; return new SeleneDriver( @@ -254,6 +257,31 @@ public void Refresh() asWebDriver().Navigate().Refresh(); } + Task INavigation.BackAsync() + { + return Value.Navigate().BackAsync(); + } + + Task INavigation.ForwardAsync() + { + return Value.Navigate().ForwardAsync(); + } + + Task INavigation.GoToUrlAsync(string url) + { + return Value.Navigate().GoToUrlAsync(url); + } + + Task INavigation.GoToUrlAsync(Uri url) + { + return Value.Navigate().GoToUrlAsync(url); + } + + Task INavigation.RefreshAsync() + { + return Value.Navigate().RefreshAsync(); + } + // // Some IWebDriver original properties and methods // diff --git a/NSelene/SeleneElement.cs b/NSelene/SeleneElement.cs index ad85e04..bf44ff1 100644 --- a/NSelene/SeleneElement.cs +++ b/NSelene/SeleneElement.cs @@ -65,6 +65,7 @@ public SeleneElement With( bool? typeByJs = null, bool? clickByJs = null, bool? waitForNoOverlapFoundByJs = null, + bool? logOuterHtmlOnFailure = null, Action, Action> _hookWaitAction = null ) { @@ -77,6 +78,7 @@ public SeleneElement With( customized.TypeByJs = typeByJs; customized.ClickByJs = clickByJs; customized.WaitForNoOverlapFoundByJs = waitForNoOverlapFoundByJs; + customized.LogOuterHtmlOnFailure = logOuterHtmlOnFailure; customized._HookWaitAction = _hookWaitAction; return new SeleneElement( @@ -152,8 +154,12 @@ private IWebElement ActualVisibleWebElement { throw new SeleneException( () => - "Element not visible:\n" // TODO: should we render here also the current element locator? (will be redundant for S but might help in S.S, SS.S.S) - + webElement.GetAttribute("outerHTML") + "Element not visible" // TODO: should we render here also the current element locator? (will be redundant for S but might help in S.S, SS.S.S) + + ( + (this.Config.LogOuterHtmlOnFailure ?? false) + ? $":\n{webElement.GetAttribute("outerHTML")}" + : "" + ) ); } return webElement; @@ -177,8 +183,14 @@ private IWebElement ActualNotOverlappedWebElement { throw new SeleneException( () => - $"Element: {webElement.GetAttribute("outerHTML")}\n" - + $"\tis overlapped by: {cover.GetAttribute("outerHTML")}" + $"Element" + + ( + // TODO: do we actually need to use it here? ... + (this.Config.LogOuterHtmlOnFailure ?? false) + ? $": {webElement.GetAttribute("outerHTML")}" + : "" + ) // TODO: ... while not applied here? + + $"\n\tis overlapped by: {cover.GetAttribute("outerHTML")}" ); } return webElement; diff --git a/NSelene/SeleneLocator.cs b/NSelene/SeleneLocator.cs index 70e6a99..e198216 100644 --- a/NSelene/SeleneLocator.cs +++ b/NSelene/SeleneLocator.cs @@ -156,11 +156,15 @@ public override IWebElement Find () if (found == null) { var actualTexts = webelments.ToList().Select(element => element.Text).ToArray(); - var htmlelements = webelments.ToList().Select(element => element.GetAttribute("outerHTML")).ToArray(); + var maybeHtmlElements = this.context.Config.LogOuterHtmlOnFailure ?? false + ? webelments.ToList().Select(element => element.GetAttribute("outerHTML")).ToArray() + : null; throw new NotFoundException("element was not found in collection by condition " + condition + "\n Actual visible texts : " + "[" + string.Join(",", actualTexts) + "]" // TODO: think: this line is actually needed in the case of FindBy(ExactText ...) ... is there any way to add such information not here? - + "\n Actual html elements : " + "[" + string.Join(",", htmlelements) + "]" + + maybeHtmlElements != null + ? "\n Actual html elements : " + "[" + string.Join(",", maybeHtmlElements) + "]" + : "" // TODO: should we add here some other info about elements? e.g. visiblitiy? ); /* @@ -219,11 +223,15 @@ public override IWebElement Find () if (found == null) { var actualTexts = webelments.ToList().Select(element => element.Text).ToArray(); - var htmlelements = webelments.ToList().Select(element => element.GetAttribute("outerHTML")).ToArray(); + var maybeHtmlElements = this.context.Config.LogOuterHtmlOnFailure ?? false + ? webelments.ToList().Select(element => element.GetAttribute("outerHTML")).ToArray() + : null; throw new NotFoundException("element was not found in collection by condition " + condition + "\n Actual visible texts : " + "[" + string.Join(",", actualTexts) + "]" // TODO: think: this line is actually needed in the case of FindBy(ExactText ...) ... is there any way to add such information not here? - + "\n Actual html elements : " + "[" + string.Join(",", htmlelements) + "]" + + maybeHtmlElements != null + ? "\n Actual html elements : " + "[" + string.Join(",", maybeHtmlElements) + "]" + : "" // TODO: should we add here some other info about elements? e.g. visiblitiy? ); /* diff --git a/NSeleneTests/Integration/SharedDriver/Configuration__HookWaitAction_Specs.cs b/NSeleneTests/Integration/SharedDriver/Configuration__HookWaitAction_Specs.cs index d3261dc..c773ad8 100644 --- a/NSeleneTests/Integration/SharedDriver/Configuration__HookWaitAction_Specs.cs +++ b/NSeleneTests/Integration/SharedDriver/Configuration__HookWaitAction_Specs.cs @@ -26,10 +26,10 @@ public void HookWaitAction_SetGlobally_CanLogActionsLikeClickAndShould() wait(); log.Add($"{entityObject}.{describeComputation()}: PASSED"); } - catch (Exception error) + catch (Exception) { log.Add($"{entityObject}.{describeComputation()}: FAILED"); - throw error; + throw; } }; Given.OpenedPageWithBody(@" @@ -80,10 +80,10 @@ public void HookWaitAction_SetPerElements_CanLogActionsLikeClickAndShould() wait(); log.Add($"{entityObject}.{describeComputation()}: PASSED"); } - catch (Exception error) + catch (Exception) { log.Add($"{entityObject}.{describeComputation()}: FAILED"); - throw error; + throw; } }; Given.OpenedPageWithBody(@" diff --git a/NSeleneTests/Integration/SharedDriver/Harness/BaseTest.cs b/NSeleneTests/Integration/SharedDriver/Harness/BaseTest.cs index beae362..c8cda75 100644 --- a/NSeleneTests/Integration/SharedDriver/Harness/BaseTest.cs +++ b/NSeleneTests/Integration/SharedDriver/Harness/BaseTest.cs @@ -27,6 +27,8 @@ public void initDriver() Configuration.TypeByJs = false; Configuration.ClickByJs = false; Configuration.WaitForNoOverlapFoundByJs = false; + // TODO: ensure we have tests where it is set to false + Configuration.LogOuterHtmlOnFailure = true; Configuration._HookWaitAction = null; } diff --git a/README.md b/README.md index a02691c..b8388f6 100644 --- a/README.md +++ b/README.md @@ -33,9 +33,9 @@ For docs see tests in the [NSeleneTests](https://github.com/yashaka/NSelene/blob ## Versions -- Upcomig version to use is just released [1.0.0-alpha11](https://www.nuget.org/packages/NSelene/1.0.0-alpha11) +- Upcomig version to use is just released [1.0.0-alpha14](https://www.nuget.org/packages/NSelene/1.0.0-alpha14) - targets netstandard2.0 - - wraps Selenium 4.9.1 + - wraps Selenium >= 4.9.* - it differs from [0.0.0.7](https://www.nuget.org/packages/NSelene/0.0.0.7) in the following: - repacked in sdk-style format - removed things marked as obsolete til 0.0.0.7