From 1b3579e4e0e5c0cd34d8adc52cc1718f5dc3510f Mon Sep 17 00:00:00 2001 From: TikhomirovSergey Date: Sun, 21 Dec 2014 19:57:42 +0300 Subject: [PATCH 1/8] IContextAware interface. Appium project needs this. If the similar interface exists at selenium-java so why it can't be at C#/.Net client. :) --- dotnet/src/webdriver/IContextAware.cs | 30 +++++++++++++++++++++++++++ dotnet/src/webdriver/WebDriver.csproj | 1 + 2 files changed, 31 insertions(+) create mode 100644 dotnet/src/webdriver/IContextAware.cs diff --git a/dotnet/src/webdriver/IContextAware.cs b/dotnet/src/webdriver/IContextAware.cs new file mode 100644 index 0000000000000..fd6ca592e4441 --- /dev/null +++ b/dotnet/src/webdriver/IContextAware.cs @@ -0,0 +1,30 @@ +using System; +using System.Collections.Generic; +using System.Collections.ObjectModel; +using System.Linq; +using System.Text; + +namespace OpenQA.Selenium +{ + /// + /// Some implementations of WebDriver, notably those that support native testing, need the ability + /// to switch between the native and web-based contexts. This can be achieved by using this + /// interface. + /// + interface IContextAware + { + /// + /// Switches the focus of future commands for this driver to the context with the given name + /// AND + /// returns an opaque handle to this context that uniquely identifies it within this driver + /// instance. + /// + string Context { get; set; } + + /// + /// Return a list of context handles which can be used to iterate over all contexts of this + /// WebDriver instance + /// + ReadOnlyCollection Contexts { get; } + } +} diff --git a/dotnet/src/webdriver/WebDriver.csproj b/dotnet/src/webdriver/WebDriver.csproj index 1461d59495d1a..80d8324edb554 100644 --- a/dotnet/src/webdriver/WebDriver.csproj +++ b/dotnet/src/webdriver/WebDriver.csproj @@ -88,6 +88,7 @@ + From ecc720665850748a1a0857116884fed0fe0ad6de Mon Sep 17 00:00:00 2001 From: Sergey Tikhomirov Date: Sun, 21 Dec 2014 23:51:38 +0300 Subject: [PATCH 2/8] new interface IContextAware is public --- dotnet/src/webdriver/IContextAware.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/dotnet/src/webdriver/IContextAware.cs b/dotnet/src/webdriver/IContextAware.cs index fd6ca592e4441..37964cc77eee2 100644 --- a/dotnet/src/webdriver/IContextAware.cs +++ b/dotnet/src/webdriver/IContextAware.cs @@ -11,7 +11,7 @@ namespace OpenQA.Selenium /// to switch between the native and web-based contexts. This can be achieved by using this /// interface. /// - interface IContextAware + public interface IContextAware { /// /// Switches the focus of future commands for this driver to the context with the given name From 0052e32f15be0e2714a1975e037fb592a8f1bbf2 Mon Sep 17 00:00:00 2001 From: Sergey Tikhomirov Date: Sat, 3 Jan 2015 16:52:00 +0300 Subject: [PATCH 3/8] Package configuration was added --- dotnet/src/support/WebDriver.Support.csproj | 6 ++++++ dotnet/src/support/packages.config | 4 ++++ dotnet/src/support/packages/repositories.config | 4 ++++ 3 files changed, 14 insertions(+) create mode 100644 dotnet/src/support/packages.config create mode 100644 dotnet/src/support/packages/repositories.config diff --git a/dotnet/src/support/WebDriver.Support.csproj b/dotnet/src/support/WebDriver.Support.csproj index 98bdadb6ae277..59d6c06f5a96b 100644 --- a/dotnet/src/support/WebDriver.Support.csproj +++ b/dotnet/src/support/WebDriver.Support.csproj @@ -112,6 +112,9 @@ + + packages\Castle.Core.3.3.3\lib\net40-client\Castle.Core.dll + 3.5 @@ -143,4 +146,7 @@ true + + + \ No newline at end of file diff --git a/dotnet/src/support/packages.config b/dotnet/src/support/packages.config new file mode 100644 index 0000000000000..769a089e9f6e6 --- /dev/null +++ b/dotnet/src/support/packages.config @@ -0,0 +1,4 @@ + + + + \ No newline at end of file diff --git a/dotnet/src/support/packages/repositories.config b/dotnet/src/support/packages/repositories.config new file mode 100644 index 0000000000000..0dec135fcc9e1 --- /dev/null +++ b/dotnet/src/support/packages/repositories.config @@ -0,0 +1,4 @@ + + + + \ No newline at end of file From a03c629ebb5fc6b03b1e2aaf1326987fd4b9c633 Mon Sep 17 00:00:00 2001 From: Sergey Tikhomirov Date: Sat, 3 Jan 2015 17:32:21 +0300 Subject: [PATCH 4/8] Page Factory enhancement. Test actualization. --- .gitignore | 3 +- .../PageObjects/DefaultElementLocator.cs | 252 +++++++++ .../DefaultElementLocatorFactory.cs | 93 ---- .../PageObjects/DefaultFieldDecorator.cs | 185 +++++++ .../PageObjects/DefaultLocatorFactory.cs | 81 +++ .../ElementCollectionInterceptor.cs | 23 + .../support/PageObjects/ElementInterceptor.cs | 29 ++ .../PageObjects/IElementLocatorFactory.cs | 46 -- dotnet/src/support/PageObjects/Interceptor.cs | 31 ++ .../Interfaces/IAdjustableByTimeSpan.cs | 33 ++ .../PageObjects/Interfaces/IElementLocator.cs | 23 + .../PageObjects/Interfaces/IFieldDecorator.cs | 21 + .../PageObjects/Interfaces/ILocatorFactory.cs | 23 + dotnet/src/support/PageObjects/PageFactory.cs | 150 ++---- .../RetryingElementLocatorFactory.cs | 153 ------ .../PageObjects/WebElementListProxy.cs | 206 -------- .../support/PageObjects/WebElementProxy.cs | 257 --------- dotnet/src/support/WebDriver.Support.csproj | 17 +- .../support/PageObjects/DefaultLocatorTest.cs | 95 ++++ ...ldPopulationByDefaultFieldDecoratorTest.cs | 125 +++++ .../PageObjects/PageFactoryBrowserTest.cs | 40 +- .../support/PageObjects/PageFactoryTest.cs | 493 ------------------ ...pertySettingByDefaultFieldDecoratorTest.cs | 213 ++++++++ .../TimeOutOfTheWaitingChangeTest.cs | 68 +++ .../support/WebDriver.Support.Tests.csproj | 5 +- 25 files changed, 1297 insertions(+), 1368 deletions(-) create mode 100644 dotnet/src/support/PageObjects/DefaultElementLocator.cs delete mode 100644 dotnet/src/support/PageObjects/DefaultElementLocatorFactory.cs create mode 100644 dotnet/src/support/PageObjects/DefaultFieldDecorator.cs create mode 100644 dotnet/src/support/PageObjects/DefaultLocatorFactory.cs create mode 100644 dotnet/src/support/PageObjects/ElementCollectionInterceptor.cs create mode 100644 dotnet/src/support/PageObjects/ElementInterceptor.cs delete mode 100644 dotnet/src/support/PageObjects/IElementLocatorFactory.cs create mode 100644 dotnet/src/support/PageObjects/Interceptor.cs create mode 100644 dotnet/src/support/PageObjects/Interfaces/IAdjustableByTimeSpan.cs create mode 100644 dotnet/src/support/PageObjects/Interfaces/IElementLocator.cs create mode 100644 dotnet/src/support/PageObjects/Interfaces/IFieldDecorator.cs create mode 100644 dotnet/src/support/PageObjects/Interfaces/ILocatorFactory.cs delete mode 100644 dotnet/src/support/PageObjects/RetryingElementLocatorFactory.cs delete mode 100644 dotnet/src/support/PageObjects/WebElementListProxy.cs delete mode 100644 dotnet/src/support/PageObjects/WebElementProxy.cs create mode 100644 dotnet/test/support/PageObjects/DefaultLocatorTest.cs create mode 100644 dotnet/test/support/PageObjects/FieldPopulationByDefaultFieldDecoratorTest.cs delete mode 100644 dotnet/test/support/PageObjects/PageFactoryTest.cs create mode 100644 dotnet/test/support/PageObjects/ThePropertySettingByDefaultFieldDecoratorTest.cs create mode 100644 dotnet/test/support/PageObjects/TimeOutOfTheWaitingChangeTest.cs diff --git a/.gitignore b/.gitignore index addf95d36f0ee..29c405434bcab 100644 --- a/.gitignore +++ b/.gitignore @@ -66,4 +66,5 @@ GSYMS GTAGS .project Gemfile.lock -/rb/.bundle \ No newline at end of file +/rb/.bundle +/dotnet/src/support/packages/ \ No newline at end of file diff --git a/dotnet/src/support/PageObjects/DefaultElementLocator.cs b/dotnet/src/support/PageObjects/DefaultElementLocator.cs new file mode 100644 index 0000000000000..d266fbf3c9268 --- /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 understands the + /// annotations 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 deleted file mode 100644 index 8c8770f9e8680..0000000000000 --- a/dotnet/src/support/PageObjects/DefaultElementLocatorFactory.cs +++ /dev/null @@ -1,93 +0,0 @@ -// -// Copyright 2014 Software Freedom Conservancy -// -// Licensed under the Apache License, Version 2.0 (the "License"); -// you may not use this file except in compliance with the License. -// You may obtain a copy of the License at -// -// http://www.apache.org/licenses/LICENSE-2.0 -// -// Unless required by applicable law or agreed to in writing, software -// distributed under the License is distributed on an "AS IS" BASIS, -// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -// See the License for the specific language governing permissions and -// limitations under the License. -// - -using System; -using System.Collections.Generic; -using System.Collections.ObjectModel; -using System.Linq; -using System.Text; - -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. - /// - public class DefaultElementLocatorFactory : IElementLocatorFactory - { - /// - /// Locates an element using the given and list of criteria. - /// - /// The object within which to search for an element. - /// The list of methods by which to search for the element. - /// An which is the first match under the desired criteria. - public IWebElement LocateElement(ISearchContext searchContext, IEnumerable bys) - { - if (searchContext == null) - { - throw new ArgumentNullException("searchContext", "searchContext may not be null"); - } - - if (bys == null) - { - throw new ArgumentNullException("bys", "List of criteria may not be null"); - } - - string errorString = null; - foreach (var by in bys) - { - try - { - return searchContext.FindElement(by); - } - catch (NoSuchElementException) - { - errorString = (errorString == null ? "Could not find element by: " : errorString + ", or: ") + by; - } - } - - throw new NoSuchElementException(errorString); - } - - /// - /// Locates a list of elements using the given and list of criteria. - /// - /// The object within which to search for elements. - /// The list of methods by which to search for the elements. - /// An list of all elements which match the desired criteria. - public ReadOnlyCollection LocateElements(ISearchContext searchContext, IEnumerable bys) - { - if (searchContext == null) - { - throw new ArgumentNullException("searchContext", "searchContext may not be null"); - } - - if (bys == null) - { - throw new ArgumentNullException("bys", "List of criteria may not be null"); - } - - List collection = new List(); - foreach (var by in bys) - { - ReadOnlyCollection list = searchContext.FindElements(by); - collection.AddRange(list); - } - - return collection.AsReadOnly(); - } - } -} diff --git a/dotnet/src/support/PageObjects/DefaultFieldDecorator.cs b/dotnet/src/support/PageObjects/DefaultFieldDecorator.cs new file mode 100644 index 0000000000000..a578cca906672 --- /dev/null +++ b/dotnet/src/support/PageObjects/DefaultFieldDecorator.cs @@ -0,0 +1,185 @@ +using Castle.DynamicProxy; +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; + +namespace OpenQA.Selenium.Support.PageObjects +{ + /// + /// Default decorator for use with . Will decorate 1) all of the + /// fields and 2) ReadOnlyCollection of fields 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 IWebElement implementor + /// - The declared type of field or property is IList or ReadOnlyCollection. The declared + /// generic parameter type should be IWebElement or any IWebElement implementor. + /// + /// + /// + public object Decorate(MemberInfo member) + { + return CreateProxy(member); + } + + /// + /// This method creates proxies in order to populate + /// fields 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); + ProxyGenerator proxyGenerator = new ProxyGenerator(); + + if (InterfacesThatCanBeProxiedAsWebElement.Contains(targetType)) + { + return proxyGenerator.CreateInterfaceProxyWithoutTarget(typeof(IWebElement), + InterfacesThatCanBeProxiedAsWebElement.ToArray(), + new ElementInterceptor(locator)); + } + + foreach (var type in InterfacesThatCanBeProxiedAsWebElement) + { + Type listType = typeof(IList<>).MakeGenericType(type); + if (listType.Equals(targetType)) + { + return proxyGenerator.CreateInterfaceProxyWithoutTarget(targetType, + new ElementCollectionInterceptor(locator)); + } + } + 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); + } + } +} 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/ElementCollectionInterceptor.cs b/dotnet/src/support/PageObjects/ElementCollectionInterceptor.cs new file mode 100644 index 0000000000000..057ea3daf301e --- /dev/null +++ b/dotnet/src/support/PageObjects/ElementCollectionInterceptor.cs @@ -0,0 +1,23 @@ +using Castle.DynamicProxy; +using OpenQA.Selenium.Support.PageObjects.Interfaces; +using System; +using System.Reflection; + +namespace OpenQA.Selenium.Support.PageObjects +{ + /// + /// Intercepts the request to a collection of 's + /// + sealed class ElementCollectionInterceptor: Interceptor, IInterceptor + { + public ElementCollectionInterceptor(IElementLocator elementLocator) + :base(elementLocator) + {} + + public void Intercept(IInvocation invocation) + { + var elements = ElementLocator.Elements; + invocation.ReturnValue = base.Execute(invocation, elements); + } + } +} diff --git a/dotnet/src/support/PageObjects/ElementInterceptor.cs b/dotnet/src/support/PageObjects/ElementInterceptor.cs new file mode 100644 index 0000000000000..355ddf3795095 --- /dev/null +++ b/dotnet/src/support/PageObjects/ElementInterceptor.cs @@ -0,0 +1,29 @@ +using OpenQA.Selenium.Support.PageObjects.Interfaces; +using System; +using System.Reflection; +using Castle.DynamicProxy; +using OpenQA.Selenium.Internal; + +namespace OpenQA.Selenium.Support.PageObjects +{ + /// + /// Intercepts the request to a single + /// + sealed class ElementInterceptor : Interceptor, IInterceptor + { + public ElementInterceptor(IElementLocator elementLocator) + :base(elementLocator) + {} + + public void Intercept(IInvocation invocation) + { + var element = ElementLocator.Element; + if (typeof(IWrapsElement).IsAssignableFrom(invocation.Method.DeclaringType)) + { + invocation.ReturnValue = element; + return; + } + invocation.ReturnValue = base.Execute(invocation, element); + } + } +} diff --git a/dotnet/src/support/PageObjects/IElementLocatorFactory.cs b/dotnet/src/support/PageObjects/IElementLocatorFactory.cs deleted file mode 100644 index 8f9a5ea5cc2a7..0000000000000 --- a/dotnet/src/support/PageObjects/IElementLocatorFactory.cs +++ /dev/null @@ -1,46 +0,0 @@ -// -// Copyright 2014 Software Freedom Conservancy -// -// Licensed under the Apache License, Version 2.0 (the "License"); -// you may not use this file except in compliance with the License. -// You may obtain a copy of the License at -// -// http://www.apache.org/licenses/LICENSE-2.0 -// -// Unless required by applicable law or agreed to in writing, software -// distributed under the License is distributed on an "AS IS" BASIS, -// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -// See the License for the specific language governing permissions and -// limitations under the License. -// - -using System; -using System.Collections.Generic; -using System.Collections.ObjectModel; -using System.Linq; -using System.Text; - -namespace OpenQA.Selenium.Support.PageObjects -{ - /// - /// Interface describing how elements are to be located by a - /// - public interface IElementLocatorFactory - { - /// - /// Locates an element using the given and list of criteria. - /// - /// The object within which to search for an element. - /// The list of methods by which to search for the element. - /// An which is the first match under the desired criteria. - IWebElement LocateElement(ISearchContext searchContext, IEnumerable bys); - - /// - /// Locates a list of elements using the given and list of criteria. - /// - /// The object within which to search for elements. - /// The list of methods by which to search for the elements. - /// An list of all elements which match the desired criteria. - ReadOnlyCollection LocateElements(ISearchContext searchContext, IEnumerable bys); - } -} diff --git a/dotnet/src/support/PageObjects/Interceptor.cs b/dotnet/src/support/PageObjects/Interceptor.cs new file mode 100644 index 0000000000000..751685dec0825 --- /dev/null +++ b/dotnet/src/support/PageObjects/Interceptor.cs @@ -0,0 +1,31 @@ +using Castle.DynamicProxy; +using OpenQA.Selenium.Support.PageObjects.Interfaces; +using System; +using System.Collections.Generic; +using System.Reflection; + +namespace OpenQA.Selenium.Support.PageObjects +{ + abstract class Interceptor + { + protected readonly IElementLocator ElementLocator; + + public Interceptor(IElementLocator elementLocator) + { + this.ElementLocator = elementLocator; + } + + /// + /// This is the common behavior of intercetor. + /// The method will be used or overridden by subclasses + /// + /// This is container of method invocation parameters + /// This the real proxied object + /// The values which is returned as a result of proxied method. + protected object Execute(IInvocation invocation, object proxied) + { + MethodInfo proxiedMethod = invocation.Method; + return proxiedMethod.Invoke(proxied, invocation.Arguments); + } + } +} diff --git a/dotnet/src/support/PageObjects/Interfaces/IAdjustableByTimeSpan.cs b/dotnet/src/support/PageObjects/Interfaces/IAdjustableByTimeSpan.cs new file mode 100644 index 0000000000000..d967a96e43990 --- /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 time aou for the waiting. + /// + TimeSpan WaitingTimeSpan + { + set; + get; + } + + /// + /// This property should get or set a time aou 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..96933aeee4d88 --- /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..1c9255e3fd1c3 --- /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 to + 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..c9a9a5310cf6a --- /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 IElementLocator instance will be + /// returned per call. + /// + public interface ILocatorFactory + { + /// + /// When a field on a class needs to be decorated with an {@link ElementLocator} 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..101126ddf182b 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,36 +87,31 @@ 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. /// /// thrown if a field or property decorated with the is not of type /// or IList{IWebElement}. /// - public static void InitElements(ISearchContext driver, object page) + public static void InitElements(ISearchContext searchContext, object page) { - InitElements(driver, page, new DefaultElementLocatorFactory()); + InitElements(page, new DefaultLocatorFactory(searchContext)); } /// /// Initializes the elements in the Page Object. /// - /// The driver used to find elements on the page. /// The Page Object to be populated with elements. - /// The implementation that + /// The implementation that /// determines how elements are located. - /// - /// thrown if a field or property decorated with the is not of type - /// or IList{IWebElement}. - /// - public static void InitElements(ISearchContext driver, object page, IElementLocatorFactory locatorFactory) + public static void InitElements(object page, ILocatorFactory locatorFactory) { if (page == null) { @@ -133,109 +123,61 @@ public static void InitElements(ISearchContext driver, object page, IElementLoca throw new ArgumentNullException("locatorFactory", "locatorFactory cannot be null"); } - // Get a list of all of the fields and properties (public and non-public [private, protected, etc.]) - // in the passed-in page object. Note that we walk the inheritance tree to get superclass members. - var type = page.GetType(); + 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) { - const BindingFlags NonPublicBindingOptions = BindingFlags.Instance | BindingFlags.NonPublic; members.AddRange(type.GetFields(NonPublicBindingOptions)); members.AddRange(type.GetProperties(NonPublicBindingOptions)); type = type.BaseType; } - // Examine each member, and if it is both marked with an appropriate attribute, and of - // the proper type, set the member's value to the appropriate type of proxy object. - foreach (var member in members) - { - List bys = CreateLocatorList(member); - if (bys.Count > 0) - { - bool cache = ShouldCacheLookup(member); - - object proxyObject = null; - var field = member as FieldInfo; - var property = member as PropertyInfo; - if (field != null) - { - proxyObject = CreateProxyObject(field.FieldType, driver, bys, cache, locatorFactory); - if (proxyObject == null) - { - throw new ArgumentException("Type of field '" + field.Name + "' is not IWebElement or IList"); - } - - field.SetValue(page, proxyObject); - } - else if (property != null) - { - proxyObject = CreateProxyObject(property.PropertyType, driver, bys, cache, locatorFactory); - if (proxyObject == null) - { - throw new ArgumentException("Type of property '" + property.Name + "' is not IWebElement or IList"); - } - - property.SetValue(page, proxyObject, null); - } - } - } - } + foreach (var member in members){ + var field = member as FieldInfo; + var property = member as PropertyInfo; - private static List CreateLocatorList(MemberInfo member) - { - var useSequenceAttributes = Attribute.GetCustomAttributes(member, typeof(FindsBySequenceAttribute), true); - bool useSequence = useSequenceAttributes.Length > 0; + var value = decorator.Decorate(member); - 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; - } + if (value == null){ + continue; + } - bys.Add(castedAttribute.Finder); + if (field != null){ + field.SetValue(page, value); } - if (useSequence) + if (property != null) { - ByChained chained = new ByChained(bys.ToArray()); - bys.Clear(); - bys.Add(chained); + property.SetValue(page, value, null); } } - - return bys; - } - - private static bool ShouldCacheLookup(MemberInfo member) - { - var cacheAttributeType = typeof(CacheLookupAttribute); - bool cache = member.GetCustomAttributes(cacheAttributeType, true).Length != 0 || member.DeclaringType.GetCustomAttributes(cacheAttributeType, true).Length != 0; - return cache; - } - - private static object CreateProxyObject(Type memberType, ISearchContext driver, List bys, bool cache, IElementLocatorFactory locatorFactory) - { - object proxyObject = null; - if (memberType == typeof(IList)) - { - proxyObject = new WebElementListProxy(driver, bys, cache, locatorFactory); - } - else if (memberType == typeof(IWebElement)) - { - proxyObject = new WebElementProxy(driver, bys, cache, locatorFactory); - } - - return proxyObject; } } } diff --git a/dotnet/src/support/PageObjects/RetryingElementLocatorFactory.cs b/dotnet/src/support/PageObjects/RetryingElementLocatorFactory.cs deleted file mode 100644 index b2597094c3741..0000000000000 --- a/dotnet/src/support/PageObjects/RetryingElementLocatorFactory.cs +++ /dev/null @@ -1,153 +0,0 @@ -// -// Copyright 2014 Software Freedom Conservancy -// -// Licensed under the Apache License, Version 2.0 (the "License"); -// you may not use this file except in compliance with the License. -// You may obtain a copy of the License at -// -// http://www.apache.org/licenses/LICENSE-2.0 -// -// Unless required by applicable law or agreed to in writing, software -// distributed under the License is distributed on an "AS IS" BASIS, -// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -// See the License for the specific language governing permissions and -// limitations under the License. -// - -using System; -using System.Collections.Generic; -using System.Collections.ObjectModel; -using System.Linq; -using System.Text; -using System.Threading; - -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. - /// - public class RetryingElementLocatorFactory : IElementLocatorFactory - { - private static readonly TimeSpan DefaultTimeout = TimeSpan.FromSeconds(5); - private static readonly TimeSpan DefaultPollingInterval = TimeSpan.FromMilliseconds(500); - - private TimeSpan timeout; - private TimeSpan pollingInterval; - - /// - /// Initializes a new instance of the class. - /// - public RetryingElementLocatorFactory() - : this(DefaultTimeout) - { - } - - /// - /// Initializes a new instance of the class. - /// - /// The indicating how long the locator should - /// retry before timing out. - public RetryingElementLocatorFactory(TimeSpan timeout) - : this(timeout, DefaultPollingInterval) - { - } - - /// - /// Initializes a new instance of the class. - /// - /// The indicating how long the locator should - /// retry before timing out. - /// The indicating how often to poll - /// for the existence of the element. - public RetryingElementLocatorFactory(TimeSpan timeout, TimeSpan pollingInterval) - { - this.timeout = timeout; - this.pollingInterval = pollingInterval; - } - - /// - /// Locates an element using the given and list of criteria. - /// - /// The object within which to search for an element. - /// The list of methods by which to search for the element. - /// An which is the first match under the desired criteria. - public IWebElement LocateElement(ISearchContext searchContext, IEnumerable bys) - { - if (searchContext == null) - { - throw new ArgumentNullException("searchContext", "searchContext may not be null"); - } - - if (bys == null) - { - throw new ArgumentNullException("bys", "List of criteria may not be null"); - } - - string errorString = null; - DateTime endTime = DateTime.Now.Add(this.timeout); - bool timeoutReached = DateTime.Now > endTime; - while (!timeoutReached) - { - foreach (var by in bys) - { - try - { - return searchContext.FindElement(by); - } - catch (NoSuchElementException) - { - errorString = (errorString == null ? "Could not find element by: " : errorString + ", or: ") + by; - } - } - - timeoutReached = DateTime.Now > endTime; - if (!timeoutReached) - { - Thread.Sleep(this.pollingInterval); - } - } - - throw new NoSuchElementException(errorString); - } - - /// - /// Locates a list of elements using the given and list of criteria. - /// - /// The object within which to search for elements. - /// The list of methods by which to search for the elements. - /// An list of all elements which match the desired criteria. - public ReadOnlyCollection LocateElements(ISearchContext searchContext, IEnumerable bys) - { - if (searchContext == null) - { - throw new ArgumentNullException("searchContext", "searchContext may not be null"); - } - - if (bys == null) - { - throw new ArgumentNullException("bys", "List of criteria may not be null"); - } - - List collection = new List(); - DateTime endTime = DateTime.Now.Add(this.timeout); - bool timeoutReached = DateTime.Now > endTime; - while (!timeoutReached) - { - foreach (var by in bys) - { - ReadOnlyCollection list = searchContext.FindElements(by); - collection.AddRange(list); - } - - timeoutReached = collection.Count == 0 && DateTime.Now > endTime; - if (!timeoutReached) - { - Thread.Sleep(this.pollingInterval); - } - } - - return collection.AsReadOnly(); - } - } -} diff --git a/dotnet/src/support/PageObjects/WebElementListProxy.cs b/dotnet/src/support/PageObjects/WebElementListProxy.cs deleted file mode 100644 index 0a29f4f592fbe..0000000000000 --- a/dotnet/src/support/PageObjects/WebElementListProxy.cs +++ /dev/null @@ -1,206 +0,0 @@ -// -// Copyright 2007-2013 WebDriver committers -// Portions copyright 2013 Software Freedom Conservancy -// -// Licensed under the Apache License, Version 2.0 (the "License"); -// you may not use this file except in compliance with the License. -// You may obtain a copy of the License at -// -// http://www.apache.org/licenses/LICENSE-2.0 -// -// Unless required by applicable law or agreed to in writing, software -// distributed under the License is distributed on an "AS IS" BASIS, -// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -// See the License for the specific language governing permissions and -// limitations under the License. -// - -using System; -using System.Collections; -using System.Collections.Generic; -using System.Collections.ObjectModel; -using System.Linq; -using System.Text; - -namespace OpenQA.Selenium.Support.PageObjects -{ - /// - /// Represents a proxy class for a list of elements to be used with the PageFactory. - /// - internal class WebElementListProxy : IList - { - private readonly IElementLocatorFactory locatorFactory; - private readonly ISearchContext searchContext; - private readonly IEnumerable bys; - private readonly bool cache; - private List collection = null; - - /// - /// Initializes a new instance of the class. - /// - /// The driver used to search for elements. - /// The list of methods by which to search for the elements. - /// to cache the lookup to the element; otherwise, . - /// The implementation that - /// determines how elements are located. - internal WebElementListProxy(ISearchContext searchContext, IEnumerable bys, bool cache, IElementLocatorFactory locatorFactory) - { - this.locatorFactory = locatorFactory; - this.searchContext = searchContext; - this.bys = bys; - this.cache = cache; - } - - /// - /// Prevents a default instance of the class from being created. - /// - private WebElementListProxy() - { - } - - /// - /// Gets the number of elements contained in the instance. - /// - public int Count - { - get { return this.ElementList.Count; } - } - - /// - /// Gets a value indicating whether this list is read only. - /// - public bool IsReadOnly - { - get { return true; } - } - - private List ElementList - { - get - { - if (!this.cache || this.collection == null) - { - this.collection = new List(); - this.collection.AddRange(this.locatorFactory.LocateElements(this.searchContext, this.bys)); - } - - return this.collection; - } - } - - /// - /// Gets or sets the element at the specified index. - /// - /// The zero-based index of the element to get or set. - /// The at the specified index. - public IWebElement this[int index] - { - get - { - return this.ElementList[index]; - } - - set - { - throw new NotImplementedException(); - } - } - - /// - /// Determines whether an element is in the . - /// - /// The object to locate in the . The value can be . - /// if the specified item is in the list; otherwise, . - public bool Contains(IWebElement item) - { - return this.ElementList.Contains(item); - } - - /// - /// Copies the elements of the to an Array, starting at a particular Array index. - /// - /// The one-dimensional Array that is the destination of the elements copied from . - /// The Array must have zero-based indexing. - /// The zero-based index in array at which copying begins. - public void CopyTo(IWebElement[] array, int arrayIndex) - { - this.ElementList.CopyTo(array, arrayIndex); - } - - /// - /// Determines the index of a specific item in the . - /// - /// The object to locate in the . - /// The index of if found in the list; otherwise, -1. - public int IndexOf(IWebElement item) - { - return this.ElementList.IndexOf(item); - } - - /// - /// Adds an item to the . - /// - /// The to add. - public void Add(IWebElement item) - { - throw new NotImplementedException(); - } - - /// - /// Removes all items from the . - /// - public void Clear() - { - throw new NotImplementedException(); - } - - /// - /// Inserts an item to the at the specified index. - /// - /// The zero-based index at which item should be inserted. - /// The object to insert into the . - public void Insert(int index, IWebElement item) - { - throw new NotImplementedException(); - } - - /// - /// Removes the item at the specified index. - /// - /// The zero-based index of the item to remove. - public void RemoveAt(int index) - { - throw new NotImplementedException(); - } - - /// - /// Removes the first occurrence of a specific object from the . - /// - /// The object to remove from the . - /// if item was successfully removed from the ; - /// otherwise, . This method also returns if item is not found - /// in the original . - public bool Remove(IWebElement item) - { - throw new NotImplementedException(); - } - - /// - /// Returns an enumerator that iterates through the collection. - /// - /// A that can be used to iterate through the collection. - IEnumerator IEnumerable.GetEnumerator() - { - return this.ElementList.GetEnumerator(); - } - - /// - /// Returns an enumerator that iterates through the collection. - /// - /// A IEnumerator{IWebElement} that can be used to iterate through the collection. - public IEnumerator GetEnumerator() - { - return this.ElementList.GetEnumerator(); - } - } -} diff --git a/dotnet/src/support/PageObjects/WebElementProxy.cs b/dotnet/src/support/PageObjects/WebElementProxy.cs deleted file mode 100644 index c0f9491a8158f..0000000000000 --- a/dotnet/src/support/PageObjects/WebElementProxy.cs +++ /dev/null @@ -1,257 +0,0 @@ -// -// Copyright 2007-2013 WebDriver committers -// Portions copyright 2013 Software Freedom Conservancy -// -// Licensed under the Apache License, Version 2.0 (the "License"); -// you may not use this file except in compliance with the License. -// You may obtain a copy of the License at -// -// http://www.apache.org/licenses/LICENSE-2.0 -// -// Unless required by applicable law or agreed to in writing, software -// distributed under the License is distributed on an "AS IS" BASIS, -// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -// See the License for the specific language governing permissions and -// limitations under the License. -// - -using System; -using System.Collections.Generic; -using System.Collections.ObjectModel; -using System.Drawing; -using System.Linq; -using System.Text; -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. - /// - internal class WebElementProxy : IWebElement, ILocatable, IWrapsElement - { - private readonly IElementLocatorFactory locatorFactory; - private readonly ISearchContext searchContext; - private readonly IEnumerable bys; - private readonly bool cache; - private IWebElement cachedElement; - - /// - /// Initializes a new instance of the class. - /// - /// The driver used to search for elements. - /// The list of methods by which to search for the element. - /// to cache the lookup to the element; otherwise, . - /// The implementation that - /// determines how elements are located. - internal WebElementProxy(ISearchContext searchContext, IEnumerable bys, bool cache, IElementLocatorFactory locatorFactory) - { - this.locatorFactory = locatorFactory; - this.searchContext = searchContext; - this.bys = bys; - this.cache = cache; - } - - /// - /// Prevents a default instance of the class from being created. - /// - private WebElementProxy() - { - } - - /// - /// Gets the tag name of this element. - /// - public string TagName - { - get { return this.WrappedElement.TagName; } - } - - /// - /// Gets the innerText of this element, without any leading or trailing whitespace, - /// and with other whitespace collapsed. - /// - public string Text - { - get { return this.WrappedElement.Text; } - } - - /// - /// Gets a value indicating whether or not this element is enabled. - /// - public bool Enabled - { - get { return this.WrappedElement.Enabled; } - } - - /// - /// Gets a value indicating whether or not this element is selected. - /// - public bool Selected - { - get { return this.WrappedElement.Selected; } - } - - /// - /// Gets a object containing the coordinates of the upper-left corner - /// of this element relative to the upper-left corner of the page. - /// - public Point Location - { - get { return this.WrappedElement.Location; } - } - - /// - /// Gets a object containing the height and width of this element. - /// - public Size Size - { - get { return this.WrappedElement.Size; } - } - - /// - /// Gets a value indicating whether or not this element is displayed. - /// - public bool Displayed - { - get { return this.WrappedElement.Displayed; } - } - - /// - /// Gets the location of an element on the screen, scrolling it into view - /// if it is not currently on the screen. - /// - public Point LocationOnScreenOnceScrolledIntoView - { - get - { - ILocatable locatable = this.WrappedElement as ILocatable; - return locatable.LocationOnScreenOnceScrolledIntoView; - } - } - - /// - /// Gets the coordinates identifying the location of this element using - /// various frames of reference. - /// - public ICoordinates Coordinates - { - get - { - ILocatable locatable = this.WrappedElement as ILocatable; - return locatable.Coordinates; - } - } - - /// - /// Gets the interface through which the user can discover if there is an underlying element to be used. - /// - public IWebElement WrappedElement - { - get - { - if (!this.cache || this.cachedElement == null) - { - this.cachedElement = this.locatorFactory.LocateElement(this.searchContext, this.bys); - } - - return this.cachedElement; - } - } - - /// - /// Clears the content of this element. - /// - public void Clear() - { - this.WrappedElement.Clear(); - } - - /// - /// Simulates typing text into the element. - /// - /// The keys to send to the element. - public void SendKeys(string text) - { - this.WrappedElement.SendKeys(text); - } - - /// - /// Submits this element to the web server. - /// - public void Submit() - { - this.WrappedElement.Submit(); - } - - /// - /// Clicks this element. - /// - public void Click() - { - this.WrappedElement.Click(); - } - - /// - /// Gets the value of the specified attribute for this element. - /// - /// The attribute name to retrieve the value of. - /// The value of the attribute. Returns if the attribute does not exist. - public string GetAttribute(string attributeName) - { - return this.WrappedElement.GetAttribute(attributeName); - } - - /// - /// Gets the value of a CSS property of this element. - /// - /// The property name to retrieve the value of. - /// The value of the CSS property. - public string GetCssValue(string propertyName) - { - return this.WrappedElement.GetCssValue(propertyName); - } - - /// - /// Finds the first using the given method. - /// - /// The locating mechanism to use. - /// The first matching on the current context. - public IWebElement FindElement(By by) - { - return this.WrappedElement.FindElement(by); - } - - /// - /// Finds all IWebElements within the current context - /// using the given mechanism. - /// - /// The locating mechanism to use. - /// A of all WebElements - /// matching the current criteria, or an empty list if nothing matches. - public ReadOnlyCollection FindElements(By by) - { - return this.WrappedElement.FindElements(by); - } - - /// - /// Determines whether the specified object is equal to the current object. - /// - /// The object to compare with the current object. - /// if the specified object is equal to the current object; otherwise, . - public override bool Equals(object obj) - { - return this.WrappedElement.Equals(obj); - } - - /// - /// Serves as a hash function for a particular type. - /// - /// A hash code for the current object. - public override int GetHashCode() - { - return this.WrappedElement.GetHashCode(); - } - } -} diff --git a/dotnet/src/support/WebDriver.Support.csproj b/dotnet/src/support/WebDriver.Support.csproj index 59d6c06f5a96b..6aba6d0d7f508 100644 --- a/dotnet/src/support/WebDriver.Support.csproj +++ b/dotnet/src/support/WebDriver.Support.csproj @@ -79,17 +79,22 @@ - - - + + + + + + - + + + + + - - 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 @@ + + - + + From 4f6568b8ba6a018694fc1565b4fed063ef8d1cf8 Mon Sep 17 00:00:00 2001 From: Sergey Tikhomirov Date: Sat, 3 Jan 2015 17:48:06 +0300 Subject: [PATCH 5/8] The improving of summaries --- .../src/support/PageObjects/DefaultElementLocator.cs | 4 ++-- .../src/support/PageObjects/DefaultFieldDecorator.cs | 10 +++++----- .../PageObjects/Interfaces/IAdjustableByTimeSpan.cs | 4 ++-- .../support/PageObjects/Interfaces/IElementLocator.cs | 2 +- .../support/PageObjects/Interfaces/IFieldDecorator.cs | 2 +- .../support/PageObjects/Interfaces/ILocatorFactory.cs | 4 ++-- dotnet/src/support/PageObjects/PageFactory.cs | 4 ---- 7 files changed, 13 insertions(+), 17 deletions(-) diff --git a/dotnet/src/support/PageObjects/DefaultElementLocator.cs b/dotnet/src/support/PageObjects/DefaultElementLocator.cs index d266fbf3c9268..6aba1e6a78d55 100644 --- a/dotnet/src/support/PageObjects/DefaultElementLocator.cs +++ b/dotnet/src/support/PageObjects/DefaultElementLocator.cs @@ -12,8 +12,8 @@ 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 understands the - /// annotations and . + /// designed for use with the and uses + /// attributes and . /// public class DefaultElementLocator: IElementLocator, IWrapsDriver { diff --git a/dotnet/src/support/PageObjects/DefaultFieldDecorator.cs b/dotnet/src/support/PageObjects/DefaultFieldDecorator.cs index a578cca906672..2aee2c1b2ad4f 100644 --- a/dotnet/src/support/PageObjects/DefaultFieldDecorator.cs +++ b/dotnet/src/support/PageObjects/DefaultFieldDecorator.cs @@ -11,7 +11,7 @@ namespace OpenQA.Selenium.Support.PageObjects { /// /// Default decorator for use with . Will decorate 1) all of the - /// fields and 2) ReadOnlyCollection of fields that have + /// fields/properties and 2) IList of fields/properties that have /// , , /// attributes with a proxy that locates the elements using the passed /// in . @@ -61,9 +61,9 @@ public void AddInterfacesToByProxied(List interfacesToByProxied) /// /// This decorator returns values if : - /// - The declared type of field or property is IWebElement or any IWebElement implementor - /// - The declared type of field or property is IList or ReadOnlyCollection. The declared - /// generic parameter type should be IWebElement or any IWebElement implementor. + /// - 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 . /// /// /// @@ -74,7 +74,7 @@ public object Decorate(MemberInfo member) /// /// This method creates proxies in order to populate - /// fields of page object. It can be overridden when it is needed. + /// fields/set properties of page object. It can be overridden when it is needed. /// /// /// diff --git a/dotnet/src/support/PageObjects/Interfaces/IAdjustableByTimeSpan.cs b/dotnet/src/support/PageObjects/Interfaces/IAdjustableByTimeSpan.cs index d967a96e43990..96ad414b90c42 100644 --- a/dotnet/src/support/PageObjects/Interfaces/IAdjustableByTimeSpan.cs +++ b/dotnet/src/support/PageObjects/Interfaces/IAdjustableByTimeSpan.cs @@ -13,7 +13,7 @@ namespace OpenQA.Selenium.Support.PageObjects.Interfaces public interface IAdjustableByTimeSpan { /// - /// This property should get or set a time aou for the waiting. + /// This property should get or set a timeout for the waiting. /// TimeSpan WaitingTimeSpan { @@ -22,7 +22,7 @@ TimeSpan WaitingTimeSpan } /// - /// This property should get or set a time aou for the sleeping. + /// This property should get or set a timeout for the sleeping. /// TimeSpan TimeForSleeping { diff --git a/dotnet/src/support/PageObjects/Interfaces/IElementLocator.cs b/dotnet/src/support/PageObjects/Interfaces/IElementLocator.cs index 96933aeee4d88..aa2d02499e59f 100644 --- a/dotnet/src/support/PageObjects/Interfaces/IElementLocator.cs +++ b/dotnet/src/support/PageObjects/Interfaces/IElementLocator.cs @@ -16,7 +16,7 @@ public interface IElementLocator /// IWebElement Element { get; } /// - /// This property should return a read only collection of IWebElement instances + /// 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 index 1c9255e3fd1c3..8c018bfc628ba 100644 --- a/dotnet/src/support/PageObjects/Interfaces/IFieldDecorator.cs +++ b/dotnet/src/support/PageObjects/Interfaces/IFieldDecorator.cs @@ -15,7 +15,7 @@ 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 to + /// 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 index c9a9a5310cf6a..2278c37144992 100644 --- a/dotnet/src/support/PageObjects/Interfaces/ILocatorFactory.cs +++ b/dotnet/src/support/PageObjects/Interfaces/ILocatorFactory.cs @@ -7,13 +7,13 @@ namespace OpenQA.Selenium.Support.PageObjects.Interfaces { /// - /// A factory for producing s. It is expected that a new IElementLocator instance will be + /// A factory for producing s. It is expected that a new instance will be /// returned per call. /// public interface ILocatorFactory { /// - /// When a field on a class needs to be decorated with an {@link ElementLocator} this method will + /// 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 diff --git a/dotnet/src/support/PageObjects/PageFactory.cs b/dotnet/src/support/PageObjects/PageFactory.cs index 101126ddf182b..e95950872669e 100644 --- a/dotnet/src/support/PageObjects/PageFactory.cs +++ b/dotnet/src/support/PageObjects/PageFactory.cs @@ -96,10 +96,6 @@ public static T InitElements(IWebDriver driver, ILocatorFactory locatorFactor /// /// The IWebDriver or IWebElement implementation used to find elements on the page. /// The Page Object to be populated with elements. - /// - /// thrown if a field or property decorated with the is not of type - /// or IList{IWebElement}. - /// public static void InitElements(ISearchContext searchContext, object page) { InitElements(page, new DefaultLocatorFactory(searchContext)); From 3a95ba945a4b7c25783deba274bf6cdbb4054d6d Mon Sep 17 00:00:00 2001 From: Sergey Tikhomirov Date: Sat, 3 Jan 2015 18:03:28 +0300 Subject: [PATCH 6/8] The reference to the Castle.Core was removed as it doesn't support .Net Framework 3.5 --- dotnet/src/support/WebDriver.Support.csproj | 6 ------ dotnet/src/support/packages.config | 4 ---- dotnet/src/support/packages/repositories.config | 4 ---- 3 files changed, 14 deletions(-) delete mode 100644 dotnet/src/support/packages.config delete mode 100644 dotnet/src/support/packages/repositories.config diff --git a/dotnet/src/support/WebDriver.Support.csproj b/dotnet/src/support/WebDriver.Support.csproj index 6aba6d0d7f508..892e4b315f793 100644 --- a/dotnet/src/support/WebDriver.Support.csproj +++ b/dotnet/src/support/WebDriver.Support.csproj @@ -117,9 +117,6 @@ - - packages\Castle.Core.3.3.3\lib\net40-client\Castle.Core.dll - 3.5 @@ -151,7 +148,4 @@ true - - - \ No newline at end of file diff --git a/dotnet/src/support/packages.config b/dotnet/src/support/packages.config deleted file mode 100644 index 769a089e9f6e6..0000000000000 --- a/dotnet/src/support/packages.config +++ /dev/null @@ -1,4 +0,0 @@ - - - - \ No newline at end of file diff --git a/dotnet/src/support/packages/repositories.config b/dotnet/src/support/packages/repositories.config deleted file mode 100644 index 0dec135fcc9e1..0000000000000 --- a/dotnet/src/support/packages/repositories.config +++ /dev/null @@ -1,4 +0,0 @@ - - - - \ No newline at end of file From 59e96c85af1646008deee449aa933413585b6b1c Mon Sep 17 00:00:00 2001 From: Sergey Tikhomirov Date: Sat, 3 Jan 2015 19:15:41 +0300 Subject: [PATCH 7/8] Now RealProxy class is used in order to proxy elements and lists --- .../PageObjects/DefaultFieldDecorator.cs | 42 +++++++++++++++---- .../ElementCollectionInterceptor.cs | 23 ---------- .../support/PageObjects/ElementInterceptor.cs | 29 ------------- .../support/PageObjects/ElementListProxy.cs | 23 ++++++++++ .../src/support/PageObjects/ElementProxy.cs | 32 ++++++++++++++ dotnet/src/support/PageObjects/Interceptor.cs | 31 -------------- dotnet/src/support/PageObjects/Proxy.cs | 34 +++++++++++++++ dotnet/src/support/WebDriver.Support.csproj | 6 +-- 8 files changed, 126 insertions(+), 94 deletions(-) delete mode 100644 dotnet/src/support/PageObjects/ElementCollectionInterceptor.cs delete mode 100644 dotnet/src/support/PageObjects/ElementInterceptor.cs create mode 100644 dotnet/src/support/PageObjects/ElementListProxy.cs create mode 100644 dotnet/src/support/PageObjects/ElementProxy.cs delete mode 100644 dotnet/src/support/PageObjects/Interceptor.cs create mode 100644 dotnet/src/support/PageObjects/Proxy.cs diff --git a/dotnet/src/support/PageObjects/DefaultFieldDecorator.cs b/dotnet/src/support/PageObjects/DefaultFieldDecorator.cs index 2aee2c1b2ad4f..0c8102bb9bd63 100644 --- a/dotnet/src/support/PageObjects/DefaultFieldDecorator.cs +++ b/dotnet/src/support/PageObjects/DefaultFieldDecorator.cs @@ -1,11 +1,11 @@ -using Castle.DynamicProxy; -using OpenQA.Selenium.Internal; +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 { @@ -102,13 +102,11 @@ protected virtual Object CreateProxy(MemberInfo member) } IElementLocator locator = Factory.CreateElementLocator(member); - ProxyGenerator proxyGenerator = new ProxyGenerator(); if (InterfacesThatCanBeProxiedAsWebElement.Contains(targetType)) { - return proxyGenerator.CreateInterfaceProxyWithoutTarget(typeof(IWebElement), - InterfacesThatCanBeProxiedAsWebElement.ToArray(), - new ElementInterceptor(locator)); + ElementProxy ep = new ElementProxy(CreateTypeForASingleElement(), locator); + return ep.GetTransparentProxy(); } foreach (var type in InterfacesThatCanBeProxiedAsWebElement) @@ -116,8 +114,9 @@ protected virtual Object CreateProxy(MemberInfo member) Type listType = typeof(IList<>).MakeGenericType(type); if (listType.Equals(targetType)) { - return proxyGenerator.CreateInterfaceProxyWithoutTarget(targetType, - new ElementCollectionInterceptor(locator)); + + ElementListProxy ep = new ElementListProxy(targetType, locator); + return ep.GetTransparentProxy(); } } return null; @@ -181,5 +180,32 @@ 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/ElementCollectionInterceptor.cs b/dotnet/src/support/PageObjects/ElementCollectionInterceptor.cs deleted file mode 100644 index 057ea3daf301e..0000000000000 --- a/dotnet/src/support/PageObjects/ElementCollectionInterceptor.cs +++ /dev/null @@ -1,23 +0,0 @@ -using Castle.DynamicProxy; -using OpenQA.Selenium.Support.PageObjects.Interfaces; -using System; -using System.Reflection; - -namespace OpenQA.Selenium.Support.PageObjects -{ - /// - /// Intercepts the request to a collection of 's - /// - sealed class ElementCollectionInterceptor: Interceptor, IInterceptor - { - public ElementCollectionInterceptor(IElementLocator elementLocator) - :base(elementLocator) - {} - - public void Intercept(IInvocation invocation) - { - var elements = ElementLocator.Elements; - invocation.ReturnValue = base.Execute(invocation, elements); - } - } -} diff --git a/dotnet/src/support/PageObjects/ElementInterceptor.cs b/dotnet/src/support/PageObjects/ElementInterceptor.cs deleted file mode 100644 index 355ddf3795095..0000000000000 --- a/dotnet/src/support/PageObjects/ElementInterceptor.cs +++ /dev/null @@ -1,29 +0,0 @@ -using OpenQA.Selenium.Support.PageObjects.Interfaces; -using System; -using System.Reflection; -using Castle.DynamicProxy; -using OpenQA.Selenium.Internal; - -namespace OpenQA.Selenium.Support.PageObjects -{ - /// - /// Intercepts the request to a single - /// - sealed class ElementInterceptor : Interceptor, IInterceptor - { - public ElementInterceptor(IElementLocator elementLocator) - :base(elementLocator) - {} - - public void Intercept(IInvocation invocation) - { - var element = ElementLocator.Element; - if (typeof(IWrapsElement).IsAssignableFrom(invocation.Method.DeclaringType)) - { - invocation.ReturnValue = element; - return; - } - invocation.ReturnValue = base.Execute(invocation, element); - } - } -} 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/Interceptor.cs b/dotnet/src/support/PageObjects/Interceptor.cs deleted file mode 100644 index 751685dec0825..0000000000000 --- a/dotnet/src/support/PageObjects/Interceptor.cs +++ /dev/null @@ -1,31 +0,0 @@ -using Castle.DynamicProxy; -using OpenQA.Selenium.Support.PageObjects.Interfaces; -using System; -using System.Collections.Generic; -using System.Reflection; - -namespace OpenQA.Selenium.Support.PageObjects -{ - abstract class Interceptor - { - protected readonly IElementLocator ElementLocator; - - public Interceptor(IElementLocator elementLocator) - { - this.ElementLocator = elementLocator; - } - - /// - /// This is the common behavior of intercetor. - /// The method will be used or overridden by subclasses - /// - /// This is container of method invocation parameters - /// This the real proxied object - /// The values which is returned as a result of proxied method. - protected object Execute(IInvocation invocation, object proxied) - { - MethodInfo proxiedMethod = invocation.Method; - return proxiedMethod.Invoke(proxied, invocation.Arguments); - } - } -} 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/WebDriver.Support.csproj b/dotnet/src/support/WebDriver.Support.csproj index 892e4b315f793..4bbe64060d864 100644 --- a/dotnet/src/support/WebDriver.Support.csproj +++ b/dotnet/src/support/WebDriver.Support.csproj @@ -84,12 +84,12 @@ - - + + - + From 8987455eee84c2e2f467252d720a6c3d6a8609d2 Mon Sep 17 00:00:00 2001 From: Sergey Tikhomirov Date: Sat, 3 Jan 2015 19:18:02 +0300 Subject: [PATCH 8/8] .gitignore was returned to previous state --- .gitignore | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/.gitignore b/.gitignore index 29c405434bcab..addf95d36f0ee 100644 --- a/.gitignore +++ b/.gitignore @@ -66,5 +66,4 @@ GSYMS GTAGS .project Gemfile.lock -/rb/.bundle -/dotnet/src/support/packages/ \ No newline at end of file +/rb/.bundle \ No newline at end of file