Hybrid UI test framework

    The Main concepts that are used are:

Component-based architecture will give us the following key principles:

● Reusable. Components are usually designed to be reused in different scenarios in different applications. However, some components may be designed for a specific task.
● Replaceable. Components may be readily substituted with other similar components.
● Not context specific. Components are designed to operate in different environments and contexts. Specific information, such as state data, should be passed to the component instead of being included in or accessed by the component.
● Extensible. A component can be extended from existing components to provide new behavior.
● Encapsulated. Components expose interfaces that allow the caller to use its functionality, and do not reveal details of the internal processes or any internal variables or state.
● Independent. Components are designed to have minimal dependencies on other components.


    Therefore, components can be deployed into any appropriate environment without affecting other components or systems. Separation of concerns (Single responsibility principle at lower level) will enforce and support the modularity. Also simplify development and maintenance of those modules. When concerns are well-separated, individual components/packages can be reused, as well as developed and updated independently. Of special value is the ability to later improve or modify one section of code without having to know the details of other sections, and without having to make corresponding changes to those sections. By concept Meta frameworks provide a method of solving the problem with automating multiple pieces as part of a larger automation strategy. Testers define independent utility classes which can be generically used with any automation tool and can be reused between different automation projects as well. The framework provides an abstraction layer that allows the separate automation pieces to be executed and have their results reported back in a standardized way.

    Adopting the CookieCutter approach (like the meta API test framework did) will provide generic project template for the rest of the UIAutomation Test projects. This concept is widely used (and proved useful) in Python, JS, HTML and by the DevOps team too. NuGet packages will be used to support the modularity of the Test logic, specification and data. At low-level this paradigm looks like Classes Vs Objects concept. At run time you would have the objects A, B, C and so on—that is, specific instances of the class. If you’re familiar with database terms, it’s the same as the distinction between “schema” and “instance.” You could think of the class as the cookie cutter and the object as the cookie.

    We aim to utilize the structural concept of the Bridge design pattern - decouple an abstraction from its implementation so that the two can vary independently. The interface object is the "handle" known and used by the client;  while the implementation object, or "body", is safely encapsulated to ensure that it may continue to evolve, or be entirely replaced. This will allow us to share an implementation among multiple projects, improve extensibility and hiding details from clients. It’ll decompose the complex abstraction into smaller, more manageable packages/classes. Further more Bridge pattern uses dependency injection in it to achieve the required polymorphic behavior where the InterfaceAPI is being injected in the constructor to decouple the ActionAPI from the concrete implementation of API.

    Separated Interface defines an interface in a separate package from its implementation. As we develop the framework, we can improve the quality  of its design by reducing the coupling between  its parts. A good way to do this is to group the classes into packages and control the dependencies between them. We can then follow rules about how classes in one package can call classes in another - for example, one that says that classes in the domain layer may not call classes in the presentation package. However, we might need to invoke methods that contradict the general dependency structure. If so, use Separated Interface to define an interface in one package but implement it in another. This way a client that needs the dependency to the interface can be completely unaware of the implementation. The Separated Interface provides a good plug point for Gateway pattern.


   I'm aiming to cover the following KPIs:

    Build a ‘common’ framework, which provides the commonly used setup and  teardown features used by different projects, reusable code as modules (packs) eg:

● All required setup methods e.g. Creating users (normal and admin), set DB at desired state
● Common generic utils available as modules ( NuGet packs) - Reporting (Report target, QA DB), Parsers, DB Accessors, Random generators
● All required tear down
● Separation of Test harness to Test.Engine, .Specification and .Data

    By enforcing this we’re aiming to reduce code duplication and promote generic architecture, good code practices such as:
- Page object model
- Navigator pattern/Composite Page Object (aggregates reused page objects in one external object)
- Arrangement page navigation via URL over crawling
- Selenium calls wrappers of click-and-wait, safe-click
- Custom exceptions
- Naming convention of modules


   I'll put together some raw Design sketch:

● Acyclic dependencies (avoid going through several modules,layers)
● Module Reuse; Cohesive(serve a singular purpose)
● Runtime container independence( user/environments’ External config file)
● Independent SDLC of modules - be independent, less outgoing dependencies
● Published Interface - Modules should encapsulate implementation details so that other modules don’t need to understand the implementation to use the module.
● Default Implementation (generic specification) VS Adapter pattern (code)
● Module Façade- entry point to another fine grained modules underlying implementation.
● Abstract Modules-depend upon abstract elements of a module.  It’ll give us greater opportunities to extend the system by defining new modules with classes that implement or extend the abstraction. We’re aiming to provide not only reusability, but flexability too - in more Plugin-like archirtecture any clients of the module also have the ability to define their own implementations and to plug them into the module (extend and specify it’s behaviour).
● Implementation (abstract) Factory - Use of factories to create a module’s implementation classes. Any module whose classes depend upon the abstract elements of another module should avoid referencing any implementation classes.
● Separate Abstractions -  implementation plugin flexibility, ability to provide new implementations that completely replace existing implementations. But still keep the Reused Abstractions Principle (RAP) - do the implementation really have polymorphism and substitutability, or Class : Interface is 1:1 ?
● Colocate Exceptions - close to the classes that catch them VS modules across same exception (new exception low level module)
● Generics VS Polymorphism VS Aggregation - Maximizing genericity complicates use
●Separation of Functional and Rendering UI tests  - leave the rendering validation to the child projects, since they'll be specific to them anyway OR  via the ‘content sync’ feature that will allow just passing the specific locators for the form it's needed. E.g. we check that in current page there is a table which is visible and has text. If such approach (UI Map/Object Repository using Properties File) will be applied a common webelements-vault will be needed. The both projects must be able to reuse all the same locators. Such solution is available in the Zeus Automation project, where the locators are kept in a .property files per site accordingly.



  The ultimate goal of such UI framework is to share some steps and definitions (TestModule.Specification) between separate Test projects. Keeping in mind this solution is possible in the Functional UI test projects. Clearly pure rendering ones can’t take advantage here, if ‘content sync’ feature is not fully utilized. This however will require no test data polluting it e.g. hardcoded parameters or variables’ values. Techniques like Use case scenarios, State transition / Interaction diagrams can be used here to find the cross points that’ll make the reusability clear.

    Modules
  In this section will be described the main modules that needs to be placed in such Common framework. By current naming convention the format is Company.Module.PackageName:

   Reporter - implements IReporter, should be able to populate the results gained from the tests' execution. At some point there may be couple of targets - TestCase management system and a QA DB.

  DBAccessor - should be able to execute CRUD operations in the target DB of the related Application server.

  EnvironmentsManager -  should be able to pick-up the correct configuration for each environment. One possible solution is a CI server build that will set the transform files (web.config, app.config) for each corresponding environment.

    RandomGenerator - should be able to generate unique values per specified data type or required attribute (such as email, phone, country code etc.). One sub-module will be a wrapper of Company.RandLong client.

   TestFixtureRegistry -  it’ll be able to expose various parts of a fixture (needed for suites) via discrete fixture holding class variables or via Finder Methods. Finder Methods helps us avoid hard-coded values in DB lookups in order to access the fixture objects. Those methods are very similar to those of Creation Methods, but they return references to existing fixture objects rather than building brand new ones. Should make those immutable.   NOTE: usage of  Intent-Revealing Names for the fixture objects should be enforced in order to support the framework’s lookup functionality and better readability. To keep the current OO design the following implementation can be used - check if such registry already exists and remove it; System.IO’s classes StreamReader and StreamWriter via Lazy Initialization will create/access the new actual registry; by doing so will utilize In-Memory database (lightweight version is to pass reference to System.Collections.Generic.Dictionary  or System.Collections.Concurrent.ConcurrentDictionary) for the fixtures required by the tests. All this will minimize the dependency of using other code or configs, also will avoid unnecessary calls to the backend’s tier. NOTE: in consideration must be taken the Data Sensitivity and Context Sensitivity of the tests that’ll rely on this module.

    UITestFramework.CookieCutter - should support initial default setup for the UI test projects. By concept there shouldn’t be any difference between Functional and Rendering UI project, when this module is being utilized. All the required pre-setup and it’s configs must be placed here This module should be split into two:
- .Engine: The implementation includes WebDriverProxy which exposes the exact same behavior as the object it hides (IWebDriver). Also it modifies the interface without modifying it's behavior. The Action class provides proved to be useful wrappers of Selenium's commonly used duplication code. All this is powered up by WebDriverFactory, since Drivers are complex to create and we escape polluting the concrete classes constructors.
- .Tests: The implementation includes BaseBinding which should be used as binding for all test Steps
(classes) in the inheritance tree. Also TestSuiteContext should support additional configuration for the test suites. The main goal of this pack is to set up inital design and to provide signatures for the basic common Step classes ( BasicSteps/ BasicGivenSteps;  BasicWhenSteps; BasicThenSteps) as the BDD functionality does when is added to any project.  NOTE:  this abstraction will allow the testers to further extend it with PageObject model or Object repository pattern for their framework’s web elements. as per Test harness agreed design.

     UITestFramework.WebElementsComparator: This logic will extend current functional testing possibilities of Selenium to support more layout content validation. We could split out UI testing into functional and rendering verification. Every web element will be compared by utilizing the Screenshot capabilities. In general, we could search for our webelement as image in the current screenshot of the page. The Comparator will be able to support parallel execution even on a single VirtualMachine node (depends from the actual Test lab infrastructure).

     One very important aspect that will support you in case you decide to utilize such automation approach is the craftsman of the Unit testing. Every module that contains logic (not tests) should be enforced to support own .UnitTests project. I can put numerous reasons why.....but should I !?!

2 comments:

  1. Pretty article! I found some useful information in your blog, it was awesome to read, thanks for sharing this great content to my vision, keep sharing.. Need to learn Software Testing Services

    ReplyDelete