diff --git a/dotnet/src/support/PageObjects/DefaultElementLocator.cs b/dotnet/src/support/PageObjects/DefaultElementLocator.cs new file mode 100644 index 0000000000000..6aba1e6a78d55 --- /dev/null +++ b/dotnet/src/support/PageObjects/DefaultElementLocator.cs @@ -0,0 +1,252 @@ +using OpenQA.Selenium.Internal; +using OpenQA.Selenium.Support.PageObjects.Interfaces; +using System; +using System.Collections.Generic; +using System.Collections.ObjectModel; +using System.Linq; +using System.Reflection; +using OpenQA.Selenium.Support.UI; +using System.Text; + +namespace OpenQA.Selenium.Support.PageObjects +{ + /// + /// The default element locator, which will lazily locate an element or an element list on a page. This class is + /// designed for use with the and uses + /// attributes and . + /// + public class DefaultElementLocator: IElementLocator, IWrapsDriver + { + protected readonly MemberInfo Member; + protected readonly ISearchContext SearchContext; + protected readonly SearchParameterContainer SearchParameters; + private readonly IAdjustableByTimeSpan TimeOutContainer; + private IWebElement CachedElement; + private ReadOnlyCollection CachedElementCollection; + private readonly bool ShouldCacheLookUp; + + + public DefaultElementLocator(MemberInfo member, ISearchContext searchContext, IAdjustableByTimeSpan timeOutContainer) + { + this.Member = member; + this.SearchContext = searchContext; + this.TimeOutContainer = timeOutContainer; + this.SearchParameters = new SearchParameterContainer(); + this.SearchParameters.Context = this.SearchContext; + this.SearchParameters.TheGivenBys = CreateBys(); + ShouldCacheLookUp = ShouldCacheLookup(); + } + + /// + /// An algorimthm which describes the waiting mechanism + /// + /// A read only collection of s which are found. + /// If there is no relevant elements when the result is null + private static Func> FindElements() + { + return (parameterContainer) => { + ISearchContext context = parameterContainer.Context; + ReadOnlyCollection bys = parameterContainer.TheGivenBys; + try + { + List collection = new List(); + foreach (var by in bys) + { + ReadOnlyCollection list = context.FindElements(by); + collection.AddRange(list); + } + if (collection.Count > 0) + { + return collection.AsReadOnly(); + } + return null; + } + catch (StaleElementReferenceException) + { + return null; + } + + }; + } + + /// + /// + /// IWebdriver instatnce which is wrapped by the given search context. This method could be overriden + public IWebDriver WrappedDriver + { + get { + IWebDriver driver = SearchContext as IWebDriver; + if (driver != null) + { + return driver; + } + //RemoteWebElement implements IWrapsDriver and it is ISearchContext too + //So we can get IWebDriver from the element + //There can be something that wraps the original element + IWrapsElement wrapsElement = SearchContext as IWrapsElement; + IWebElement element = SearchContext as IWebElement; + + while (wrapsElement != null) + { + element = ((IWrapsElement) element).WrappedElement; + wrapsElement = element as IWrapsElement; + } + + //so we get to the original RemoteWebElement instance + return ((IWrapsDriver) element).WrappedDriver; + } + } + + /// + /// This method creates strategies according to attributes + /// which mark the target field. If it is nesessary you can make your own + /// subclass of DefaultElementLocator and override this method + /// + /// + protected virtual ReadOnlyCollection CreateBys() + { + var useSequenceAttributes = Attribute.GetCustomAttributes(Member, typeof(FindsBySequenceAttribute), true); + bool useSequence = useSequenceAttributes.Length > 0; + + List bys = new List(); + var attributes = Attribute.GetCustomAttributes(Member, typeof(FindsByAttribute), true); + if (attributes.Length > 0) + { + Array.Sort(attributes); + foreach (var attribute in attributes) + { + var castedAttribute = (FindsByAttribute)attribute; + if (castedAttribute.Using == null) + { + castedAttribute.Using = Member.Name; + } + + bys.Add(castedAttribute.Finder); + } + + if (useSequence) + { + ByChained chained = new ByChained(bys.ToArray()); + bys.Clear(); + bys.Add(chained); + } + } + + if (attributes.Length == 0) + { + bys.Add(new ByIdOrName(Member.Name)); + } + return bys.AsReadOnly(); + } + + public IWebElement Element + { + get + { + if (CachedElement != null && ShouldCacheLookUp) + { + return CachedElement; + } + ReadOnlyCollection result = WaitFor(); + if (result.Count == 0) + { + throw new NoSuchElementException("Cann't locate an element by this strategies: " + SearchParameters.TheGivenBys.ToString()); + } + + if (ShouldCacheLookUp) + { + CachedElement = result[0]; + } + return result[0]; + } + } + + /// + /// This method checks the presence of + /// attribute. Field or class are checked. + /// + /// true if the given field or declaring class have attribute . False is returned otherwise. + private bool ShouldCacheLookup() + { + var cacheAttributeType = typeof(CacheLookupAttribute); + bool cache = Member.GetCustomAttributes(cacheAttributeType, true).Length != 0 || + Member.DeclaringType.GetCustomAttributes(cacheAttributeType, true).Length != 0; + return cache; + } + + private ReadOnlyCollection WaitFor() + { + IWebDriver driver = WrappedDriver; + ITimeouts timeOuts = driver.Manage().Timeouts(); + + DefaultWait wait = new DefaultWait(SearchParameters); + wait.Timeout = TimeOutContainer.WaitingTimeSpan; + wait.PollingInterval = TimeOutContainer.TimeForSleeping; + + timeOuts.ImplicitlyWait(TimeSpan.MinValue); + ReadOnlyCollection result; + try + { + result = wait.Until(FindElements()); + } + catch (WebDriverTimeoutException) + { + result = new ReadOnlyCollection(new List()); + } + finally + { + timeOuts.ImplicitlyWait(TimeOutContainer.WaitingTimeSpan); + } + return result; + } + + public ReadOnlyCollection Elements + { + get + { + if (CachedElementCollection != null && ShouldCacheLookUp) + { + return CachedElementCollection; + } + + ReadOnlyCollection result = WaitFor(); + + if (ShouldCacheLookUp) + { + CachedElementCollection = result; + } + return result; + } + + } + + public class SearchParameterContainer + { + private ReadOnlyCollection Bys; + private ISearchContext SearchContext; + + public ReadOnlyCollection TheGivenBys + { + set + { + Bys = value; + } + get + { + return Bys; + } + } + public ISearchContext Context + { + set + { + SearchContext = value; + } + get + { + return SearchContext; + } + } + } +} +} diff --git a/dotnet/src/support/PageObjects/DefaultElementLocatorFactory.cs b/dotnet/src/support/PageObjects/DefaultElementLocatorFactory.cs index 8c8770f9e8680..14c18e7817299 100644 --- a/dotnet/src/support/PageObjects/DefaultElementLocatorFactory.cs +++ b/dotnet/src/support/PageObjects/DefaultElementLocatorFactory.cs @@ -26,6 +26,7 @@ namespace OpenQA.Selenium.Support.PageObjects /// A default locator for elements for use with the . This locator /// implements no retry logic for elements not being found, nor for elements being stale. /// + [Obsolete("It is supposed to be removed. It is better to use OpenQA.Selenium.Support.PageObjects.DefaultLocatorFactory")] public class DefaultElementLocatorFactory : IElementLocatorFactory { /// diff --git a/dotnet/src/support/PageObjects/DefaultFieldDecorator.cs b/dotnet/src/support/PageObjects/DefaultFieldDecorator.cs new file mode 100644 index 0000000000000..0c8102bb9bd63 --- /dev/null +++ b/dotnet/src/support/PageObjects/DefaultFieldDecorator.cs @@ -0,0 +1,211 @@ +using OpenQA.Selenium.Internal; +using OpenQA.Selenium.Remote; +using OpenQA.Selenium.Support.PageObjects.Interfaces; +using System; +using System.Collections.Generic; +using System.Collections.ObjectModel; +using System.Reflection; +using System.Reflection.Emit; + +namespace OpenQA.Selenium.Support.PageObjects +{ + /// + /// Default decorator for use with . Will decorate 1) all of the + /// fields/properties and 2) IList of fields/properties that have + /// , , + /// attributes with a proxy that locates the elements using the passed + /// in . + /// + /// Note!!! Your own attributes and are appreciated. + /// + public class DefaultFieldDecorator: IFieldDecorator, IAdjustableByTimeSpan + { + + private readonly ILocatorFactory Factory; + private readonly List InterfacesThatCanBeProxiedAsWebElement = new List(); + + /// + /// This decorator uses ILocatorFactory instance in order to + /// populate fields / set properties + /// + /// + public DefaultFieldDecorator(ILocatorFactory factory) + { + if (factory == null) + { + throw new ArgumentNullException("The given Locator factory should not be NULL!"); + } + this.Factory = factory; + InterfacesThatCanBeProxiedAsWebElement.AddRange(typeof(RemoteWebElement).GetInterfaces()); + InterfacesThatCanBeProxiedAsWebElement.Add(typeof(IWrapsElement)); + } + + /// + /// This method can be used by external for Selenium projects where IWebElement implementors + /// implement more interfaces than it does RemoteWebElement + /// + /// Additional interfaces which have to be proxied. They should be related to + /// IWebElement implentors at external for Selenium projects + public void AddInterfacesToByProxied(List interfacesToByProxied) + { + foreach (var type in interfacesToByProxied) + { + if (type.IsInterface) + { + continue; + } + throw new ArgumentException("One of given types is not interface. It is " + type.Name, "interfacesToByProxied"); + } + InterfacesThatCanBeProxiedAsWebElement.AddRange(interfacesToByProxied); + } + + /// + /// This decorator returns values if : + /// - The declared type of field or property is IWebElement or any interface implemented by + /// - The declared type of field or property is IList. The declared + /// generic parameter type should be IWebElement or any interface implemented by . + /// + /// + /// + public object Decorate(MemberInfo member) + { + return CreateProxy(member); + } + + /// + /// This method creates proxies in order to populate + /// fields/set properties of page object. It can be overridden when it is needed. + /// + /// + /// + protected virtual Object CreateProxy(MemberInfo member) + { + FieldInfo field = member as FieldInfo; + PropertyInfo property = member as PropertyInfo; + + Type targetType = null; + if (field != null) + { + targetType = field.FieldType; + } + + bool hasPropertySet = false; + if (property != null) + { + hasPropertySet = (property.CanWrite); + targetType = property.PropertyType; + } + + if (field == null & (property == null | !hasPropertySet)) + { + return null; + } + + IElementLocator locator = Factory.CreateElementLocator(member); + + if (InterfacesThatCanBeProxiedAsWebElement.Contains(targetType)) + { + ElementProxy ep = new ElementProxy(CreateTypeForASingleElement(), locator); + return ep.GetTransparentProxy(); + } + + foreach (var type in InterfacesThatCanBeProxiedAsWebElement) + { + Type listType = typeof(IList<>).MakeGenericType(type); + if (listType.Equals(targetType)) + { + + ElementListProxy ep = new ElementListProxy(targetType, locator); + return ep.GetTransparentProxy(); + } + } + return null; + } + + /// + /// This property gets/sets waiting time at if the given + /// implements . + /// Otherwise it does nothing (set) and returns TimeSpan.MinVlue. + /// + public TimeSpan WaitingTimeSpan + { + get + { + IAdjustableByTimeSpan adjustableByTimeSpan = Factory as IAdjustableByTimeSpan; + if (adjustableByTimeSpan != null) + { + return adjustableByTimeSpan.WaitingTimeSpan; + } + return TimeSpan.MinValue; + } + set + { + IAdjustableByTimeSpan adjustableByTimeSpan = Factory as IAdjustableByTimeSpan; + if (adjustableByTimeSpan != null) + { + adjustableByTimeSpan.WaitingTimeSpan = value; + } + } + } + + /// + /// This property gets/sets sleeping (polling) time at if the given + /// implements . + /// Otherwise it does nothing (set) and returns TimeSpan.MinVlue. + /// + public TimeSpan TimeForSleeping + { + get + { + IAdjustableByTimeSpan adjustableByTimeSpan = Factory as IAdjustableByTimeSpan; + if (adjustableByTimeSpan != null) + { + return adjustableByTimeSpan.TimeForSleeping; + } + return TimeSpan.MinValue; + } + set + { + IAdjustableByTimeSpan adjustableByTimeSpan = Factory as IAdjustableByTimeSpan; + if (adjustableByTimeSpan != null) + { + adjustableByTimeSpan.TimeForSleeping = value; + } + } + } + + /// True if if the given + /// implements . + public bool IsAdjustableByTimeSpan() + { + return ((Factory as IAdjustableByTimeSpan) != null); + } + + private Type CreateTypeForASingleElement() + { + var orginalAssemblyName = typeof(IWebElement).Assembly.GetName().Name; + ModuleBuilder moduleBuilder; + + var tempAssemblyName = new AssemblyName(Guid.NewGuid().ToString()); + + var dynamicAssembly = AppDomain.CurrentDomain.DefineDynamicAssembly( + tempAssemblyName, + System.Reflection.Emit.AssemblyBuilderAccess.RunAndCollect); + + moduleBuilder = dynamicAssembly.DefineDynamicModule( + tempAssemblyName.Name, + tempAssemblyName + ".dll"); + + var typeBuilder = moduleBuilder.DefineType( + typeof(IWebElement).FullName, + TypeAttributes.Public | TypeAttributes.Interface | TypeAttributes.Abstract); + + foreach (Type type in InterfacesThatCanBeProxiedAsWebElement) + { + typeBuilder.AddInterfaceImplementation(type); + } + + return typeBuilder.CreateType(); + } + } +} diff --git a/dotnet/src/support/PageObjects/DefaultLocatorFactory.cs b/dotnet/src/support/PageObjects/DefaultLocatorFactory.cs new file mode 100644 index 0000000000000..dde667af19973 --- /dev/null +++ b/dotnet/src/support/PageObjects/DefaultLocatorFactory.cs @@ -0,0 +1,81 @@ +using OpenQA.Selenium.Support.PageObjects.Interfaces; +using System; +using System.Collections.Generic; +using System.Linq; +using System.Reflection; +using System.Text; + +namespace OpenQA.Selenium.Support.PageObjects +{ + /// + /// The default ILocatorFactory implementor + /// + public class DefaultLocatorFactory: ILocatorFactory, IAdjustableByTimeSpan + { + + private static readonly TimeSpan DefaultWaitingTime = TimeSpan.FromSeconds(5); + private static readonly TimeSpan DefaultSleepingTime = TimeSpan.FromMilliseconds(500); + + protected TimeSpan WaitingTime; + protected TimeSpan SleepingTime; + protected readonly ISearchContext SearchContext; + + /// + /// It uses the given ISearchContext (IWebdriver or IWebElement) instance and + /// the waiting/sleeping timeouts + /// + /// IWebdriver or IWebElement instance + /// The waiting timeout + /// The sleeping/polling timeout + public DefaultLocatorFactory(ISearchContext searchContext, TimeSpan waitingTime, TimeSpan sleepingTime) + { + this.SearchContext = searchContext; + this.WaitingTime = waitingTime; + this.SleepingTime = sleepingTime; + } + + /// + /// It uses the given ISearchContext (IWebdriver or IWebElement) instance. The default waiting + /// timeout is 5 seconds. The default sleeping/polling timeout is 500 milliseconds. + /// + /// IWebdriver or IWebElement instance + public DefaultLocatorFactory(ISearchContext searchContext) + :this(searchContext, DefaultWaitingTime, DefaultSleepingTime) + { + } + + /// + /// + /// + /// + /// + public IElementLocator CreateElementLocator(MemberInfo member) + { + return new DefaultElementLocator(member, SearchContext, this); + } + + public TimeSpan WaitingTimeSpan + { + get + { + return WaitingTime; + } + set + { + WaitingTime = value; + } + } + + public TimeSpan TimeForSleeping + { + get + { + return SleepingTime; + } + set + { + SleepingTime = value; + } + } + } +} diff --git a/dotnet/src/support/PageObjects/ElementListProxy.cs b/dotnet/src/support/PageObjects/ElementListProxy.cs new file mode 100644 index 0000000000000..b566dc4c85e01 --- /dev/null +++ b/dotnet/src/support/PageObjects/ElementListProxy.cs @@ -0,0 +1,23 @@ +using OpenQA.Selenium.Support.PageObjects.Interfaces; +using System; +using System.Reflection; +using System.Runtime.Remoting.Messaging; + +namespace OpenQA.Selenium.Support.PageObjects +{ + /// + /// Intercepts the request to a collection of 's + /// + sealed class ElementListProxy: Proxy + { + public ElementListProxy(Type typeToBeProxied, IElementLocator elementLocator) + :base(typeToBeProxied, elementLocator) + {} + + public override IMessage Invoke(IMessage msg) + { + var elements = ElementLocator.Elements; + return base.Execute(msg as IMethodCallMessage, elements); + } + } +} diff --git a/dotnet/src/support/PageObjects/ElementProxy.cs b/dotnet/src/support/PageObjects/ElementProxy.cs new file mode 100644 index 0000000000000..7c1d47816e8ef --- /dev/null +++ b/dotnet/src/support/PageObjects/ElementProxy.cs @@ -0,0 +1,32 @@ +using OpenQA.Selenium.Support.PageObjects.Interfaces; +using System; +using System.Reflection; +using OpenQA.Selenium.Internal; +using System.Runtime.Remoting.Messaging; + +namespace OpenQA.Selenium.Support.PageObjects +{ + /// + /// Intercepts the request to a single + /// + sealed class ElementProxy : Proxy + { + public ElementProxy(Type typeToBeProxied, IElementLocator elementLocator) + : base(typeToBeProxied, elementLocator) + {} + + public override IMessage Invoke(IMessage msg) + { + var element = ElementLocator.Element; + IMethodCallMessage call = msg as IMethodCallMessage; + + if (typeof(IWrapsElement).IsAssignableFrom((call.MethodBase as MethodInfo).DeclaringType)) + { + return new ReturnMessage(element, null, 0, + call.LogicalCallContext, call); + } + return base.Execute(call, element); + + } + } +} diff --git a/dotnet/src/support/PageObjects/IElementLocatorFactory.cs b/dotnet/src/support/PageObjects/IElementLocatorFactory.cs index 8f9a5ea5cc2a7..ef328b1f30380 100644 --- a/dotnet/src/support/PageObjects/IElementLocatorFactory.cs +++ b/dotnet/src/support/PageObjects/IElementLocatorFactory.cs @@ -25,6 +25,7 @@ namespace OpenQA.Selenium.Support.PageObjects /// /// Interface describing how elements are to be located by a /// + [Obsolete("It is supposed to be removed", true)] public interface IElementLocatorFactory { /// diff --git a/dotnet/src/support/PageObjects/Interfaces/IAdjustableByTimeSpan.cs b/dotnet/src/support/PageObjects/Interfaces/IAdjustableByTimeSpan.cs new file mode 100644 index 0000000000000..96ad414b90c42 --- /dev/null +++ b/dotnet/src/support/PageObjects/Interfaces/IAdjustableByTimeSpan.cs @@ -0,0 +1,33 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using System.Text; + +namespace OpenQA.Selenium.Support.PageObjects.Interfaces +{ + /// + /// This interface is for entities which require + /// to change their behavior by the given value. + /// It is expected that they can wait for something. + /// + public interface IAdjustableByTimeSpan + { + /// + /// This property should get or set a timeout for the waiting. + /// + TimeSpan WaitingTimeSpan + { + set; + get; + } + + /// + /// This property should get or set a timeout for the sleeping. + /// + TimeSpan TimeForSleeping + { + set; + get; + } + } +} diff --git a/dotnet/src/support/PageObjects/Interfaces/IElementLocator.cs b/dotnet/src/support/PageObjects/Interfaces/IElementLocator.cs new file mode 100644 index 0000000000000..aa2d02499e59f --- /dev/null +++ b/dotnet/src/support/PageObjects/Interfaces/IElementLocator.cs @@ -0,0 +1,23 @@ +using System; +using System.Collections.Generic; +using System.Collections.ObjectModel; +using System.Linq; +using System.Text; + +namespace OpenQA.Selenium.Support.PageObjects.Interfaces +{ + /// + /// + /// + public interface IElementLocator + { + /// + /// This property should return a single IWebElement instance + /// + IWebElement Element { get; } + /// + /// This property should return a read-only collection of IWebElement instances + /// + ReadOnlyCollection Elements { get; } + } +} diff --git a/dotnet/src/support/PageObjects/Interfaces/IFieldDecorator.cs b/dotnet/src/support/PageObjects/Interfaces/IFieldDecorator.cs new file mode 100644 index 0000000000000..8c018bfc628ba --- /dev/null +++ b/dotnet/src/support/PageObjects/Interfaces/IFieldDecorator.cs @@ -0,0 +1,21 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using System.Reflection; +using System.Text; + +namespace OpenQA.Selenium.Support.PageObjects.Interfaces +{ + /// + ///Allows the PageFactory to decorate fields. + /// + public interface IFieldDecorator + { + /// + /// This method is called by PageFactory on all fields to decide how to decorate the field. + /// + /// It is a a field OR property that is supposed to be decorated + /// An object that should be set up as member value + Object Decorate(MemberInfo member); + } +} diff --git a/dotnet/src/support/PageObjects/Interfaces/ILocatorFactory.cs b/dotnet/src/support/PageObjects/Interfaces/ILocatorFactory.cs new file mode 100644 index 0000000000000..2278c37144992 --- /dev/null +++ b/dotnet/src/support/PageObjects/Interfaces/ILocatorFactory.cs @@ -0,0 +1,23 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using System.Reflection; +using System.Text; + +namespace OpenQA.Selenium.Support.PageObjects.Interfaces +{ + /// + /// A factory for producing s. It is expected that a new instance will be + /// returned per call. + /// + public interface ILocatorFactory + { + /// + /// When a field/property of a class needs to be decorated with an this method will + /// be called. + /// + /// a field OR property that should be filled + /// An instance + IElementLocator CreateElementLocator(MemberInfo member); + } +} diff --git a/dotnet/src/support/PageObjects/PageFactory.cs b/dotnet/src/support/PageObjects/PageFactory.cs index 0cd186da4c8da..e420c31fa83ba 100644 --- a/dotnet/src/support/PageObjects/PageFactory.cs +++ b/dotnet/src/support/PageObjects/PageFactory.cs @@ -23,6 +23,7 @@ using System.Reflection; using OpenQA.Selenium.Interactions.Internal; using OpenQA.Selenium.Internal; +using OpenQA.Selenium.Support.PageObjects.Interfaces; namespace OpenQA.Selenium.Support.PageObjects { @@ -52,13 +53,10 @@ private PageFactory() /// /// /// thrown if no constructor to the class can be found with a single IWebDriver argument - /// -or- - /// if a field or property decorated with the is not of type - /// or IList{IWebElement}. /// public static T InitElements(IWebDriver driver) { - return InitElements(driver, new DefaultElementLocatorFactory()); + return InitElements(driver, new DefaultLocatorFactory(driver)); } /// @@ -66,7 +64,7 @@ public static T InitElements(IWebDriver driver) /// /// The of the Page Object class. /// The instance used to populate the page. - /// The implementation that + /// The implementation that /// determines how elements are located. /// An instance of the Page Object class with the elements initialized. /// @@ -77,11 +75,8 @@ public static T InitElements(IWebDriver driver) /// /// /// thrown if no constructor to the class can be found with a single IWebDriver argument - /// -or- - /// if a field or property decorated with the is not of type - /// or IList{IWebElement}. /// - public static T InitElements(IWebDriver driver, IElementLocatorFactory locatorFactory) + public static T InitElements(IWebDriver driver, ILocatorFactory locatorFactory) { T page = default(T); Type pageClassType = typeof(T); @@ -92,24 +87,132 @@ public static T InitElements(IWebDriver driver, IElementLocatorFactory locato } page = (T)ctor.Invoke(new object[] { driver }); - InitElements(driver, page, locatorFactory); + InitElements(page, locatorFactory); return page; } /// /// Initializes the elements in the Page Object. /// - /// The driver used to find elements on the page. + /// The IWebDriver or IWebElement implementation used to find elements on the page. + /// The Page Object to be populated with elements. + public static void InitElements(ISearchContext searchContext, object page) + { + InitElements(page, new DefaultLocatorFactory(searchContext)); + } + + /// + /// Initializes the elements in the Page Object. + /// + /// The Page Object to be populated with elements. + /// The implementation that + /// determines how elements are located. + public static void InitElements(object page, ILocatorFactory locatorFactory) + { + if (page == null) + { + throw new ArgumentNullException("page", "page cannot be null"); + } + + if (locatorFactory == null) + { + throw new ArgumentNullException("locatorFactory", "locatorFactory cannot be null"); + } + + InitElements(new DefaultFieldDecorator(locatorFactory), page); + } + + /// + /// Initializes the elements in the Page Object. + /// /// The Page Object to be populated with elements. + /// The implementation that + /// decorates IWebElement or IList of IWebElement fields . + public static void InitElements(IFieldDecorator decorator, object page) + { + if (decorator == null) + { + throw new ArgumentNullException("decorator", "decorator cannot be null"); + } + + if (page == null) + { + throw new ArgumentNullException("page", "page cannot be null"); + } + + var members = new List(); + var type = page.GetType(); + const BindingFlags PublicBindingOptions = BindingFlags.Instance | BindingFlags.Public; + const BindingFlags NonPublicBindingOptions = BindingFlags.Instance | BindingFlags.NonPublic; + + members.AddRange(type.GetFields(PublicBindingOptions)); + members.AddRange(type.GetProperties(PublicBindingOptions)); + + while (type != null) + { + members.AddRange(type.GetFields(NonPublicBindingOptions)); + members.AddRange(type.GetProperties(NonPublicBindingOptions)); + type = type.BaseType; + } + + foreach (var member in members){ + var field = member as FieldInfo; + var property = member as PropertyInfo; + + var value = decorator.Decorate(member); + + if (value == null){ + continue; + } + + if (field != null){ + field.SetValue(page, value); + } + + if (property != null) + { + property.SetValue(page, value, null); + } + } + } + + #region ObsolateAPI + /// + /// Initializes the elements in the Page Object with the given type. + /// + /// The of the Page Object class. + /// The instance used to populate the page. + /// The implementation that + /// determines how elements are located. + /// An instance of the Page Object class with the elements initialized. + /// + /// The class used in the argument must have a public constructor + /// that takes a single argument of type . This helps to enforce + /// best practices of the Page Object pattern, and encapsulates the driver into the Page + /// Object so that it can have no external WebDriver dependencies. + /// /// - /// thrown if a field or property decorated with the is not of type + /// thrown if no constructor to the class can be found with a single IWebDriver argument + /// -or- + /// if a field or property decorated with the is not of type /// or IList{IWebElement}. /// - public static void InitElements(ISearchContext driver, object page) + [Obsolete("It is supposed to be removed. It is better to use InitElements(object page, ILocatorFactory locatorFactory)")] + public static T InitElements(IWebDriver driver, IElementLocatorFactory locatorFactory) { - InitElements(driver, page, new DefaultElementLocatorFactory()); + T page = default(T); + Type pageClassType = typeof(T); + ConstructorInfo ctor = pageClassType.GetConstructor(new Type[] { typeof(IWebDriver) }); + if (ctor == null) + { + throw new ArgumentException("No constructor for the specified class containing a single argument of type IWebDriver can be found"); + } + + page = (T)ctor.Invoke(new object[] { driver }); + InitElements(driver, page, locatorFactory); + return page; } - + /// /// Initializes the elements in the Page Object. /// @@ -121,6 +224,7 @@ public static void InitElements(ISearchContext driver, object page) /// thrown if a field or property decorated with the is not of type /// or IList{IWebElement}. /// + [Obsolete("It is supposed to be removed.")] public static void InitElements(ISearchContext driver, object page, IElementLocatorFactory locatorFactory) { if (page == null) @@ -156,7 +260,7 @@ public static void InitElements(ISearchContext driver, object page, IElementLoca if (bys.Count > 0) { bool cache = ShouldCacheLookup(member); - + object proxyObject = null; var field = member as FieldInfo; var property = member as PropertyInfo; @@ -184,6 +288,7 @@ public static void InitElements(ISearchContext driver, object page, IElementLoca } } + [Obsolete("It is supposed to be removed.")] private static List CreateLocatorList(MemberInfo member) { var useSequenceAttributes = Attribute.GetCustomAttributes(member, typeof(FindsBySequenceAttribute), true); @@ -216,6 +321,7 @@ private static List CreateLocatorList(MemberInfo member) return bys; } + [Obsolete("It is supposed to be removed.")] private static bool ShouldCacheLookup(MemberInfo member) { var cacheAttributeType = typeof(CacheLookupAttribute); @@ -223,6 +329,7 @@ private static bool ShouldCacheLookup(MemberInfo member) return cache; } + [Obsolete("It is supposed to be removed.")] private static object CreateProxyObject(Type memberType, ISearchContext driver, List bys, bool cache, IElementLocatorFactory locatorFactory) { object proxyObject = null; @@ -237,5 +344,7 @@ private static object CreateProxyObject(Type memberType, ISearchContext driver, return proxyObject; } + + #endregion ObsolateAPI } } diff --git a/dotnet/src/support/PageObjects/Proxy.cs b/dotnet/src/support/PageObjects/Proxy.cs new file mode 100644 index 0000000000000..0771b3d733bae --- /dev/null +++ b/dotnet/src/support/PageObjects/Proxy.cs @@ -0,0 +1,34 @@ +using OpenQA.Selenium.Support.PageObjects.Interfaces; +using System; +using System.Collections.Generic; +using System.Reflection; +using System.Runtime.Remoting.Messaging; +using System.Runtime.Remoting.Proxies; + +namespace OpenQA.Selenium.Support.PageObjects +{ + abstract class Proxy: RealProxy + { + protected readonly IElementLocator ElementLocator; + + public Proxy(Type typeToBeProxied, IElementLocator elementLocator) + :base(typeToBeProxied) + { + this.ElementLocator = elementLocator; + } + + /// + /// This is the common behavior of proxy. + /// The method will be used or overridden by subclasses + /// + /// This is container of method invocation parameters + /// This the real proxied object + /// The ReturnMessage instance as a result of proxied method invocation. + protected ReturnMessage Execute(IMethodCallMessage msg, object proxied) + { + MethodInfo proxiedMethod = msg.MethodBase as MethodInfo; + return new ReturnMessage(proxiedMethod.Invoke(proxied, msg.Args), null, 0, + msg.LogicalCallContext, msg); + } + } +} diff --git a/dotnet/src/support/PageObjects/RetryingElementLocatorFactory.cs b/dotnet/src/support/PageObjects/RetryingElementLocatorFactory.cs index b2597094c3741..8b9ad2869d052 100644 --- a/dotnet/src/support/PageObjects/RetryingElementLocatorFactory.cs +++ b/dotnet/src/support/PageObjects/RetryingElementLocatorFactory.cs @@ -27,6 +27,7 @@ namespace OpenQA.Selenium.Support.PageObjects /// A locator for elements for use with the that retries locating /// the element up to a timeout if the element is not found. /// + [Obsolete("It is supposed to be removed. It is better to use OpenQA.Selenium.Support.PageObjects.DefaultLocatorFactory")] public class RetryingElementLocatorFactory : IElementLocatorFactory { private static readonly TimeSpan DefaultTimeout = TimeSpan.FromSeconds(5); @@ -140,7 +141,7 @@ public ReadOnlyCollection LocateElements(ISearchContext searchConte collection.AddRange(list); } - timeoutReached = collection.Count == 0 && DateTime.Now > endTime; + timeoutReached = collection.Count != 0 || DateTime.Now > endTime; if (!timeoutReached) { Thread.Sleep(this.pollingInterval); diff --git a/dotnet/src/support/PageObjects/WebElementListProxy.cs b/dotnet/src/support/PageObjects/WebElementListProxy.cs index 0a29f4f592fbe..f12210faf053f 100644 --- a/dotnet/src/support/PageObjects/WebElementListProxy.cs +++ b/dotnet/src/support/PageObjects/WebElementListProxy.cs @@ -27,6 +27,7 @@ namespace OpenQA.Selenium.Support.PageObjects /// /// Represents a proxy class for a list of elements to be used with the PageFactory. /// + [Obsolete("It is supposed to be removed")] internal class WebElementListProxy : IList { private readonly IElementLocatorFactory locatorFactory; diff --git a/dotnet/src/support/PageObjects/WebElementProxy.cs b/dotnet/src/support/PageObjects/WebElementProxy.cs index c0f9491a8158f..3e312f28a6998 100644 --- a/dotnet/src/support/PageObjects/WebElementProxy.cs +++ b/dotnet/src/support/PageObjects/WebElementProxy.cs @@ -24,11 +24,13 @@ using OpenQA.Selenium.Interactions.Internal; using OpenQA.Selenium.Internal; + namespace OpenQA.Selenium.Support.PageObjects { /// /// Represents a proxy class for an element to be used with the PageFactory. /// + [Obsolete("It is supposed to be removed")] internal class WebElementProxy : IWebElement, ILocatable, IWrapsElement { private readonly IElementLocatorFactory locatorFactory; diff --git a/dotnet/src/support/WebDriver.Support.csproj b/dotnet/src/support/WebDriver.Support.csproj index 98bdadb6ae277..9493a8c8b5acb 100644 --- a/dotnet/src/support/WebDriver.Support.csproj +++ b/dotnet/src/support/WebDriver.Support.csproj @@ -79,15 +79,25 @@ - - + + + + + + + + + + + + diff --git a/dotnet/test/support/PageObjects/DefaultLocatorTest.cs b/dotnet/test/support/PageObjects/DefaultLocatorTest.cs new file mode 100644 index 0000000000000..2ee0c3964bea1 --- /dev/null +++ b/dotnet/test/support/PageObjects/DefaultLocatorTest.cs @@ -0,0 +1,95 @@ +using NMock2; +using NUnit.Framework; +using OpenQA.Selenium.Support.PageObjects.Interfaces; +using System; +using System.Collections.Generic; +using System.Collections.ObjectModel; +using System.Linq; +using System.Reflection; +using System.Text; + +namespace OpenQA.Selenium.Support.PageObjects +{ + [TestFixture] + public class DefaultLocatorTest + { + [FindsBy(How = How.Id, Using = "SomeId", Priority = 0)] + [FindsBy(How = How.ClassName, Using = "SomeClass", Priority = 1)] + [FindsBy(How = How.TagName, Using = "SomeTag", Priority = 2)] + public IWebElement F1; + + [FindsBySequence] + [FindsBy(How = How.Id, Using = "SomeId", Priority = 0)] + [FindsBy(How = How.TagName, Using = "SomeTag", Priority = 2)] + [FindsBy(How = How.ClassName, Using = "SomeClass", Priority = 1)] + public IWebElement F2; + + public IWebElement F3; + + private static Mockery mocks = new Mockery(); + private static ISearchContext mockDriver = mocks.NewMock(); + private static TestLocatorFactory factory = new TestLocatorFactory(mockDriver); + + [Test] + public void ShouldReturnCollectionOfBys() + { + FieldInfo f1 = this.GetType().GetField("F1"); + TestLocator tl = (TestLocator)factory.CreateElementLocator(f1); + IList result = tl.getSearchParameters().TheGivenBys; + Assert.AreEqual(3, result.Count); + + Assert.IsTrue(result[0].ToString().Contains("SomeId")); + Assert.IsTrue(result[1].ToString().Contains("SomeClass")); + Assert.IsTrue(result[2].ToString().Contains("SomeTag")); + } + + [Test] + public void ShouldReturnByChained() + { + FieldInfo f2 = this.GetType().GetField("F2"); + TestLocator t2 = (TestLocator)factory.CreateElementLocator(f2); + IList result = t2.getSearchParameters().TheGivenBys; + Assert.AreEqual(1, result.Count); + Assert.IsTrue(result[0] as ByChained != null); + } + + [Test] + public void ShouldReturnByIdOrName() + { + FieldInfo f3 = this.GetType().GetField("F3"); + TestLocator t3 = (TestLocator)factory.CreateElementLocator(f3); + IList result = t3.getSearchParameters().TheGivenBys; + Assert.AreEqual(1, result.Count); + Assert.IsTrue(result[0] as ByIdOrName != null); + Assert.IsTrue(result[0].ToString().Contains("F3")); + } + } + + public class TestLocator : DefaultElementLocator + { + + public TestLocator(MemberInfo member, ISearchContext searchContext, IAdjustableByTimeSpan timeOutContainer) + :base(member, searchContext, timeOutContainer) + {} + + public SearchParameterContainer getSearchParameters() + { + return SearchParameters; + } + } + + public class TestLocatorFactory : DefaultLocatorFactory + { + public TestLocatorFactory(ISearchContext searchContext) + :base(searchContext) + { + } + + public IElementLocator CreateElementLocator(MemberInfo member) + { + return new TestLocator(member, SearchContext, this); + } + } + + +} diff --git a/dotnet/test/support/PageObjects/FieldPopulationByDefaultFieldDecoratorTest.cs b/dotnet/test/support/PageObjects/FieldPopulationByDefaultFieldDecoratorTest.cs new file mode 100644 index 0000000000000..13cc19a4ec417 --- /dev/null +++ b/dotnet/test/support/PageObjects/FieldPopulationByDefaultFieldDecoratorTest.cs @@ -0,0 +1,125 @@ +using NMock2; +using NUnit.Framework; +using OpenQA.Selenium.Internal; +using System; +using System.Collections.Generic; +using System.Linq; +using System.Text; + +namespace OpenQA.Selenium.Support.PageObjects +{ + [TestFixture] + public class FieldPopulationByDefaultFieldDecoratorTest + { + private static Mockery mocks = new Mockery(); + private ISearchContext mockDriver = mocks.NewMock(); + + [FindsBy(How = How.Id, Using = "SomeId")] + private IWebElement mockElement1; + + [FindsBy(How = How.Id, Using = "SomeId")] + private IWebDriver mockElement2; + + [FindsBy(How = How.Id, Using = "SomeId")] + private IFindsById mockElement3; + + [FindsBy(How = How.Id, Using = "SomeId")] + private IList mockElements1; + + [FindsBy(How = How.Id, Using = "SomeId")] + private IList mockElements2; + + [FindsBy(How = How.Id, Using = "SomeId")] + private IList mockElements3; + + private IWebDriver unmarkedField2; + + private IWebElement unmarkedField1; + + private IFindsById unmarkedField3; + + private IList unmarkedField5; + + private IList unmarkedField4; + + private IList unmarkedField6; + + [SetUp] + public void SetUp() + { + PageFactory.InitElements(mockDriver, this); + } + + [Test] + public void IsPopulatedMarkedWebElementField() + { + Assert.NotNull(mockElement1); + } + + [Test] + public void IsNotPopulatedMarkedNotWebElementField() + { + Assert.IsNull(mockElement2); + } + + [Test] + public void IsPopulatedMarkedFieldWithInterfaceImplementedByRemoteWebElementent() + { + Assert.NotNull(mockElement3); + } + + [Test] + public void IsPopulatedMarkedListOfWebElementField() + { + Assert.NotNull(mockElements1); + } + + [Test] + public void IsNotPopulatedMarkedNotWebElementListField() + { + Assert.IsNull(mockElements2); + } + + [Test] + public void IsPopulatedMarkedListFieldWithInterfaceImplementedByRemoteWebElementent() + { + Assert.NotNull(mockElements3); + } + + [Test(Description = "At this case ByIdOrName locator strategy should be used")] + public void IsPopulatedWebElementFieldWithoutAttributes() + { + Assert.NotNull(unmarkedField1); + } + + [Test] + public void IsNotPopulatedNotWebElementFieldWithoutAttributes() + { + Assert.IsNull(unmarkedField2); + } + + [Test(Description = "At this case ByIdOrName locator strategy should be used")] + public void IsPopulatedFieldWithInterfaceImplementedByRemoteWebElemententWithoutAttributes() + { + Assert.NotNull(unmarkedField3); + } + + [Test(Description = "At this case ByIdOrName locator strategy should be used")] + public void IsPopulatedListOfWebElementFieldWithoutAttributes() + { + Assert.NotNull(unmarkedField6); + } + + [Test] + public void IsNotPopulatedNotWebElementListFieldWithoutAttributes() + { + Assert.IsNull(unmarkedField5); + } + + [Test(Description = "At this case ByIdOrName locator strategy should be used")] + public void IsPopulatedListFieldWithInterfaceImplementedByRemoteWebElemententWithoutAttributes() + { + Assert.NotNull(unmarkedField4); + } + } +} diff --git a/dotnet/test/support/PageObjects/PageFactoryBrowserTest.cs b/dotnet/test/support/PageObjects/PageFactoryBrowserTest.cs index 86d337fec6929..3480e25e21f9f 100644 --- a/dotnet/test/support/PageObjects/PageFactoryBrowserTest.cs +++ b/dotnet/test/support/PageObjects/PageFactoryBrowserTest.cs @@ -5,6 +5,7 @@ using OpenQA.Selenium.Support.UI; using System.Collections.ObjectModel; using System; +using OpenQA.Selenium.Internal; namespace OpenQA.Selenium.Support.PageObjects { @@ -35,33 +36,53 @@ public void LooksUpAgainAfterPageNavigation() driver.Navigate().Refresh(); - Assert.True(page.formElement.Displayed); + Assert.True(page.formTestElement.Displayed); + } + + [Test] + public void CheckThatListIsFoundByIdOrName() + { + driver.Url = xhtmlTestPage; + var page = new Page(); + + PageFactory.InitElements(driver, page); + Assert.GreaterOrEqual(1, page.someForm.Count); + } + + [Test] + public void CheckThatElementIsFoundByIdOrName() + { + driver.Url = xhtmlTestPage; + var page = new Page(); + + PageFactory.InitElements(driver, page); + Assert.IsTrue(page.parent.Displayed); } [Test] public void ElementEqualityWorks() { driver.Url = xhtmlTestPage; - var page = new PageFactoryTest.Page(); + var page = new Page(); PageFactory.InitElements(driver, page); var expectedElement = driver.FindElement(By.Name("someForm")); + var result = ((IWrapsElement)page.formTestElement).WrappedElement; - Assert.True(page.formElement.Equals(expectedElement)); - Assert.True(expectedElement.Equals(page.formElement)); - Assert.AreEqual(expectedElement.GetHashCode(), page.formElement.GetHashCode()); + Assert.True(result.Equals(expectedElement)); + Assert.AreEqual(expectedElement.GetHashCode(), result.GetHashCode()); } [Test] public void UsesElementAsScriptArgument() { driver.Url = xhtmlTestPage; - var page = new PageFactoryTest.Page(); + var page = new Page(); PageFactory.InitElements(driver, page); - var tagName = (string)((IJavaScriptExecutor)driver).ExecuteScript("return arguments[0].tagName", page.formElement); + var tagName = (string)((IJavaScriptExecutor)driver).ExecuteScript("return arguments[0].tagName", page.formTestElement); Assert.AreEqual("form", tagName.ToLower()); } @@ -105,7 +126,10 @@ public void ShouldFindElementUsingSequence() private class Page { [FindsBy(How = How.Name, Using = "someForm")] - public IWebElement formElement; + public IWebElement formTestElement; + + public IList someForm; + public IWebElement parent; [FindsBySequence] [FindsBy(How = How.Id, Using = "parent", Priority = 0)] diff --git a/dotnet/test/support/PageObjects/PageFactoryTest.cs b/dotnet/test/support/PageObjects/PageFactoryTest.cs deleted file mode 100644 index 3565855d7770f..0000000000000 --- a/dotnet/test/support/PageObjects/PageFactoryTest.cs +++ /dev/null @@ -1,493 +0,0 @@ -using System; -using System.Collections.Generic; -using System.Collections.ObjectModel; -using NMock2; -using NUnit.Framework; - -namespace OpenQA.Selenium.Support.PageObjects -{ - [TestFixture] - public class PageFactoryTest - { - private Mockery mocks; - private ISearchContext mockDriver; - private IWebElement mockElement; - - [SetUp] - public void SetUp() - { - mocks = new Mockery(); - mockDriver = mocks.NewMock(); - mockElement = mocks.NewMock(); - } - - [TearDown] - public void TearDown() - { - mocks.VerifyAllExpectationsHaveBeenMet(); - } - - [Test] - public void ElementShouldBeNullUntilInitElementsCalled() - { - var page = new Page(); - - Assert.Null(page.formElement); - - PageFactory.InitElements(mockDriver, page); - Assert.NotNull(page.formElement); - } - - [Test] - public void ElementShouldBeAbleToUseGenericVersionOfInitElements() - { - IWebDriver driver = mocks.NewMock(); - var page = PageFactory.InitElements(driver); - Assert.IsInstanceOf(page); - Assert.NotNull(page.formElement); - } - - [Test] - public void FindsElement() - { - var page = new Page(); - AssertFindsElementByExactlyOneLookup(page, () => page.formElement); - } - - [Test] - public void FindsElementEachAccess() - { - var page = new Page(); - - AssertFindsElementByExactlyOneLookup(page, () => page.formElement); - mocks.VerifyAllExpectationsHaveBeenMet(); - - ExpectOneLookup(); - AssertFoundElement(page.formElement); - } - - [Test] - public void FindsPrivateElement() - { - var page = new PrivatePage(); - AssertFindsElementByExactlyOneLookup(page, page.GetField); - } - - [Test] - public void FindsPropertyElement() - { - var page = new ElementAsPropertyPage(); - AssertFindsElementByExactlyOneLookup(page, () => page.FormElement); - } - - [Test] - public void FindsElementByNameIfUsingIsAbsent() - { - ExpectOneLookup(); - - var page = new PageWithNameWithoutUsing(); - AssertFindsElement(page, () => page.someForm); - } - - [Test] - public void FindsElementByIdIfUsingIsAbsent() - { - Expect.Once.On(mockElement).GetProperty("TagName").Will(Return.Value("form")); - Expect.Once.On(mockDriver).Method("FindElement").With(By.Id("someForm")).Will(Return.Value(mockElement)); - - var page = new PageWithIdWithoutUsing(); - AssertFindsElement(page, () => page.someForm); - } - - [Test] - public void FindsParentAndChildElement() - { - ExpectOneLookup(); - var mockElement2 = mocks.NewMock(); - Expect.Once.On(mockDriver).Method("FindElement").With(By.TagName("body")).Will(Return.Value(mockElement2)); - Expect.Once.On(mockElement2).GetProperty("TagName").Will(Return.Value("body")); - - var page = new ChildPage(); - - AssertFindsElement(page, () => page.formElement); - AssertFoundElement(page.childElement, "body"); - } - - [Test] - public void LooksUpPrivateFieldInSuperClass() - { - var page = new SubClassToPrivatePage(); - AssertFindsElementByExactlyOneLookup(page, page.GetField); - } - - [Test] - public void LooksUpOverridenVirtualParentClassElement() - { - ExpectOneLookup(); - - var page = new AbstractChild(); - AssertFindsElement(page, () => page.element); - } - - [Test] - public void FallsBackOnOtherLocatorsOnFailure() - { - Expect.Once.On(mockDriver).Method("FindElement").With(By.Name("notthisname")).Will(Throw.Exception(new NoSuchElementException())); - Expect.Once.On(mockDriver).Method("FindElement").With(By.TagName("form")).Will(Return.Value(mockElement)); - Expect.Never.On(mockDriver).Method("FindElement").With(By.Id("notthiseither")).Will(Return.Value(mockElement)); - - Expect.Once.On(mockElement).GetProperty("TagName").Will(Return.Value("form")); - - var page = new FallsbackPage(); - PageFactory.InitElements(mockDriver, page); - - AssertFoundElement(page.formElement); - } - - [Test] - public void ThrowsIfAllLocatorsFail() - { - Expect.Once.On(mockDriver).Method("FindElement").With(By.Name("notthisname")).Will(Throw.Exception(new NoSuchElementException())); - Expect.Once.On(mockDriver).Method("FindElement").With(By.TagName("notthiseither")).Will(Throw.Exception(new NoSuchElementException())); - Expect.Once.On(mockDriver).Method("FindElement").With(By.Id("stillnotthis")).Will(Throw.Exception(new NoSuchElementException())); - - var page = new FailsToFallbackPage(); - PageFactory.InitElements(mockDriver, page); - - Assert.Throws(typeof(NoSuchElementException), page.formElement.Clear); - } - - [Test] - public void CachesElement() - { - Expect.Once.On(mockDriver).Method("FindElement").With(By.Name("someForm")).Will(Return.Value(mockElement)); - Expect.Exactly(2).On(mockElement).GetProperty("TagName").Will(Return.Value("form")); - - var page = new CachedPage(); - - AssertFindsElement(page, () => page.formElement); - AssertFoundElement(page.formElement); - } - - [Test] - public void CachesIfClassMarkedCachedElement() - { - Expect.Once.On(mockDriver).Method("FindElement").With(By.Name("someForm")).Will(Return.Value(mockElement)); - Expect.Exactly(2).On(mockElement).GetProperty("TagName").Will(Return.Value("form")); - - var page = new CachedClassPage(); - - AssertFindsElement(page, () => page.formElement); - AssertFoundElement(page.formElement); - } - - [Test] - public void UsingCustomBy() - { - Expect.Exactly(1).On(mockDriver).Method("FindElement").With(new CustomBy("customCriteria")).Will(Return.Value(mockElement)); - Expect.Exactly(1).On(mockElement).GetProperty("TagName").Will(Return.Value("form")); - - var page = new CustomByPage(); - - AssertFindsElement(page, () => page.customFoundElement); - } - - [Test] - public void UsingCustomByNotFound() - { - Expect.Once.On(mockDriver).Method("FindElement").With(new CustomBy("customCriteriaNotFound")).Will(Throw.Exception(new NoSuchElementException())); - - var page = new CustomByNotFoundPage(); - PageFactory.InitElements(mockDriver, page); - Assert.Throws(typeof(NoSuchElementException), page.customFoundElement.Clear); - } - - [Test] - [ExpectedException(typeof(ArgumentException), ExpectedMessage = "descendent of", MatchType = MessageMatch.Contains)] - public void UsingCustomByWithInvalidSuperClass() - { - var page = new InvalidCustomFinderTypePage(); - PageFactory.InitElements(mockDriver, page); - } - - [Test] - [ExpectedException(typeof(ArgumentException), ExpectedMessage = "How.Custom", MatchType = MessageMatch.Contains)] - public void UsingCustomByWithNoClass() - { - var page = new NoCustomFinderClassPage(); - PageFactory.InitElements(mockDriver, page); - } - - [Test] - [ExpectedException(typeof(ArgumentException), ExpectedMessage = "constructor", MatchType = MessageMatch.Contains)] - public void UsingCustomByWithInvalidCtor() - { - var page = new InvalidCtorCustomByPage(); - PageFactory.InitElements(mockDriver, page); - } - - [Test] - [ExpectedException(typeof(ArgumentException), ExpectedMessage = "is not IWebElement or IList", MatchType = MessageMatch.Contains)] - public void ThrowsIfElementTypeIsInvalid() - { - var page = new InvalidElementTypePage(); - PageFactory.InitElements(mockDriver, page); - } - - [Test] - [ExpectedException(typeof(ArgumentException), ExpectedMessage = "is not IWebElement or IList", MatchType = MessageMatch.Contains)] - public void ThrowsIfElementCollectionTypeIsInvalid() - { - var page = new InvalidCollectionTypePage(); - PageFactory.InitElements(mockDriver, page); - } - - [Test] - [ExpectedException(typeof(ArgumentException), ExpectedMessage = "is not IWebElement or IList", MatchType = MessageMatch.Contains)] - public void ThrowsIfConcreteCollectionTypeIsUsed() - { - var page = new ConcreteCollectionTypePage(); - PageFactory.InitElements(mockDriver, page); - } - - #region Test helper methods - - private void ExpectOneLookup() - { - Expect.Exactly(1).On(mockDriver).Method("FindElement").With(By.Name("someForm")).Will(Return.Value(mockElement)); - Expect.Exactly(1).On(mockElement).GetProperty("TagName").Will(Return.Value("form")); - } - - /// - /// Asserts that getElement yields an element from page which can be interacted with, by exactly one element lookup - /// - private void AssertFindsElementByExactlyOneLookup(object page, Func getElement) - { - ExpectOneLookup(); - AssertFindsElement(page, getElement); - } - - /// - /// Asserts that getElement yields an element which can be interacted with, with no constraints on element lookups - /// - private void AssertFindsElement(object page, Func getElement) - { - PageFactory.InitElements(mockDriver, page); - - AssertFoundElement(getElement()); - } - - /// - /// Asserts that the element has been found and can be interacted with - /// - private static void AssertFoundElement(IWebElement element) - { - AssertFoundElement(element, "form"); - } - - /// - /// Asserts that the element has been found and has the tagName passed - /// - private static void AssertFoundElement(IWebElement element, string tagName) - { - Assert.AreEqual(tagName, element.TagName.ToLower()); - } - - #endregion - - #region Page classes for tests - #pragma warning disable 649 //We set fields through reflection, so expect an always-null warning - - internal class Page - { - [FindsBy(How = How.Name, Using = "someForm")] - public IWebElement formElement; - } - - internal class PageWithNameWithoutUsing - { - [FindsBy(How = How.Name)] - public IWebElement someForm; - } - - internal class PageWithIdWithoutUsing - { - [FindsBy(How = How.Id)] - public IWebElement someForm; - } - - private class PrivatePage - { - [FindsBy(How = How.Name, Using = "someForm")] - private IWebElement formElement; - - public IWebElement GetField() - { - return formElement; - } - } - - private class ChildPage : Page - { - [FindsBy(How = How.TagName, Using = "body")] - public IWebElement childElement; - } - - private class SubClassToPrivatePage : PrivatePage - { - - } - - private class AbstractParent - { - [FindsBy(How = How.Name, Using = "someForm")] - public virtual IWebElement element { get; set; } - } - - private class AbstractChild : AbstractParent - { - public override IWebElement element { get; set; } - } - - private class FallsbackPage - { - [FindsBy(How = How.Name, Using = "notthisname", Priority = 0)] - [FindsBy(How = How.TagName, Using = "form", Priority = 1)] - [FindsBy(How = How.Id, Using = "notthiseither", Priority = 2)] - public IWebElement formElement; - } - - private class FailsToFallbackPage - { - [FindsBy(How = How.Name, Using = "notthisname", Priority = 0)] - [FindsBy(How = How.TagName, Using = "notthiseither", Priority = 1)] - [FindsBy(How = How.Id, Using = "stillnotthis", Priority = 2)] - public IWebElement formElement; - } - - private class ElementAsPropertyPage - { - [FindsBy(How = How.Name, Using = "someForm")] - public IWebElement FormElement { get; set; } - } - - private class CachedPage - { - [FindsBy(How = How.Name, Using = "someForm")] - [CacheLookup] - public IWebElement formElement; - } - - [CacheLookup] - private class CachedClassPage - { - [FindsBy(How = How.Name, Using = "someForm")] - public IWebElement formElement; - } - - private class CustomBy : By - { - Mockery mocks = new Mockery(); - private string criteria; - - public CustomBy(string customByString) - { - criteria = customByString; - this.FindElementMethod = (context) => - { - if (this.criteria != "customCriteria") - { - throw new NoSuchElementException(); - } - - IWebElement mockElement = mocks.NewMock(); - return mockElement; - }; - } - } - - private class CustomByNoCtor : By - { - Mockery mocks = new Mockery(); - private string criteria; - - public CustomByNoCtor() - { - criteria = "customCriteria"; - this.FindElementMethod = (context) => - { - if (this.criteria != "customCriteria") - { - throw new NoSuchElementException(); - } - - IWebElement mockElement = mocks.NewMock(); - return mockElement; - }; - } - } - - private class CustomByPage - { - [FindsBy(How = How.Custom, Using = "customCriteria", CustomFinderType = typeof(CustomBy))] - public IWebElement customFoundElement; - } - - private class CustomByNotFoundPage - { - [FindsBy(How = How.Custom, Using = "customCriteriaNotFound", CustomFinderType = typeof(CustomBy))] - public IWebElement customFoundElement; - } - - private class NoCustomFinderClassPage - { - [FindsBy(How = How.Custom, Using = "custom")] - public IWebElement customFoundElement; - } - - private class InvalidCustomFinderTypePage - { - [FindsBy(How = How.Custom, Using = "custom", CustomFinderType = typeof(string))] - public IWebElement customFoundElement; - } - - private class InvalidCtorCustomByPage - { - [FindsBy(How = How.Custom, Using = "custom", CustomFinderType = typeof(CustomByNoCtor))] - public IWebElement customFoundElement; - } - - private class GenericFactoryPage - { - private IWebDriver driver; - public GenericFactoryPage(IWebDriver driver) - { - this.driver = driver; - } - - [FindsBy(How = How.Name, Using = "someForm")] - public IWebElement formElement; - } - - private class InvalidElementTypePage - { - [FindsBy(How = How.Name, Using = "someForm")] - public string myElement; - } - - private class InvalidCollectionTypePage - { - [FindsBy(How = How.Name, Using = "someForm")] - public List myElement; - } - - private class ConcreteCollectionTypePage - { - [FindsBy(How = How.Name, Using = "someForm")] - public ReadOnlyCollection myElement; - } - - #pragma warning restore 649 - #endregion - } -} \ No newline at end of file diff --git a/dotnet/test/support/PageObjects/ThePropertySettingByDefaultFieldDecoratorTest.cs b/dotnet/test/support/PageObjects/ThePropertySettingByDefaultFieldDecoratorTest.cs new file mode 100644 index 0000000000000..a105595d30a78 --- /dev/null +++ b/dotnet/test/support/PageObjects/ThePropertySettingByDefaultFieldDecoratorTest.cs @@ -0,0 +1,213 @@ +using NMock2; +using NUnit.Framework; +using OpenQA.Selenium.Internal; +using System; +using System.Collections.Generic; +using System.Linq; +using System.Text; + +namespace OpenQA.Selenium.Support.PageObjects +{ + [TestFixture] + public class ThePropertySettingByDefaultFieldDecoratorTest + { + private static Mockery mocks = new Mockery(); + private ISearchContext mockDriver = mocks.NewMock(); + + private object mockElement1; + private object mockElement2; + private object mockElement3; + + private object mockElements1; + private object mockElements2; + private object mockElements3; + + private object unmarkedField2; + private object unmarkedField1; + private object unmarkedField3; + + private object unmarkedField5; + private object unmarkedField4; + private object unmarkedField6; + + [FindsBy(How = How.Id, Using = "SomeId")] + private IWebElement mockProperty1 + { + set + { + mockElement1 = value; + } + } + + [FindsBy(How = How.Id, Using = "SomeId")] + private IWebDriver mockProperty2 + { + set + { + mockElement2 = value; + } + } + + [FindsBy(How = How.Id, Using = "SomeId")] + private IFindsById mockProperty3 + { + set + { + mockElement3 = value; + } + } + + [FindsBy(How = How.Id, Using = "SomeId")] + private IList mockListProperty1 + { + set + { + mockElements1 = value; + } + } + + [FindsBy(How = How.Id, Using = "SomeId")] + private IList mockListProperty2 + { + set + { + mockElements2 = value; + } + } + + [FindsBy(How = How.Id, Using = "SomeId")] + private IList mockListProperty3 + { + set + { + mockElements3 = value; + } + } + + private IWebElement mockUnmarkedProperty1 + { + set + { + unmarkedField1 = value; + } + } + + private IWebDriver mockUnmarkedProperty2 + { + set + { + unmarkedField2 = value; + } + } + + private IFindsById mockUnmarkedProperty3 + { + set + { + unmarkedField3 = value; + } + } + + private IList mockUnmarkedListProperty1 + { + set + { + unmarkedField6 = value; + } + } + + private IList mockUnmarkedListProperty2 + { + set + { + unmarkedField5 = value; + } + } + + private IList mockUnmarkedListProperty3 + { + set + { + unmarkedField4 = value; + } + } + + [SetUp] + public void SetUp() + { + PageFactory.InitElements(mockDriver, this); + } + + [Test] + public void IsPopulatedMarkedWebElementField() + { + Assert.NotNull(mockElement1); + } + + [Test] + public void IsNotPopulatedMarkedNotWebElementField() + { + Assert.IsNull(mockElement2); + } + + [Test] + public void IsPopulatedMarkedFieldWithInterfaceImplementedByRemoteWebElementent() + { + Assert.NotNull(mockElement3); + } + + [Test] + public void IsPopulatedMarkedListOfWebElementField() + { + Assert.NotNull(mockElements1); + } + + [Test] + public void IsNotPopulatedMarkedNotWebElementListField() + { + Assert.IsNull(mockElements2); + } + + [Test] + public void IsPopulatedMarkedListFieldWithInterfaceImplementedByRemoteWebElementent() + { + Assert.NotNull(mockElements3); + } + + [Test(Description = "At this case ByIdOrName locator strategy should be used")] + public void IsPopulatedWebElementFieldWithoutAttributes() + { + Assert.NotNull(unmarkedField1); + } + + [Test] + public void IsNotPopulatedNotWebElementFieldWithoutAttributes() + { + Assert.IsNull(unmarkedField2); + } + + [Test(Description = "At this case ByIdOrName locator strategy should be used")] + public void IsPopulatedFieldWithInterfaceImplementedByRemoteWebElemententWithoutAttributes() + { + Assert.NotNull(unmarkedField3); + } + + [Test(Description = "At this case ByIdOrName locator strategy should be used")] + public void IsPopulatedListOfWebElementFieldWithoutAttributes() + { + Assert.NotNull(unmarkedField6); + } + + [Test] + public void IsNotPopulatedNotWebElementListFieldWithoutAttributes() + { + Assert.IsNull(unmarkedField5); + } + + [Test(Description = "At this case ByIdOrName locator strategy should be used")] + public void IsPopulatedListFieldWithInterfaceImplementedByRemoteWebElemententWithoutAttributes() + { + Assert.NotNull(unmarkedField4); + } + } +} diff --git a/dotnet/test/support/PageObjects/TimeOutOfTheWaitingChangeTest.cs b/dotnet/test/support/PageObjects/TimeOutOfTheWaitingChangeTest.cs new file mode 100644 index 0000000000000..d49a8eafd07cf --- /dev/null +++ b/dotnet/test/support/PageObjects/TimeOutOfTheWaitingChangeTest.cs @@ -0,0 +1,68 @@ +using NUnit.Framework; +using OpenQA.Selenium.Environment; +using System; +using System.Collections.Generic; +using System.Linq; +using System.Text; + +namespace OpenQA.Selenium.Support.PageObjects +{ + [TestFixture] + public class TimeOutOfTheWaitingChangeTest : DriverTestFixture + { + [FindsBy(How = How.Id, Using = "FakeID", Priority = 1)] + private IList FakeElements; + private readonly double acceptableDeltaInMillis = 800; + + [TestFixtureSetUp] + public void RunBeforeAnyTest() + { + EnvironmentManager.Instance.WebServer.Start(); + } + + [TestFixtureTearDown] + public void RunAfterAnyTests() + { + EnvironmentManager.Instance.CloseCurrentDriver(); + EnvironmentManager.Instance.WebServer.Stop(); + } + + private double ConvertToMilliseconds(DateTime dateTime1, DateTime dateTime2) + { + return dateTime1.Subtract( + dateTime2 + ).TotalMilliseconds; + } + + [Test] + public void TestOfTheTimeOutChanging() + { + DefaultLocatorFactory factory = new DefaultLocatorFactory(driver, TimeSpan.FromSeconds(5), TimeSpan.FromMilliseconds(50)); + + PageFactory.InitElements(this, factory); + DateTime startTime = DateTime.Now; + int count = FakeElements.Count; + DateTime endTime = DateTime.Now; + + Assert.IsTrue(Math.Abs((ConvertToMilliseconds(endTime, startTime)) - TimeSpan.FromSeconds(5).TotalMilliseconds) + <= acceptableDeltaInMillis); + + factory.WaitingTimeSpan = TimeSpan.FromMilliseconds(1800); + startTime = DateTime.Now; + count = FakeElements.Count; + endTime = DateTime.Now; + + Assert.IsTrue(Math.Abs((ConvertToMilliseconds(endTime, startTime)) - TimeSpan.FromMilliseconds(1800).TotalMilliseconds) + <= acceptableDeltaInMillis); + + startTime = DateTime.Now; + driver.FindElements(By.Id("FakeID")); + endTime = DateTime.Now; + + Assert.IsTrue(Math.Abs((ConvertToMilliseconds(endTime, startTime)) - TimeSpan.FromMilliseconds(1800).TotalMilliseconds) + <= acceptableDeltaInMillis); + } + + + } +} diff --git a/dotnet/test/support/WebDriver.Support.Tests.csproj b/dotnet/test/support/WebDriver.Support.Tests.csproj index 1a52f7af7cb28..59989c2f3e50e 100644 --- a/dotnet/test/support/WebDriver.Support.Tests.csproj +++ b/dotnet/test/support/WebDriver.Support.Tests.csproj @@ -80,9 +80,12 @@ + + - + +