/// <summary>
        /// Run tests and collect statistics.
        /// </summary>
        /// <param name="testClassStatistic">The test class statistics.</param>
        /// <param name="instance">The instance.</param>
        /// <param name="testMethod">The test method.</param>
        private static void Test(TestClassStatistic testClassStatistic, IPageObjectTests instance, MethodInfo testMethod)
        {
            Trace.WriteLine("Executing page test <" + testMethod.Name + ">");

            var startTimeForCurrentIteration = DateTime.Now;

            try
            {
                // execute test method
                testMethod.Invoke(instance, null);

                // add method statistics
                testClassStatistic += new TestMethodStatistic()
                {
                    MethodInfo = testMethod, PageType = instance.PageType, Info = "none", Start = startTimeForCurrentIteration, Success = true
                };
            }
            catch (Exception e)
            {
                Exception exceptionForTrace = e;

                if (e is System.Reflection.TargetInvocationException && e.InnerException != null)
                {
                    exceptionForTrace = e.InnerException;
                }

                Trace.WriteLine(string.Format("Exception in page test {0}: {1}", testMethod.Name, e.Message));
                Trace.WriteLine(exceptionForTrace.StackTrace);
                Trace.WriteLine(exceptionForTrace.ToString());

                // add method statistics
                testClassStatistic += new TestMethodStatistic()
                {
                    MethodInfo = testMethod, PageType = instance.PageType, Info = e.GetType().Name, Start = startTimeForCurrentIteration, Success = false
                };

                throw;
            }

            Trace.WriteLine("Page test finished: " + testMethod.Name);
            Trace.WriteLine("------------------------------------------------------------");
        }
        /// <summary>
        /// Run tests and collect statistics.
        /// </summary>
        /// <param name="testClassStatistic">The test class statistics.</param>
        /// <param name="instance">The instance.</param>
        /// <param name="testMethod">The test method.</param>
        private static void Test(TestClassStatistic testClassStatistic, IPageObjectTests instance, MethodInfo testMethod)
        {
            Trace.WriteLine($"Executing page test <{testMethod.Name}>");

            var startTimeForCurrentIteration = DateTime.Now;

            try
            {
                // execute test method
                testMethod.Invoke(instance, null);

                // add method statistics
                testClassStatistic += new TestMethodStatistic()
                {
                    MethodInfo = testMethod, Info = null, Start = startTimeForCurrentIteration, Success = true
                };
            }
            catch (Exception e)
            {
                Exception exceptionForTrace = e;
                if (e is TargetInvocationException && e.InnerException != null)
                {
                    exceptionForTrace = e.InnerException;
                }

                Trace.WriteLine($"Exception in page test {testMethod.Name}: {e.Message}{Environment.NewLine}{exceptionForTrace.StackTrace}{Environment.NewLine}{exceptionForTrace.ToString()}");

                // add method statistics
                testClassStatistic += new TestMethodStatistic()
                {
                    MethodInfo = testMethod, Info = e.GetType().Name, Start = startTimeForCurrentIteration, Success = false
                };

                ExceptionDispatchInfo.Capture(e.InnerException).Throw();
            }

            Trace.WriteLine($"Page test finished: {testMethod.Name}{Environment.NewLine}------------------------------------------------------------");
        }
        /// <summary>
        /// Run tests for this page object.
        /// </summary>
        /// <param name="source">The source page object.</param>
        /// <param name="methodFilter">The test method filter predicate.</param>
        /// <param name="pageTestClassFilter">The page test class filter predicate.</param>
        /// <returns>This page object.</returns>
        public static IPageObject Test(this IPageObject source, Predicate <MethodInfo> methodFilter = null, Predicate <IPageObjectTests> pageTestClassFilter = null)
        {
            methodFilter        = methodFilter ?? (_ => true);
            pageTestClassFilter = pageTestClassFilter ?? (_ => true);

            foreach (Type classWithTests in source.TestClasses())
            {
                Trace.WriteLine("Found page test class for current class " + source.GetType().ToString() + ": " + classWithTests.ToString());

                // create and initialize page test class
                IPageObjectTestsInternal instance;
                var root = ((IUIObjectInternal)source).Root;
                try
                {
                    var configuration = root.Configuration;
                    configuration
                    .DependencyRegistrator
                    .RegisterType(classWithTests);

                    instance = (IPageObjectTestsInternal)configuration.resolver.Resolve(classWithTests);
                }
                catch (Stashbox.Exceptions.ResolutionFailedException exception)
                {
                    throw new TypeResolutionFailedException(exception, $"Configure the resolver via '{nameof(Configuration.DependencyRegistrator)}' in class '{root.GetType().FullName}'.");
                }

                // init page test class
                instance.Init(source);

                // check if tests should be executed according to the page test filter
                if (pageTestClassFilter(instance))
                {
                    Trace.WriteLine("Page test class filter returned true; running tests...");
                }
                else
                {
                    Trace.WriteLine("Page test class filter returned true; skipping tests...");
                    continue;
                }

                // check if tests should be executed according to the runnable predicate
                if (instance.Runnable)
                {
                    Trace.WriteLine("Runnable returned true; running tests...");
                }
                else
                {
                    Trace.WriteLine("Runnable returned false; skipping tests");
                    continue;
                }

                // get page test methods, and order them from top to bottom
                // ordering does not respect inheritence
                var methods               = classWithTests.GetMethods().Where(method => method.GetCustomAttributes(typeof(PageTestAttribute), false).Length > 0);
                var methodsByLine         = methods.OrderBy(method => method.GetCustomAttribute <PageTestAttribute>(true).Line);
                var matchingMethodsByLine = methodsByLine.Where(method => methodFilter(method));

                int testMethodsCount = matchingMethodsByLine.Count();
                if (testMethodsCount > 0)
                {
                    Trace.WriteLine("Found " + testMethodsCount + " test methods");
                }
                else
                {
                    Trace.WriteLine("No test method found; skipping test class");
                    continue;
                }

                Trace.WriteLine($"Execute function {nameof(IPageObjectTests.BeforeFirstTest)}...");
                instance.BeforeFirstTest();

                // check if tests should be executed
                if (!instance.ReadyToRun)
                {
                    throw new TestNotReadyToRunException(classWithTests.FullName);
                }
                else
                {
                    Trace.WriteLine("Tests are ready to run...");
                }

                // create class statistics object
                TestClassStatistic testClassStatistic = new TestClassStatistic(classWithTests);

                // execute test methods
                try
                {
                    foreach (var testMethod in matchingMethodsByLine)
                    {
                        if (instance.IsTestRunnable(testMethod))
                        {
                            Test(testClassStatistic, instance, testMethod);
                        }
                        else
                        {
                            Trace.WriteLine($"Skipping test: '{nameof(instance.IsTestRunnable)}' returned false for test method '{testMethod.Name}'.");
                        }
                    }

                    Trace.WriteLine($"Execute function {nameof(IPageObjectTests.AfterLastTest)}...");
                    instance.AfterLastTest();
                }
                finally
                {
                    var pageObjectStatistic = PageObjectStatistic(source);
                    pageObjectStatistic += testClassStatistic;
                }
            }

            return(source);
        }
        /// <summary>
        /// Run tests for this page object.
        /// </summary>
        /// <param name="source">The source page object.</param>
        /// <param name="filter">The test method filter predicate.</param>
        /// <param name="writeTree">Whether to write the result tree.</param>
        /// <param name="pageObjectStatistics">The page object statistics.</param>
        /// <returns>This page object.</returns>
        private static Tree Test(this IPageObject source, Func <MethodInfo, Type, bool> filter, bool writeTree, PageObjectStatistics pageObjectStatistics)
        {
            filter = filter ?? ((e, f) => true);
            Tree result  = null;
            bool success = true;

            try
            {
                foreach (Type classWithTests in source.TestClasses())
                {
                    Trace.WriteLine($"Found page test class for current class {source.GetType().ToString()}: {classWithTests.ToString()}");

                    // create and initialize page test class
                    var instance = (IPageObjectTestsInternal)Activator.CreateInstance(classWithTests);
                    instance.Init(source);

                    // check if tests should be executed
                    if (instance.Runnable)
                    {
                        Trace.WriteLine("Runnable returned true; running tests...");
                    }
                    else
                    {
                        Trace.WriteLine("Runnable returned false; skipping tests");
                        continue;
                    }

                    // get page test methods, and order them from top to bottom
                    // ordering does not respect inheritance
                    var methods               = classWithTests.GetMethods().Where(m => m.GetCustomAttributes(typeof(PageTestAttribute), false).Length > 0);
                    var methodsByLine         = methods.OrderBy(asd => asd.GetCustomAttribute <PageTestAttribute>(true).Line);
                    var matchingMethodsByLine = methodsByLine.Where(f => filter(f, classWithTests));

                    int testMethodsCount = matchingMethodsByLine.Count();
                    if (testMethodsCount > 0)
                    {
                        Trace.WriteLine($"Found {testMethodsCount} test methods");
                    }
                    else
                    {
                        Trace.WriteLine("No test method found; skipping test class");
                        continue;
                    }

                    Trace.WriteLine($"Execute function {nameof(IPageObjectTests.BeforeFirstTest)}...");
                    instance.BeforeFirstTest();

                    // check if tests should be executed
                    if (!instance.ReadyToRun)
                    {
                        throw new TestNotReadyToRunException(classWithTests.FullName);
                    }
                    else
                    {
                        Trace.WriteLine("Tests are ready to run...");
                    }

                    // create class statistics object
                    TestClassStatistic testClassStatistic = new TestClassStatistic(classWithTests);

                    // execute test methods
                    try
                    {
                        foreach (var testMethod in matchingMethodsByLine)
                        {
                            Test(testClassStatistic, instance, testMethod);
                        }

                        Trace.WriteLine($"Execute function {nameof(IPageObjectTests.AfterLastTest)}...");
                        instance.AfterLastTest();
                    }
                    finally
                    {
                        success = false;
                        pageObjectStatistics.Add(source, testClassStatistic);
                    }
                }
            }
            finally
            {
                result = Root(source).Tree;
                foreach (var pair in pageObjectStatistics.Stats)
                {
                    // merge trees
                    result += pair.Value;
                    result += new Edge {
                        To = pair.Value.Root.Id, From = new Node()
                        {
                            Id = pair.Key.FullName
                        }.Id
                    };
                }

                if (!success || writeTree)
                {
                    result.WriteGraph();
                }
            }

            return(result);
        }