/// <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);
        }