How does it work?
tapir’s architecture is fundamentaly based on the Spring Framework, more precisely on Spring Boot. You do not need to be a Spring expert in order to use tapir, but a basic understanding of the main concepts helps you while exploring tapir. It does not make sense to read the whole Spring (Boot) reference as Spring is a General Purpose Framework with a lot of features. tapir only uses a small subset of this. Therefore we introduce the main concepts in this chapter and refer to the Spring reference selectively.
Spring manages all instances of classes (so called beans) in a container. So to fire up a Spring based application you have to build the Spring Container first. This job is done by the TapirBootstrapper. It searches for a module in the classpath which contains a @BootstrapConfiguration annotated class.
Beside the Bootstrap modules all inclusion modules are picked up by searching for @ModuleConfiguration annotated classes. All of these configurations provide so-called beans which are just instances of certain classes. These beans can be defined by adding a @Bean annotated method in the configuration class or by placing a @Component annotated class in a (sub-) package of the configuration class.
After picking up the configurations and their beans the Spring Container is built. Nearly all instances used in tapir are managed by Spring. Therefore it is possible to access these instances in any class.
Building your Execution Plan
The test execution runs completely within the Spring container and therefore all involved instances are managed by Spring.
The picture looks similar to the one above, but has a completely different purpose. The TapirExecutorpicks up all the @TestSuiteand @TestClass annotated classes which are provided by the picked up configurations. In other words, TestSuites and TestClasses are Spring beans and the TapirExecuter collects all Spring beans which are annotated by @TestSuite or @TestClass. Test suites help you structuring your test classes. You can also nest your test suites infinitely.
The test suites and classes are transformed to an execution plan which is executed afterwards.
Running your Execution Plan
The execution of the execution plan is delegated to the ExecutionPlanExecutor. The ExecutionPlanExecuter traverses the execution plan and executes the test classes and their steps. If a test class fails, the whole test class/suite is skipped unless you annotate it with @ProceedOnFailure.
The executor notifies registered execution listeners about every event (suite/class/step started/succeeded/failed/skipped). These execution listeners can use this information, e.g. for building reports.
tapir heavily uses Dependency Injection. Nearly all instances are managed by Spring. Therefore it’s possible to inject any bean in any other bean by using the @Autowired annotation. As tapir just leverages the features Spring provides, it’s most reasonable to read the corresponding Spring documentation.
tapir is shipped with a couple of modules. These modules can be distinguishes into core, ui and extension modules. The core modules drive tapir’s fundamental concepts like annotation processing, using immutables or building and executing an execution plan. The ui modules provide some basic technology agnostic API and concrete implemtations like Selenium.
Extensions are completely independent of any other extension. They use the API provided by the core modules (mostly the execution module) to hook in. On the one hand, there are extensions which extend the features while writing tests like Conditional, Configuration and Variant, on the other hand there are extensions which are just used at runtime like Allure or CLI.
While developing the page objects you just need the Page module and an API modules which provides interfaces for the UI elements on your page. The test classes and their steps can use these pages to declare tests. If approriate, additional extensions like Conditional can be used.
When it comes to the execution of test suits and classes additional extensions step in. The test execution decides which browser should be used to execute the tests and which reports (Allure, JUnit,…) should be generated. Of yourse the test execution is also aware of the extensions which have been used by the test classes and interprets them.