diff --git a/doc/source/_static/balderexample-loginserver-login.png b/doc/source/_static/balderexample-loginserver-login.png deleted file mode 100644 index 37c29beb..00000000 Binary files a/doc/source/_static/balderexample-loginserver-login.png and /dev/null differ diff --git a/doc/source/_static/custom.css b/doc/source/_static/custom.css new file mode 100644 index 00000000..a7f0fba6 --- /dev/null +++ b/doc/source/_static/custom.css @@ -0,0 +1,97 @@ +.balder-button-grid { + display: grid; + grid-template-columns: repeat(2, 1fr); + grid-template-rows: repeat(2, auto); + gap: 20px; + margin: 20px 0; +} + +@media (max-width: 768px) { + .balder-button-grid { + grid-template-columns: 1fr; + } +} + +.balder-button-item { + background-color: #f0f0f0; + border: 1px solid #ccc; + border-radius: 8px; + padding: 15px; + text-align: center; + cursor: pointer; + transition: background-color 0.3s; +} + +.balder-button-item:hover { + background-color: #e0e0e0; +} + +.balder-button-item h4 { + margin: 0 0 10px 0; + font-size: 1.2em; +} + +.balder-button-item p { + margin: 0; + font-size: 0.9em; + color: #666; +} + + +.balder-highlight-container { + margin: 2em 0; +} + +.balder-row { + display: flex; + align-items: center; + margin-bottom: 2em; + gap: 20px; +} + +.balder-row.reverse { + flex-direction: row-reverse; +} + +.balder-image-side { + flex: 0 0 18%; +} + +.balder-image-side img { + width: 86%; + height: auto; + max-height: 300px; + object-fit: cover; + border-radius: 8px; +} + +.balder-text-side { + flex: 1; + padding: 15px; +} + +.balder-text-side h4 { + margin: 0 0 10px 0; + font-size: 1.2em; +} + +.balder-text-side p { + margin: 0; + font-size: 0.9em; +} + +@media (max-width: 768px) { + .balder-row, .balder-row.reverse { + flex-direction: column; + gap: 15px; + } + + .balder-image-side { + flex: 0 0 auto; + width: 40%; + } + + .balder-text-side { + width: 100%; + } +} \ No newline at end of file diff --git a/doc/source/_static/icons8/icons8-bookmark.svg b/doc/source/_static/icons8/icons8-bookmark.svg new file mode 100644 index 00000000..287ad87d --- /dev/null +++ b/doc/source/_static/icons8/icons8-bookmark.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/doc/source/_static/icons8/icons8-box.svg b/doc/source/_static/icons8/icons8-box.svg new file mode 100644 index 00000000..d923b196 --- /dev/null +++ b/doc/source/_static/icons8/icons8-box.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/doc/source/_static/icons8/icons8-check-mark.svg b/doc/source/_static/icons8/icons8-check-mark.svg new file mode 100644 index 00000000..1916f225 --- /dev/null +++ b/doc/source/_static/icons8/icons8-check-mark.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/doc/source/_static/icons8/icons8-checkmark.svg b/doc/source/_static/icons8/icons8-checkmark.svg new file mode 100644 index 00000000..8d49f3c5 --- /dev/null +++ b/doc/source/_static/icons8/icons8-checkmark.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/doc/source/_static/icons8/icons8-connect.svg b/doc/source/_static/icons8/icons8-connect.svg new file mode 100644 index 00000000..4532680a --- /dev/null +++ b/doc/source/_static/icons8/icons8-connect.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/doc/source/_static/icons8/icons8-external-link.svg b/doc/source/_static/icons8/icons8-external-link.svg new file mode 100644 index 00000000..c492afd8 --- /dev/null +++ b/doc/source/_static/icons8/icons8-external-link.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/doc/source/_static/icons8/icons8-file.svg b/doc/source/_static/icons8/icons8-file.svg new file mode 100644 index 00000000..7de6ecf7 --- /dev/null +++ b/doc/source/_static/icons8/icons8-file.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/doc/source/_static/icons8/icons8-idea.svg b/doc/source/_static/icons8/icons8-idea.svg new file mode 100644 index 00000000..3be248c4 --- /dev/null +++ b/doc/source/_static/icons8/icons8-idea.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/doc/source/_static/icons8/icons8-puzzle.svg b/doc/source/_static/icons8/icons8-puzzle.svg new file mode 100644 index 00000000..d7949f80 --- /dev/null +++ b/doc/source/_static/icons8/icons8-puzzle.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/doc/source/_static/icons8/icons8-refresh.svg b/doc/source/_static/icons8/icons8-refresh.svg new file mode 100644 index 00000000..def6288e --- /dev/null +++ b/doc/source/_static/icons8/icons8-refresh.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/doc/source/_static/icons8/icons8-share.svg b/doc/source/_static/icons8/icons8-share.svg new file mode 100644 index 00000000..be199756 --- /dev/null +++ b/doc/source/_static/icons8/icons8-share.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/doc/source/_static/icons8/icons8-synchronize.svg b/doc/source/_static/icons8/icons8-synchronize.svg new file mode 100644 index 00000000..63c2090b --- /dev/null +++ b/doc/source/_static/icons8/icons8-synchronize.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/doc/source/_static/icons8/icons8-toolbox.svg b/doc/source/_static/icons8/icons8-toolbox.svg new file mode 100644 index 00000000..ebcfaddb --- /dev/null +++ b/doc/source/_static/icons8/icons8-toolbox.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/doc/source/_static/icons8/icons8-user.svg b/doc/source/_static/icons8/icons8-user.svg new file mode 100644 index 00000000..648fba82 --- /dev/null +++ b/doc/source/_static/icons8/icons8-user.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/doc/source/conf.py b/doc/source/conf.py index 69710aa0..91bcc165 100644 --- a/doc/source/conf.py +++ b/doc/source/conf.py @@ -72,7 +72,7 @@ html_logo = "_static/balder.png" html_theme_options = { "announcement": ( - "Balder is still in BETA state and is not officially released yet" + "Balder official release date: 25th of October - Stay Tuned" ) } @@ -81,6 +81,8 @@ # so a file named "default.css" will overwrite the builtin "default.css". html_static_path = ['_static'] +html_css_files = ['custom.css'] + # -- mermaid mermaid_version = "" diff --git a/doc/source/index.rst b/doc/source/index.rst index 1dccef4a..99756fbe 100644 --- a/doc/source/index.rst +++ b/doc/source/index.rst @@ -3,8 +3,8 @@ You can adapt this file completely to your liking, but it should at least contain the root `toctree` directive. -Balder -****** +Balder: Testing real-world complexity +************************************* .. toctree:: :maxdepth: 2 @@ -18,47 +18,157 @@ Balder BalderHub -Balder is a very powerful, universal and flexible python test system that allows you to reuse a once written testcode as -efficiently as possible for different but similar platforms/devices/applications. - -In the real world you have a lot of different projects that use the same interfaces, like -the SNMP, SMTP or HTTP protocol. Other processes like *login into backend* are being implemented multiple -times as well. This often works in the similar way for login with the app, over the api or directly over the web -frontend. If you want to test these various possibilities, you have to provide similar tests, which are often times -repetitive. - -For example if you have a backend system which you are testing on the login function, you would have three possible -ways to login. You could use the mobile app, the normal web app and the api interface. For all of these possibilities -you follow a similar pattern: - -1. open the login area/create the login request -2. insert the username -3. insert the password -4. send the request -5. check if you are successfully logged in - -These steps are independent from the method you want to login with. You would still have the same **Scenario** but -different **Setups**: - -* **Setup 1:** a smartphone app on iOS -* **Setup 2:** a smartphone app on Android -* **Setup 3:** a webpage which provides the login area -* **Setup 4:** a API endpoint which allows to identify with your username and password - -Balder was created especially for that. You can write one **Scenario** and use it for different setups. - -You can find out more about Scenario-Based-Testing and how Balder works in this documentation. - -If you are completely new, we recommend to start with the :ref:`Getting Started ` section. Since, in -our opinion, the best way is learning-by-doing. We recommend to continue with the :ref:`Tutorial Guide`. If -you want to discover all components of Balder you can continue with the :ref:`Basic Guides`. All profound -functions of Balder, like the different internal work processes, you can find in the -:ref:`Reference Guides`. - -It is recommended to go throw the different subsections of the :ref:`Getting Started ` and the -:ref:`Tutorial Guide` in the provided order to fully understand the basic functionality of **Scenario-based-testing** -and how it works in the Balder framework. The different subsections of :ref:`Basic Guides` and -:ref:`Reference Guides` are created independently from each other. So feel free to jump to the -subsection you want to learn more about. But we always recommend to start with the complete -:ref:`Getting Started ` section before going deeper into the :ref:`Basic Guides` and -:ref:`Reference Guides`. \ No newline at end of file +Balder is a Python test framework that allows you to reuse test code you've written once across different platforms, +devices, or applications. You can **install ready-to-use tests** or apply your **own pre-written tests** to evaluate +all kinds of end-user devices/applications - without reinventing the wheel. + + +.. raw:: html + +
+ +
+
+ connection icon +
+
+

Scalability without the Headache

+

+ Write a single test scenario for your core business logic, and reuse it across the web, mobile apps, + desktop applications, or even embedded devices. +

+ No rewrites are needed! +

+
+
+ +
+
+ share icon +
+
+

Reusability at its Core

+

+ Every component in Balder is designed with sharing in mind. You can package your Balder tests or/and + test-features and easily share them within your company or with the entire open-source community. +

+
+
+ +
+
+ synchronization icon +
+
+

Testing Multiple Versions - all at once

+

+ Stop copying your test code every time a new product version comes out. Simply replace the specific test + features that change in the product. +

+ Balder will manage everything else seamlessly! +

+
+
+ +
+
+ file icon +
+
+

Ready-to-use Tests

+

+ BalderHub projects allow you to install tests just like any other Python package. +

+ Simply integrate them into your workflow and start using them right away! +

+
+
+ +
+
+ checkmark icon +
+
+

Test-your-Tests

+

+ Testing your tests can help reduce flaky ones, but it often takes a lot of time. +

+ Not with Balder: Testing your tests has never been easier. +

+
+
+ +
+
+ user icon +
+
+

Community driven power

+

+ Tests thrive when maintained by a collective. Balder fosters collaboration, where diverse minds + tackle complex challenges together. +

+ Leading to robust solutions that a single team could never achieve alone. +

+
+
+ +
+ + + +Are you up for it? +================== + +Let's dive in and explore our tutorials. + +.. raw:: html + + + + +To get a short introduction to how Balder works, take a look at the :ref:`Getting Started ` section. +It provides a brief overview of the theory behind the Balder test system. You'll find a detailed explanation of all +components in the :ref:`Basic Guides `. + +The different subsections of :ref:`Basic Guides ` and :ref:`Reference Guides ` are +written independently of each other. So feel free to jump to any subsection you want to learn more about. However, we +always recommend starting with the complete :ref:`Getting Started ` section before diving deeper into +the :ref:`Basic Guides ` and :ref:`Reference Guides `. + +Let's build the open-source testing universe together +===================================================== + + +Help to build the future of testing, by developing your own BalderHub project. + +`With BalderHub projects `__, you can easily share your tests with your team, your company, or +even the entire world. + +Help build the future of testing +`by creating your own BalderHub project `__. + + +.. raw:: html + +
+
Icons from
+

icons8.com

+
diff --git a/doc/source/tutorial_guide/00_intro.rst b/doc/source/tutorial_guide/00_intro.rst index c86b6b85..1ba37ab3 100644 --- a/doc/source/tutorial_guide/00_intro.rst +++ b/doc/source/tutorial_guide/00_intro.rst @@ -1,173 +1,6 @@ Balder Intro Example ******************** -In the next sections, we want to test the login functionality for a website. For this, we will create a scenario, that -allows us to test the login procedure. This scenario secures that we are logged-in over the possibility that we can -access the internal webpage. - -Our goal is an implementation, that allows executing the scenario with a lot of different environments (``Setup`` -classes). For example we want to execute our test scenarios with an UI setup and also with a REST API setup. - -The project we are using in this tutorial is the ``balderexample-loginserver``. You can find it -`on GitHub `_. It is a -`Django Application `_ that has a graphical login interface to login into a -internal backend area. In addition to that, the project also implements a REST interface, which allows to get a JSON -representation of all registered users and permission groups. This REST application supports a -`Basic Authentication `_ (with username and password). - -Do not worry, you don't need experiences with Django or with REST, we will go through this topics and don't have to -implement these functionality by ourselves. - -So let's start. - -Prepare the loginserver -======================= - -Before we can really start testing the loginserver, we have to initialize our environment. - -Checkout/Download loginserver ------------------------------ - -First of all you have to checkout the loginserver. For this make sure, that you have -`installed git `_. - -After you have installed git, you can checkout the repository with the command: - -.. code-block:: - - >>> git clone https://github.com/balder-dev/balderexample-loginserver.git - -This command will download the latest environment of the loginserver. - -**ALTERNATIVE: Manually download the repository** - -Otherwise you can also download the loginserver package -`from github `_ and extract it in a directory of your choice. -Make sure that you download/checkout the main branch! - -OPTIONAL: Start virtual environment ------------------------------------ - -With virtual environments it is possible to separate your system installed packages from the project dependencies. With -this you install project packages only on project level and your system packages will not be changed. More information -about the virtual environment, you can find in -`the official venv documentation `_. - -You can create a new virtual environment simple by executing the build in command. Go to the root directory of the -loginserver project and execute the following command - -.. code-block:: - - >>> python38 -m venv venv - -This will create the virtual environment. - -After you have created the venv, you have to activate it. The command to activate the virtual environment is depended -on your OS: - -.. list-table:: Activate VirtualEnv - :widths: 25 25 50 - :header-rows: 1 - - * - OS - - Shell - - Command - * - POSIX - - bash/zsh - - $ source venv/bin/activate - * - POSIX - - fish - - $ source venv/bin/activate.fish - * - POSIX - - csh/tcsh - - $ source venv/bin/activate.csh - * - POSIX - - PowerShell Core - - $ source venv/bin/Activate.ps1 - * - POSIX - - csh/tcsh - - $ source venv/bin/activate.csh - * - POSIX - - PowerShell Core - - $ venv/bin/Activate.ps1 - * - WINDOWS - - cmd.exe - - C:\> venv\Scripts\activate.bat - * - WINDOWS - - PowerShell - - PS C:\> venv\Scripts\Activate.ps1 - -.. note:: - You have to activate the venv (last command) in every new console session before you can use the virtual - environment. - -Install requirements --------------------- - -All dependencies that are required by the loginserver project are defined in the ``requirements.txt``. You can install -all of these dependencies by simply executing the following command: - -.. code-block:: - - >>> pip install -r requirements.txt - -This command will install all dependencies for the loginserver. - -Start development server ------------------------- - -If you want to run the ``balderexample-loginserver`` application, just start the django included developer server. For -this, call the following command: - -.. code-block:: - - >>> python3 manage.py runserver - - -Now the server is available at url http://localhost:8000 - -.. image:: ../_static/balderexample-loginserver-login.png - -**Use specific port:** - -You can also set a specific port with an additional argument. To start the server on port 3000 simply execute the -command: - -.. code-block:: - - >>> python3 manage.py runserver 3000 - -.. warning:: - Never use this package in an real environment. It uses the Django developer server that is only for developing and - not secure to use in a productive environment. - -Prepare Balder -============== - -Now it's time to prepare our Balder environment. For this, we create a sub folder ``tests`` into our project. .. note:: - - For an easier development we integrate our Balder project inside the main project directory. In the most cases, it - doesn't make sense to do it this way, because we want to do the test for multiple environments. For an easier - workflow in this example here, however, we create a directory ``tests`` directly in the project. - -First of all, we have to create a new Balder environment in our project. For this we create a new Balder project -in our ``tests`` directory. We will create the following directory structure: - -.. code-block:: none - - - balderexample-loginserver/ - |- ... - |- tests - |- lib - |- __init__.py - |- connections.py - |- features.py - |- scenarios - |- __init__.py - -The ``lib`` directory contains important stuff we maybe want to reuse, like :ref:`Feature ` or -:ref:`Connection ` objects. The scenario module will contain our :ref:`Scenario ` class later. - -One submodule is still missing. We also need :ref:`Setup ` classes. We will add them later. + This tutorial is coming soon! It will be released on 25th of October. \ No newline at end of file diff --git a/doc/source/tutorial_guide/01_create_scenario.rst b/doc/source/tutorial_guide/01_create_scenario.rst deleted file mode 100644 index e4e0d445..00000000 --- a/doc/source/tutorial_guide/01_create_scenario.rst +++ /dev/null @@ -1,446 +0,0 @@ -Part 1: Develop a scenario -************************** - -First of all we have to think about the test scenarios we want to create for such a login app. There are a lot of -different aspects, that should be tested. For example scenarios for login are required, that expects a valid username -and also an invalid. The same procedure for passwords. We could test the registration process, while checking if all -required fields have to be given to create a valid account and also check if this newly created account works then. What -happens if we try to reset the password. Do we get a mail and does the reset process works as expected? You can see, -that there are a lot of different tests that should be created here. - -Let's begin with the typical situation of a client and server. The server offers a login page and an exclusive section -that requires authentication from the client before accessing it. Once authenticated, the client can enter this secure -area and is also able to logs out itself. - -Our test should look like the following: - -* check that we have no access to the internal page -* insert a valid username -* insert a valid password -* press submit -* check that we have access to the internal page -* logout -* check that we have no access to the internal page - -With this test we ensure, that it is possible to login with a valid username and a valid password. Then we can -access the internal page and also check that we can logout again. - -Sounds nice, so let's do it. - -Create the new scenario ------------------------ - -First of all we create a new file in our scenario submodule ``tests/scenarios/scenario_simple_loginout.py``. It is -required, that the file name starts with ``scenario_*``, because Balder only collects this files while it searches for -scenario files. - -In this newly created file, we have to create a new :class:`Scenario` class: - -.. code-block:: python - - import balder - - class ScenarioSimpleLoginOut(balder.Scenario): - ... - -You can name your class as you like, but it has to begin with ``Scenario*`` and has to inherit from the global Balder -class :class:`balder.Scenario`. - -Add a new test method ---------------------- - -Now we also want to add a test method in our newly created ``ScenarioSimpleLoginOut``. For this we have to add a method, -that starts with ``test_*``. - -.. code-block:: python - - import balder - - class ScenarioSimpleLoginOut(balder.Scenario): - - def test_valid_login_logout(self): - pass - -This is all. Now we have a valid scenario with an valid test method, but of course it still doesn't do something. -Before we can start to write our testcode, we have to create our devices. - -Add our devices ---------------- - -Devices are special test scenario members. In our case we will have a device, that represents the server or the -interface to the backend itself and a device that represents the client, which tries to login. - -So we add these two devices. We call them ``ServerDevice`` and ``ClientDevice``. - -.. code-block:: python - - import balder - - class ScenarioSimpleLoginOut(balder.Scenario): - - class ServerDevice(balder.Device): - pass - - class ClientDevice(balder.Device): - pass - - def test_valid_login_logout(self): - pass - -The device classes are always inner-classes of the scenario class, that uses the devices. In addition, they must inherit -from :class:`balder.Device`. - -Connect the devices -------------------- - -Now we have two devices which has no relationship to each other. But in the real world, we have a relationship here. -Both devices should be connected over a :class:`HttpConnection`. - -.. note:: - This is the first stage we can think about to create a more generic scenario, because the two devices can be - connected in every possible way to do an login process. You can also login over an :class:`SerialConnection` or - over a :class:`BluetoothConnection`. But for now we can use this :class:`HttpConnection`, we come back to this - generalization mechanism later. - -To connect two devices you can simply use the ``@balder.connect()`` decorator: - -.. code-block:: python - - import balder - import balder.connections as conn - - class ScenarioSimpleLoginOut(balder.Scenario): - - class ServerDevice(balder.Device): - pass - - @balder.connect(ServerDevice, conn.HttpConnection) - class ClientDevice(balder.Device): - pass - - def test_valid_login_logout(self): - pass - -.. note:: - Often it is easier to create the decorator on the second mentioned device, because python knows the reference only - to the devices that are defined above. As an alternative Balder also supports the mentioning of the other device - with a string reference. The following code is the same like the statement before: - - .. code-block:: python - - class ScenarioSimpleLoginOut(balder.Scenario): - - @balder.connect("ClientDevice", conn.HttpConnection) - class ServerDevice(balder.Device): - pass - - class ClientDevice(balder.Device): - pass - - def test_valid_login_logout(self): - pass - -.. note:: - Please note, that Balder currently only supports bidirectional connections. The support for non-bidirectional - connections will be added in a later version of Balder. - -Think about device features ---------------------------- - -With that we have created our scenario environment. We know which devices we need and how they are connected with each -other. But till now, these devices can not do something. They still have no functionality. We have to add some features -to them. - -So think about which features we need. Our server has to provide an address, we can connect with and a feature that -provides the backend. On the other side our client needs the functionality to connect with the server and send requests -to it. - -So let us introduce some features: - -* ``HasLoginSystemFeature``: this feature defines that the owner has a system where it is possible to connect with -* ``ValidRegisteredUserFeature``: this feature describes a user that is already registered in the backend system - -In addition to that we also need some features for our client device: - -* ``InsertCredentialsFeature``: this feature defines that the owner can login and logout to the backend of another - device -* ``ViewInternalPageFeature``: this feature defines that the owner can access the internal page of another device - - -Often it can be easier if we just write down, how we want to structure our scenario. For this just instantiate our -future feature classes inside our ``ScenarioSimpleLoginOut`` devices, even though we have not defined the feature -classes yet. We will add the necessary implementations and imports later. - -.. code-block:: python - - import balder - import balder.connections as conn - - class ScenarioSimpleLoginOut(balder.Scenario): - - class ServerDevice(balder.Device): - _autonomous = HasLoginSystemFeature() - user_credential = ValidRegisteredUserFeature() - - @balder.connect(ServerDevice, conn.HttpConnection) - class ClientDevice(balder.Device): - login_out = InsertCredentialsFeature(server=ServerDevice) - internal_page = ViewInternalPageFeature(server=ServerDevice) - - def test_valid_login_logout(self): - pass - -As you can see you can simply add :class:`Feature` classes to devices by instantiating and assigning them as class -attributes. - -.. note:: - Note that we have not defined the feature classes itself yet, but we have already instantiate it in the device - classes. This helps us to think clearer about the required feature methods we need later. If you like it in an other - order and first want to define the feature classes, of course you can firstly create the features too. - -We have added the ``HasLoginSystemFeature`` with the attribute name ``_autonomous``, that describes an -:ref:`Autonomous-Features`. This naming is not mandatory but recommended, because it has no properties or method you can -use. In short term an autonomous feature describes a feature that only identifies its owner with some functionality but -doesn't really provide methods. You can think about it as an property the device has, but you can not interact with it. -You can read more about autonomous features :ref:`here `. - -We are also able to define the imports for now even if we do not have the feature class definition yet. We will -implement all scenario features in our ``lib.features`` submodule that we have created before. So let us add the -imports for all of our features: - -.. code-block:: python - - import balder - import balder.connections as conn - from ..lib.features import HasLoginSystemFeature, ValidRegisteredUserFeature, InsertCredentialsFeature, ViewInternalPageFeature - - class ScenarioSimpleLoginOut(balder.Scenario): - - class ServerDevice(balder.Device): - _autonomous = HasLoginSystemFeature() - user_credential = ValidRegisteredUserFeature() - - @balder.connect(ServerDevice, conn.HttpConnection) - class ClientDevice(balder.Device): - login_out = InsertCredentialsFeature(server="ServerDevice") - internal_page = ViewInternalPageFeature(server="ServerDevice") - - def test_valid_login_logout(self): - pass - -Maybe you recognized the constructor argument ``server=ServerDevice`` for the ``InsertCredentialsFeature`` and the -``ViewInternalPageFeature``. This is a so called :ref:`VDevice mapping `. We will need -that for getting some server data without giving it over method arguments. It is quite enough to have the knowledge -that such a thing exists. We will dive a little deeper into this later. - -Write the testcase ------------------- - -Writing tests freestyle is often the most comfortable way to go about it. After the test is written, we can then add -the used feature classes and methods later on. This helps streamline the writing process, making it easier to get the -test down. - -So let's do it. Let us go back and read our scenario again: - -* check that we have no access to the internal page -* insert a valid username -* insert a valid password -* press submit -* check that we have access to the internal page -* logout -* check that we have no access to the internal page - -With this we now create the code for our test method: - -**Check that we have no access to the internal page:** - -.. code-block:: python - - # secure that we are not logged in - assert not self.ClientDevice.internal_page.check_internal_page_viewable(), \ - "can access internal data before user is logged in" - -**insert a valid username + password and press submit:** - -.. code-block:: python - - # get example user with a valid username and password - username, password = self.ServerDevice.user_credential.get_user() - - # insert the user data and execute the login command - self.ClientDevice.login_out.insert_username(username) - self.ClientDevice.login_out.insert_password(password) - assert self.ClientDevice.login_out.execute_login(), \ - "login does not work" - - -**check that we have access to the internal page:** - -.. code-block:: python - - # check that the internal page is viewable - assert self.ClientDevice.internal_page.check_internal_page_viewable(), \ - "can not access internal data after login" - -**logout:** - -.. code-block:: python - - # now log out user - assert self.ClientDevice.login_out.execute_logout(), \ - "logout does not work" - -**check that we have no access to the internal page:** - -.. code-block:: python - - # check that we can not access the internal page after user is logged out - assert not self.ClientDevice.internal_page.check_internal_page_viewable(), \ - "can access internal data after user was logged out" - -The final scenario ------------------- - -Now let's take a look how the full scenario looks like. For this we take a look at the complete code. - -.. code-block:: python - - import balder - import balder.connections as conn - from ..lib.features import HasLoginSystemFeature, ValidRegisteredUserFeature, InsertCredentialsFeature, ViewInternalPageFeature - - class ScenarioSimpleLoginOut(balder.Scenario): - - class ServerDevice(balder.Device): - _autonomous = HasLoginSystemFeature() - user_credential = ValidRegisteredUserFeature() - - @balder.connect(ServerDevice, conn.HttpConnection) - class ClientDevice(balder.Device): - login_out = InsertCredentialsFeature(server="ServerDevice") - internal_page = ViewInternalPageFeature(server="ServerDevice") - - def test_valid_login_logout(self): - # secure that we are not logged in - assert not self.ClientDevice.internal_page.check_internal_page_viewable(), \ - "can access internal data before user is logged in" - - # get example user with a valid username and password - username, password = self.ServerDevice.user_credential.get_user() - - # insert the user data and execute the login command - self.ClientDevice.login_out.insert_username(username) - self.ClientDevice.login_out.insert_password(password) - assert self.ClientDevice.login_out.execute_login(), \ - "login does not work" - - # check that the internal page is viewable - assert self.ClientDevice.internal_page.check_internal_page_viewable(), \ - "can not access internal data after login" - - # now log out user - assert self.ClientDevice.login_out.execute_logout(), \ - "logout does not work" - - # check that we can not access the internal page after user is logged out - assert not self.ClientDevice.internal_page.check_internal_page_viewable(), \ - "can access internal data after user was logged out" - -That was it. This is the complete scenario code for testing a general authentication process. But for now -we don't have a real implementation for all the feature methods. So let us go to define them too. - - -Define the features -------------------- - -We have already imported the features from our submodule ``test.lib.features``. Now we want to add them in these module -too: - -.. code-block:: python - - # file tests/lib/features.py - - import balder - - class HasLoginSystemFeature(balder.Feature): - pass - - class ValidRegisteredUserFeature(balder.Feature): - pass - - class InsertCredentialsFeature(balder.Feature): - pass - - class ViewInternalPageFeature(balder.Feature): - pass - - -We can add our previously used methods here too: - - -.. code-block:: python - - # file tests/lib/features.py - - import balder - - - class HasLoginSystemFeature(balder.Feature): - pass - - - class ValidRegisteredUserFeature(balder.Feature): - - def get_user() -> Tuple[str, str]: - raise NotImplementedError("this method has to be implemented on setup level") - - - class InsertCredentialsFeature(balder.Feature): - - class Server(balder.VDevice): - # our vDevice we have mapped earlier (we will come back to this later) - it only - # instantiates the autonomous feature - _ = HasLoginSystemFeature() - - def insert_username(self, username: str): - raise NotImplementedError("this method has to be implemented on setup level") - - def insert_password(self, password: str): - raise NotImplementedError("this method has to be implemented on setup level") - - def execute_login(self) -> bool: - raise NotImplementedError("this method has to be implemented on setup level") - - - def execute_logout(self) -> bool: - raise NotImplementedError("this method has to be implemented on setup level") - - - - class ViewInternalPageFeature(balder.Feature): - - class Server(balder.VDevice): - # our vDevice we have mapped earlier (we will come back to this later) - it only - # instantiates the autonomous feature - _ = HasLoginSystemFeature() - - def check_internal_page_viewable(self) -> bool: - raise NotImplementedError("this method has to be implemented on setup level") - - -When creating scenarios, it is often the case that only the interfaces are provided and not the implementation, as the -implementation depends heavily on the real setup. In these cases, we typically add abstract methods and properties. -However, it is still possible to provide some implementations in certain scenarios. The same applies here, which is why -we make our methods abstract by adding ``NotImplementedError`` everywhere. - -.. note:: - If you are writing BalderHub projects or if you are creating common scenarios that are used from other people - it is highly recommended to add nice comments of all the classes and methods. In addition to that it is highly - recommended to use type definitions. This makes the code more readable and nice structured. If you take a look in - the example of this code in the - `balder github repository `_ - you find these comments and type definitions, for the sake of clarity, however, we have not done it here in the - example code. - -Now we have successfully implemented the scenario. In the next session we will add a setup and execute Balder the first -time. \ No newline at end of file diff --git a/doc/source/tutorial_guide/01_develop_first_test.rst b/doc/source/tutorial_guide/01_develop_first_test.rst new file mode 100644 index 00000000..c9ab841f --- /dev/null +++ b/doc/source/tutorial_guide/01_develop_first_test.rst @@ -0,0 +1,6 @@ +Part 1: Develop a Login Test from Scratch +***************************************** + + +.. note:: + This tutorial is coming soon! It will be released on 25th of October. \ No newline at end of file diff --git a/doc/source/tutorial_guide/02_install_first_web_tests.rst b/doc/source/tutorial_guide/02_install_first_web_tests.rst new file mode 100644 index 00000000..948ac5c1 --- /dev/null +++ b/doc/source/tutorial_guide/02_install_first_web_tests.rst @@ -0,0 +1,6 @@ +Part 2: Install Tests for NextCloud Web +*************************************** + + +.. note:: + This tutorial is coming soon! It will be released on 25th of October. diff --git a/doc/source/tutorial_guide/02_single_setup.rst b/doc/source/tutorial_guide/02_single_setup.rst deleted file mode 100644 index 8442513a..00000000 --- a/doc/source/tutorial_guide/02_single_setup.rst +++ /dev/null @@ -1,803 +0,0 @@ -Part 2: Implement browser setup -******************************* - -Before we start to implement our first setup, let's take a look to the login-page. If you -:ref:`start the development server ` and open the login page with a browser of your choice -you will see the login page. - -.. image:: ../_static/balderexample-loginserver-login.png - -We want to create a setup that allows to test this login screen now. - -Install mechanize -================= - -We need a package that allows us to control a Browser window. For this we use the -`python package mechanize `_, which is a stateful programmatic web browsing -python package, that allows to fill simple forms and clicking links of a html page. - -We can simply install the python package with the command: - -.. code-block:: - - >>> pip install mechanize - -Create the new setup -==================== - -We want to create a setup for our scenario that allows us to login over the mentioned web interface. First we have to -create a new module `setups`. This can be done, by creating a new directory ``setups`` and add a ``__init__`` -file into it. In this directory we also add our new setup file ``setup_web_browser.py``. This results in the following -new directory structure: - -.. code-block:: none - - - balderexample-loginserver/ - |- ... - |- tests - |- lib - |- __init__.py - |- features.py - |- connections.py - |- scenarios - |- __init__.py - |- scenario_simple_loginout.py - |- setups - |- __init__.py - |- setup_web_browser.py - -We can now create the initial setup class in our newly created file: - -.. code-block:: python - - # file tests/setups/setup_web_browser.py - import balder - - class SetupWebBrowser(balder.Setup): - - pass - -We want to create a setup, that matches with our scenario, we have created in -:ref:`part 1 `. In our scenario we defined two devices we need for the execution. These two -devices are needed in our setup too. - -Think about devices -------------------- - -For a setup to match a scenario, there has to be a variation between its devices that matches. A setup device matches -with a scenario device if it implements at least all features (by inheriting from them) and fulfill the connections. - -.. note:: - This also means that a setup can have devices or features that are not mentioned in the scenario. - -So, according to our developed scenario we have developed in :ref:`part 1 `, we need two -devices that implements the following features: - -``MechanizeClient``: - -* ``InsertCredentialsFeature`` -* ``ViewInternalPageFeature`` - - -``Loginserver``: - -* ``HasLoginSystemFeature`` (autonomous feature) -* ``ValidRegisteredUserFeature`` - -These features have to be available in our setup so that Balder allows our newly created ``SetupWebBrowser`` as matching -candidate for our scenario. Except for the ``HasLoginSystemFeature`` all other features are abstract features, that has -no implementation yet. - -**Balder-Matching-Mechanism:** - -In the solving process, Balder determines all combinations a setup and scenario could be mapped. It also creates sub -combinations about how their devices could be combined with each other. During this initial mapping, compatibility -between the setup and scenario is not necessarily checked. - -However, before the variation is executed, Balder takes the extra step of verifying that the current variation is valid. -This means that the setup candidate must have a device that implements all the features (inherits from them) of the -current mapped scenario device in order for the variation to be accepted. In addition to that, also the defined -scenario-device-connections have to BE IN the related connections of the setup devices. - -If you want to learn more about, how Balder works, check out the :ref:`Balder Execution mechanism`. - -Autonomous feature ------------------- - -The autonomous feature ``HasLoginSystemFeature`` is a feature that doesn't contain some functional code, it simply -stands for a functionality the device has, but we do not interact with it. For example that the device has the -color red or in our case, that the device **has a login feature**. For this we have not to provide a special -implementation, we can simply add it to every device that has this feature. - -You can find more about autonomous features :ref:`here `. - -Abstract features ------------------ - -Most of the features we have implemented so far are abstract and have no implementation or at least have no complete -implementation. This is often the case for scenario features, because it simply can't be provided. In many cases, it is -impossible to provide the full implementation on scenario-level because the situation does not permit such knowledge. -Take for example a reset function; without understanding what needs to be reset there, it is hard to really implement -the full feature. We use the scenario-level feature to provide the interface or in pythonic words, the abstract methods, -which define **what we need**. - -Therefore, in Balder, the implementation is often done at the setup-level. - -For this we add a new file `features` in the `setups` directory: - -.. code-block:: none - - - balderexample-loginserver/ - |- ... - |- tests - ... - |- setups - |- __init__.py - |- features.py - |- setup_web_browser.py - -This newly created file ``features.py`` should contain our specific feature implementations for the browser controllable -login. - -.. note:: - In :ref:`Part 3: Expand setup code` we will expand this and use real hierarchy structured setup-feature code, - but for now this is quite sufficient. - -.. note:: - Please note, that the structure described here is not the be-all and end-all, but it makes often sense to capsule - the features in specific namespace areas, like shown in this tutorial. - -Add the devices ---------------- - -In the same way we have developed our scenario in :ref:`part 1 `, we add the features -before we really implement it. For an easier understanding, we use a simple name format for the features we will -overwrite in our setup area. Every of these features will be named like ``My``. - -.. note:: - If you're developing a real test project, it's a good idea to think about encapsulating your features in their own - namespaces. For example, you could create a file for all setup features and import them using - ``from project.setups import setup_features``. This approach will make it much easier to keep track of all the - features in your project, as well as making it more organized and easier to maintain. It will also make it easier - to add new features in the future. - -We will already add the import statement even if we don't have an implementation yet. Often this helps to get a clearer -imagine about the things we need. We will import these features from the setup feature file ``setups/features.py``, that -we have created recently. - -``Loginserver``: - -* ``HasLoginSystemFeature`` (autonomous feature, can be imported directly from SCENARIO-LEVEL lib folder) -* ``MyValidRegisteredUserFeature`` (specific setup feature) - - -``MechanizeClient``: - -* ``MyInsertCredentialsFeature`` (specific setup feature) -* ``MyViewInternalPageFeature`` (specific setup feature) - -We want to connect the two devices exactly in the same way as in the scenario. So we only use a simply -``HttpConnection``: - - -.. code-block:: python - - import balder - # we can directly import the autonomous feature - from ..lib.features import HasLoginSystemFeature - # all new features has to be imported from the global setup feature file, where we will define them later - from .features import MyValidRegisteredUserFeature, MyInsertCredentialsFeature, MyViewInternalPageFeature - - class SetupWebBrowser(balder.Setup): - - class Server(balder.Device): - _ = HasLoginSystemFeature() - valid_user = MyValidRegisteredUserFeature() - - @balder.connect(ServerDevice, conn.HttpConnection) - class Client(balder.Device): - credentials = MyInsertCredentialsFeature() - internal = MyViewInternalPageFeature() - -As you can see, the devices directly inherit from the basic Balder device and not from the scenario device. -Balder manage this automatically. Balder doesn't really care for the device class, because it only exchange the -features of it, but does not change the device itself. - -What's about the vDevices? --------------------------- - -As you have seen in our :ref:`scenario definition `, we uses vDevices on scenario-level. To -understand why we use them, let's check the earlier used scenario code again. Our scenario device definition looks like -the following: - -.. code-block:: python - - import balder - import balder.connections as conn - - class ScenarioSimpleLoginOut(balder.Scenario): - - class ServerDevice(balder.Device): - ... - - @balder.connect(ServerDevice, conn.HttpConnection) - class ClientDevice(balder.Device): - login_out = InsertCredentialsFeature(Server="ServerDevice") - ... - -In the ``InsertCredentialsFeature`` constructor, that is used by the ``ClientDevice`` we have defined a mapping between -our vDevice ``Server`` and our real device ``ServerDevice``. With this, we tell Balder that we want to use the -``Server`` vDevice and that our real device ``ServerDevice`` should be mapped to it, thus representing it. - -By instantiating own features inside the vDevice, we define, that Balder should ensure that our mapped device (in our -case ``ServerDevice``) also provides an implementation for them. - -You can see this definition also inside the feature ``InsertCredentialsFeature``: - -.. code-block:: python - - # file tests/lib/features.py - - ... - - class InsertCredentialsFeature(balder.Feature): - - class Server(balder.VDevice): - _ = HasLoginSystemFeature() - - ... - - ... - -You can see that our mapped vDevice ``Server``, needs a connection to a device that at least implements the -``HasLoginSystemFeature``. However, since this is only an autonomous feature, we just define that our peer device has to -provide this autonomous feature too. It simply doesn't make sense to use this feature without -another device that provides this interface. - -.. note:: - Of course you can also add normal features to your vDevices. By adding real normal features, you can access the - features of the mapped peer device over these vDevices. This makes it possible that you can request configurations - from a peer device inside a feature. - - -This vDevice-Device mapping also affects our setup, but we don't have to define the mapping again in the setup. It will -automatically secured by the device mapping algorithm. - -.. note:: - It is also possible to assign a vDevice in the setup. - -This vDevice mechanism is very powerful. You are also able to define different methods for different mapped vDevices. If -you want to find out more about that, check the section :ref:`VDevices and method-variations`. - -Implement the setup-features -============================ - -Now let us implement the different features we have already imported. Open the file ``setups/features.py`` and add the -basic code. Secure that you inherit from the parent classes of the scenario level. With inheritance Balder secures that -a feature belongs to another. We also add the abstract methods, we have defined earlier that are filled with an -``NotImplementedError``. We will provide the full implementation of our methods there later: - -.. code-block:: python - - import balder - from ..lib.features import InsertCredentialsFeature, ViewInternalPageFeature, BrowserSessionManagerFeature, \ - ValidRegisteredUserFeature - - - # Server features - class MyValidRegisteredUserFeature(ValidRegisteredUserFeature): - def get_valid_user(self): - pass - - # Client features - - class MyInsertCredentialsFeature(InsertCredentialsFeature): - - class Server(InsertCredentialsFeature.Server): - pass - - - def insert_username(self, username): - pass - - def insert_password(self, password): - pass - - def execute_login(self): - pass - - def execute_logout(self): - pass - - - class MyViewInternalPageFeature(ViewInternalPageFeature): - - class Server(ViewInternalPageFeature.Server): - pass - - def check_internal_page_viewable(self): - pass - -As you can see, we have overwritten the internal empty vDevices here too, because we will add some more features there -later. You can add features to a vDevice by overwriting the inner class with the same name of the vDevice class and -inheriting from the next parent. This is done here for the features ``MyInsertCredentialsFeature`` and -``MyViewInternalPageFeature``. - -Client feature --------------- - -We want to start with the method ``MyValidRegisteredUserFeature.get_user()``. This method should return a tuple with the -username and the password. According to the -`README.md file of the balderexample-loginserver repository `_, -the server provides static credentials: - -Username: ``guest`` -Password: ``guest12345`` - -We simply add a return statement with these values: - -.. code-block:: python - - ... - - class MyValidRegisteredUserFeature(ValidRegisteredUserFeature): - - def get_user() -> Tuple[str, str]: - return "guest", "guest12345" - - ... - -That was easy, wasn't it? So lets get a little bit deeper. - -Reference a feature from another --------------------------------- - -The ``mechanize`` package allows accessing the browser content with a so called ``mechanize.Browser`` object. After -instantiating, you can browse through a website while it handles all session stuff for us. For using it, we should -instantiate it only once. - -In our client device, we have two features to implement, the ``MyInsertCredentialsFeature`` and the -``MyViewInternalPageFeature``. Both of them must have access to the same browser. - -With that, we need the same ``Browser`` object for the whole test session, but for us it seems hard to share this object -between different feature instances. We can not add it to the constructor or something similar. But how can we share -this? - -We can use a shared feature, that is referred in our both feature classes -``MyInsertCredentialsFeature`` and ``MyViewInternalPageFeature``. - -.. note:: - - We will add this shared feature only in setup code. The scenario implementation hasn't changed, it does not know - anything about a browser object. This allows us to create other setups that do not implement our specific mechanize - feature. - - With that we are really flexible, because we can provide different implementations for the same scenario-features on - setup-level. - -Let's call this feature ``BrowserSessionManagerFeature``. It should completely manage this browser object and also -provide some methods, we can interact with. - -We add this feature to our file ``setups/features.py`` too. Because this feature is new, we can directly inherit from -``balder.Feature`` and don't need some inheritance from the SCENARIO LEVEL: - -.. code-block:: python - - class BrowserSessionManagerFeature(balder.Feature): - # our mechanize browser object that simulates the browser - browser = None - -First of all we add a property ``browser``, which should be managed by some methods. Two methods are enough for our -application. We will add a method ``create_browser_if_necessary()`` which should create a browser only if there was no -browser object generated before and a method ``open_page()`` that opens a url. For the implementation we have to add -some simple mechanize code. - -.. code-block:: python - - class BrowserSessionManagerFeature(balder.Feature): - # our mechanize browser object that simulates the browser - browser = None - - def create_browser_if_necessary(self): - if self.browser is None: - self.browser = mechanize.Browser() - - def open_page(self, open_page_url=None): - return self.browser.open(open_page_url) - -That's all. But how can we use this feature in our features ``MyInsertCredentialsFeature`` and -``MyViewInternalPageFeature``, that both needing access to it. That is really easy, simply instantiate it as static -class property in the features that want to use it. For example, this can look like the following code: - -.. code-block:: python - - class MyViewInternalPageFeature(ViewInternalPageFeature): - - ... - - browser_manager = BrowserSessionManagerFeature() - - ... - -This allows you to simply refer it from your methods. It also defines that every device that uses the feature -`MyViewInternalPageFeature` (by defining it as static attribute), has also implement the `BrowserSessionManagerFeature`. - -Implement the client setup-features ------------------------------------ - -As you may remember the setup features ``MyInsertCredentialsFeature`` and ``MyViewInternalPageFeature`` (which we still -have to implement) have a vDevice ``Server`` in our scenario implementation. But on this scenario level implementation, -the vDevice has only the one autonomous feature ``HasLoginSystemFeature``. - -We have written a very universal scenario-level feature, which is often a good decision. This allows a very flexible -scenario implementation. But now on setup-level, we need some more information from our communication partner device -that is mapped to the vDevice ``MyInsertCredentialsFeature.Server``. - -Balder allows us to access these information by simply specifying the features that provide this info in our vDevice. -As mentioned earlier, we can overwrite a vDevice, by inheriting from the vDevice of the parent feature class **and** -give the same class name to the child vDevice class: - -.. code-block:: python - - class MyViewInternalPageFeature(ViewInternalPageFeature): - - class Server(ViewInternalPageFeature.Server): - pass - - browser_manager = BrowserSessionManagerFeature() - -.. note:: - Note that it is really important, that the child vDevice class has the same name that is given in the parent feature - class! Otherwise the child vDevice will be interpreted as a new vDevice! In this case this will produce an exception - because Balder only allows the redefining of inner devices by overwriting them all on one class level. - -In a few moments, we will create a new feature class ``InternalWebpageFeature`` that should return some constant values -about the server (for example the webpage url). This feature should be implemented by our real server device. We can -ensure this on feature level, by adding this feature to our vDevice ``Server``: - -.. code-block:: python - - class MyViewInternalPageFeature(ViewInternalPageFeature): - - class Server(ViewInternalPageFeature.Server): - internal_webpage = InternalWebpageFeature() - - browser_manager = BrowserSessionManagerFeature() - -Just as we have already done with normal devices, we can address our feature in the vDevice, by using its property. So -let us add an implementation for our abstract method ``ViewInternalPageFeature.check_internal_page_viewable()`` by -using our new vDevice-Feature: - -.. code-block:: python - - class MyViewInternalPageFeature(ViewInternalPageFeature): - - class Server(ViewInternalPageFeature.Server): - internal_webpage = InternalWebpageFeature() - - browser_manager = BrowserSessionManagerFeature() - - def check_internal_page_viewable(self): - self.browser_manager.create_browser_if_necessary() - self.browser_manager.open_page(self.Server.internal_webpage.url) - if self.browser_manager.browser.title() != self.Server.internal_webpage.title: - # redirect to another webpage -> not able to read the internal webpage - return False - return True - - -We will do the same for the other feature and also add another feature ``LoginWebpageFeature`` (which we will also -implement in a few moments) to its vDevice: - -.. code-block:: python - - class MyInsertCredentialsFeature(InsertCredentialsFeature): - - class Server(InsertCredentialsFeature.Server): - login_webpage = LoginWebpageFeature() - internal_webpage = InternalWebpageFeature() - - browser_manager = BrowserSessionManagerFeature() - setup_done = False - - def do_setup_if_necessary(self): - if not self.setup_done: - self.browser_manager.create_browser_if_necessary() - self.browser_manager.open_page(self.Server.login_webpage.url) - self.setup_done = True - - def insert_username(self, username): - self.do_setup_if_necessary() - # now insert the username - self.browser_manager.browser.select_form(name=self.Server.login_webpage.dom_name_login_form) - self.browser_manager.browser[self.Server.login_webpage.dom_name_username_field] = username - - def insert_password(self, password): - self.do_setup_if_necessary() - # now insert the password - self.browser_manager.browser.select_form(name=self.Server.login_webpage.dom_name_login_form) - self.browser_manager.browser[self.Server.login_webpage.dom_name_password_field] = password - - def execute_login(self): - response = self.browser_manager.browser.submit() - return response.wrapped.code == 200 - - def execute_logout(self): - response = self.browser_manager.open_page(self.Server.internal_webpage.url_logout) - return response.wrapped.code == 200 - -We will implement the newly created ``LoginWebpageFeature`` in the ``Server`` vDevice in the next stage. - -.. note:: - The overwritten feature also implements a new method ``do_setup_if_necessary()``. This is no problem, even if it is - not defined in the parent feature. In the normal way like inheriting works, you can freely implement more logic in - child classes. - -Implement the vDevice features ------------------------------- - -We have created some new features that we need specially for this setup, the ``LoginWebpageFeature`` and -``InternalWebpageFeature``. We only use some constants here. Let's define these features. - -.. code-block:: python - - class LoginWebpageFeature(balder.Feature): - @property - def url(self): - return "http://localhost:8000/accounts/login" - - @property - def dom_name_login_form(self): - return "login" - - @property - def dom_name_username_field(self): - return "username" - - @property - def dom_name_password_field(self): - return "password" - - - class InternalWebpageFeature(balder.Feature): - @property - def url(self): - return "http://localhost:8000" - - @property - def title(self): - return "Internal" - - @property - def url_logout(self): - return "http://localhost:8000/accounts/logout" - - -.. note:: - **We instantiate every feature multiple times, why do we think they are synchronized?** - - Before a variation (fixed mapping between scenario and setup devices) will be executed, Balder automatically - exchanges all objects with the original objects that were instantiated in the setup. Everywhere! In all inner - feature references (also feature properties that are other instantiated features), scenarios, vDevices and so on. - -Update our setup ----------------- - -Our setup can not be resolved yet, because our server device does not have the vDevice features we have defined. For -this we have to add them. - -Our new setup devices should implement the following: - -.. code-block:: python - - import balder - from tests.lib.features import HasLoginSystemFeature - from tests.setups import features as setup_features - - class SetupWebBrowser(balder.Setup): - - class Server(balder.Device): - _ = HasLoginSystemFeature() - login_webpage = setup_features.LoginWebpageFeature() - internal_webpage = setup_features.InternalWebpageFeature() - valid_user = setup_features.MyValidRegisteredUserFeature() - - class Client(balder.Device): - browser_manager = setup_features.BrowserSessionManagerFeature() - credentials = setup_features.MyInsertCredentialsFeature() - internal = setup_features.MyViewInternalPageFeature() - - - -The whole setup and its features -================================ - -Done, we have successfully implement our setup. The whole code is shown below, but you can find the code -in the `single-setup branch on GitHub `_ too. - -.. code-block:: python - - # file tests/setups/features.py - import balder - from ..lib.features import InsertCredentialsFeature, ViewInternalPageFeature, BrowserSessionManagerFeature, \ - ValidRegisteredUserFeature - - - # Server features - class MyValidRegisteredUserFeature(ValidRegisteredUserFeature): - def get_valid_user(self): - return "guest", "guest12345" - - - class LoginWebpageFeature(balder.Feature): - @property - def url(self): - return "http://localhost:8000/accounts/login" - - @property - def dom_name_login_form(self): - return "login" - - @property - def dom_name_username_field(self): - return "username" - - @property - def dom_name_password_field(self): - return "password" - - - class InternalWebpageFeature(balder.Feature): - @property - def url(self): - return "http://localhost:8000" - - @property - def title(self): - return "Internal" - - @property - def url_logout(self): - return "http://localhost:8000/accounts/logout" - - - # Client features - - class MyInsertCredentialsFeature(InsertCredentialsFeature): - class Server(InsertCredentialsFeature.Server): - login_webpage = LoginWebpageFeature() - internal_webpage = InternalWebpageFeature() - - browser_manager = BrowserSessionManagerFeature() - setup_done = False - - def do_setup_if_necessary(self): - if not self.setup_done: - self.browser_manager.create_browser_if_necessary() - self.browser_manager.open_page(self.Server.login_webpage.url) - self.setup_done = True - - def insert_username(self, username): - self.do_setup_if_necessary() - # now insert the username - self.browser_manager.browser.select_form(name=self.Server.login_webpage.dom_name_login_form) - self.browser_manager.browser[self.Server.login_webpage.dom_name_username_field] = username - - def insert_password(self, password): - self.do_setup_if_necessary() - # now insert the password - self.browser_manager.browser.select_form(name=self.Server.login_webpage.dom_name_login_form) - self.browser_manager.browser[self.Server.login_webpage.dom_name_password_field] = password - - def execute_login(self): - response = self.browser_manager.browser.submit() - return response.wrapped.code == 200 - - def execute_logout(self): - response = self.browser_manager.open_page(self.Server.internal_webpage.url_logout) - return response.wrapped.code == 200 - - - class MyViewInternalPageFeature(ViewInternalPageFeature): - class Server(ViewInternalPageFeature.Server): - internal_webpage = InternalWebpageFeature() - - browser_manager = BrowserSessionManagerFeature() - - def check_internal_page_viewable(self): - self.browser_manager.create_browser_if_necessary() - self.browser_manager.open_page(self.Server.internal_webpage.url) - if self.browser_manager.browser.title() != self.Server.internal_webpage.title: - # redirect to another webpage -> not able to read the internal webpage - return False - return True - - -.. code-block:: python - - # file tests/setups/setup_web_browser.py - import balder - from tests.lib.features import HasLoginSystemFeature - from tests.setups import features as setup_features - - class SetupWebBrowser(balder.Setup): - - class Server(balder.Device): - _ = HasLoginSystemFeature() - login_webpage = setup_features.LoginWebpageFeature() - internal_webpage = setup_features.InternalWebpageFeature() - valid_user = setup_features.MyValidRegisteredUserFeature() - - class Client(balder.Device): - browser_manager = setup_features.BrowserSessionManagerFeature() - credentials = setup_features.MyInsertCredentialsFeature() - internal = setup_features.MyViewInternalPageFeature() - -Execute Balder -============== - -Now is the time to execute Balder and take advantage of the benefits it provides. We have a single setup, as well as a -single scenario, where every setup device is mapped to a scenario device, ensuring that each setup device implements -at least the features of its mapped scenario device. Therefore, it is expected that Balder will find exactly one valid -executable variation. - -Let's take a look how Balder will resolve our project without really executing it. For this you can add the argument -``--resolve-only`` to the ``balder`` call: - -.. code-block:: - - $ balder --working-dir tests --resolve-only - -.. code-block:: none - - +----------------------------------------------------------------------------------------------------------------------+ - | BALDER Testsystem | - | python version 3.9.7 (default, Sep 3 2021, 12:37:55) [Clang 12.0.5 (clang-1205.0.22.9)] | balder version 0.0.1 | - +----------------------------------------------------------------------------------------------------------------------+ - Collect 1 Setups and 1 Scenarios - resolve them to 1 mapping candidates - - RESOLVING OVERVIEW - - Scenario `ScenarioSimpleLoginOut` <-> Setup `SetupWebBrowser` - ScenarioSimpleLoginOut.ClientDevice = SetupWebBrowser.Client - ScenarioSimpleLoginOut.ServerDevice = SetupWebBrowser.Server - -> Testcase - - -Great, the mapping works. Balder finds the valid variation. - -Now it is time to really run the Balder session. - -.. note:: - Do not forget to start the django server before: - - .. code-block:: none - - $ python manage.py runserver - -After you have secured that the django server will be executed, you can start Balder with the command: - -.. code-block:: - - $ balder --working-dir tests - -.. code-block:: none - - +----------------------------------------------------------------------------------------------------------------------+ - | BALDER Testsystem | - | python version 3.9.5 (default, Nov 23 2021, 15:27:38) [GCC 9.3.0] | balder version 0.0.1 | - +----------------------------------------------------------------------------------------------------------------------+ - Collect 1 Setups and 1 Scenarios - resolve them to 1 mapping candidates - - ================================================== START TESTSESSION =================================================== - SETUP SetupWebBrowser - SCENARIO ScenarioSimpleLoginOut - VARIATION ScenarioSimpleLoginOut.ClientDevice:SetupWebBrowser.Client | ScenarioSimpleLoginOut.ServerDevice:SetupWebBrowser.Server - TEST ScenarioSimpleLoginOut.test_valid_login_logout [✓] - ================================================== FINISH TESTSESSION ================================================== - TOTAL NOT_RUN: 0 | TOTAL FAILURE: 0 | TOTAL ERROR: 0 | TOTAL SUCCESS: 1 | TOTAL SKIP: 0 | TOTAL COVERED_BY: 0 - - -Congratulations! You have successfully run your first test with Balder. \ No newline at end of file diff --git a/doc/source/tutorial_guide/03_double_setup.rst b/doc/source/tutorial_guide/03_double_setup.rst deleted file mode 100644 index 9be443c9..00000000 --- a/doc/source/tutorial_guide/03_double_setup.rst +++ /dev/null @@ -1,498 +0,0 @@ -Part 3: Expand setup code -************************* - -In this part we are going to learn one of the key concepts of Balder - reusing tests. - -This essential concept allows you to effortlessly utilize existing scenarios for various setups. Many projects require -similar testing in multiple ways, whether it's supporting a user interface on different platforms or managing a device -over several apps. Perhaps you need to test a bus system where every member must function identically. Balder was -designed precisely for this purpose. - - -There are a lot of different ways to expand our testing environment. If we have a new device, but inside the same -environment, we just need to add this device to the setup. If we just want to add a new feature to an already existing -device, we can just add it inside our setup. And of course, if we have a complete other environment, we can simply -create a complete new setup. - -In this section, we'll add a new setup class. - -How the all-in-one setup approach works is described in :ref:`Part 5: One common setup`. - -So let's go back to our example and restructure our earlier created project a little bit. - - -Prepare for the new setup -========================= - -In :ref:`part 2 ` we have already implemented a setup for our login server, that uses -the web frontend to login to our internal page. Often we also need an API/REST interface to connect with internal -components. Our loginserver also supports this. We want to add a new setup for this method now. - -First of all, think about the features we need for it. - -Think about the devices ------------------------ - -Similar to :ref:`part 2 ` we could think about the required features we need for our -new setup class. If we go back to the scenario definition we need the following features: - -``Loginserver``: - -* ``HasLoginSystemFeature`` (autonomous feature) -* ``ValidRegisteredUserFeature`` - -``RestClient`` - -* ``InsertCredentialsFeature`` -* ``ViewInternalPageFeature`` - -As you can see we need the same features like we have used earlier in our first setup. Of course we need the same one, -because we still have the same procedure. Similar to the webpage login, we need the procedure defined in -:ref:`part 1 `: - -* check that we have no access to the internal page/data -* insert a valid username -* insert a valid password -* press submit/send request -* check that we have access to the internal page/data -* logout (over request) -* check that we have no access to the internal page/data - -The procedure is the same, we only must do it a little bit different. With that we don't have to change the scenario and -also we can still use some similar setup features. So with the first step, we want to refactor our setup directory to -reuse some setup feature classes. - -Refactor our setup directory ----------------------------- - -We want to sort our setup classes into individual directories. We create a new directory ``setups/browser`` and move -the ``setup_web_browser.py`` file into it. In addition to that we rename our ``setups/features.py`` file to -``setup_features.py``. For this changes, we have to update our import statements too. Our new refactored directory -should now looks like the following: - -.. code-block:: none - - - balderexample-loginserver/ - |- ... - |- tests - ... - |- setups - |- browser - |- __init__.py - |- setup_web_browser.py - |- __init__.py - |- setup_features.py - -Now we want to organize our features of the old setup. All features usable without a browser should be placed -in the ``setup_features.py`` file. All specific features that are implemented to test the login with the -browser should be placed in a separate feature file ``setups/browser/browser_features.py``. - -So our new structure looks like the following: - -.. code-block:: none - - - balderexample-loginserver/ - |- ... - |- tests - ... - |- setups - |- browser - |- __init__.py - |- browser_features.py - |- setup_web_browser.py - |- __init__.py - |- setup_features.py - -.. note:: - As you can see, it could be helpful to organize your feature inside own namespace modules. Of course you can - organize your project in the structure of your choice. You can also name it in the way you want, - but it is highly recommended to use a name to easily distinguish the source of an imported setup. If you name every - file ``features.py`` this can get really hard to understand, specially when you import from different directory - levels, like it is showed below. - - .. code-block:: python - - from .features import X, Y - from ..features import P, Z - ... - - Its easier if you rename the files, like we have done above: - - .. code-block:: python - - from . import browser_features - from .. import setup_features - ... - - class SetupExample(balder.Setup): - - class Browser(balder.Device): - glob = setup_features.GlobFeature() - browser = browser_features.SpecialBrowserFeature() - ... - -So think about which features are global and which features are special browser features. If you take a look into -our file ``setup_features.py`` you should find the following features: - -* ``MyValidRegisteredUserFeature``: This feature provides the user credentials valid for the whole ``balderexample-loginserver``. The user is valid for all access strategies. -* ``LoginWebpageFeature``: This feature provides all specific data of the login front-end webpage. -* ``InternalWebpageFeature``: This feature provides all specific data of the internal front-end webpage. -* ``MyInsertCredentialsFeature``: This feature allows inserting credentials into a login system. -* ``MyViewInternalPageFeature``: This feature allows the owner device to interact with the internal area provided by the vDevice. - -The first feature ``MyValidRegisteredUserFeature`` returns the global valid credentials to access the login area in -every possible way. This feature is not limited to the browser method, so we can left it in the higher file -``setups/setup_features.py``. All the other features, however, are specific, so we move them to the browser specific -file ``setups/browser/setup_web_browser.py``. - -Implement the REST setup -======================== - -Ok so we have redesigned our environment now. It is time to add a new setup. The ``balderexample-loginserver`` package -also provides a REST api, that allows us to request all existing users, but of course only if we are logged in. - -We want to create a setup that allows us to request all registered users. For this we can ask the -endpoint ``/api/users``. But this endpoint contains sensitive data, so it is behind an authentication system. Our -server uses a standard authentication system ``Basic Authentication`` that requires the same username and password as -credentials, we also have used in the browser setup before. We can use the python library ``requests``, which -allows us easily to execute a GET request with ``Basic Authentication``. - -Install requests ----------------- - -For testing our API, we use the python package ``requests``. Make sure that you have installed it. - -.. code-block:: - - >>> pip install requests - -Add the new file ----------------- - -First of all, we want to create the new file. We are adhering to our new structure and add a new module in our -setup directory first. We can name it ``setups/rest``. There we add a new file ``rest_features.py`` for our rest -specific features and a new ``setup_rest_basicauth.py``, which will contain our setup implementation. Our directory -should look like the following: - -.. code-block:: none - - - balderexample-loginserver/ - |- ... - |- tests - ... - |- setups - |- browser - |- __init__.py - |- browser_features.py - |- setup_web_browser.py - |- rest - |- __init__.py - |- rest_features.py - |- setup_rest_basicauth.py - |- __init__.py - |- setup_features.py - -Similar to :ref:`part 2 ` we first define our new setup with the devices and all -imported features. Again we want to create two devices, one server devices that provides the rest api and one rest -client device, that executes the requests with the basic authentication. - -Similar to the first setup, we name the features in a format ``My``. We will import them all from -our new specific rest file ``setups/rest/rest_features.py``, except for our ``MyValidRegisteredUserFeature``, which we -have already moved in the common setup-feature file ``setups/setup_features.py``. - -Our setup file will look like: - -.. code-block:: python - - import balder - from balder.connections import HttpConnection - from tests.lib.features import HasLoginSystemFeature - from tests.setups import setup_features - from tests.setups.rest import rest_features - - - class SetupRestBasicAuth(balder.Setup): - - class Server(balder.Device): - # the autonomous feature can be imported directly - _ = HasLoginSystemFeature() - # we have imported it from our common setup-feature file - valid_user = setup_features.MyValidRegisteredUserFeature() - - @balder.connect(Server, HttpConnection) - class Client(balder.Device): - # all of the following files are rest specific files - these are imported from the specific feature file - credentials = rest_features.MyInsertCredentialsFeature() - internal = rest_features.MyViewInternalPageFeature() - - -Add the REST specific features ------------------------------- - -We have added two features that requires a own REST specific implementation. Let us add these features in the file: - -.. code-block:: python - - import balder - from ...lib.features import InsertCredentialsFeature, ViewInternalPageFeature - - # Client features - class MyInsertCredentialsFeature(InsertCredentialsFeature): - class Server(InsertCredentialsFeature.Server): - pass - - def insert_username(self, username): - pass - - def insert_password(self, password): - pass - - def execute_login(self): - pass - - def execute_logout(self): - pass - - - class MyViewInternalPageFeature(ViewInternalPageFeature): - class Server(ViewInternalPageFeature.Server): - pass - - def check_internal_page_viewable(self): - pass - -As you can see, we have also overwritten the vDevice instances, because we will need them in this features too. -Similar to the :ref:`part 2 ` we need a common feature that provides access to our api -endpoint. Even though we don't really have a login area here, but actually send the access data with each request, we -want to set up the whole thing similarly. - -The shared REST feature with basic auth support ------------------------------------------------ - -Let us add a common feature ``BasicAuthManager`` to our global ``lib.features`` module. It should provide some helper -methods that allow us to set the credentials and also should provide a method allowing us to request an endpoint. -Depending on whether a username/password is specified, the request is done with basic authentication. The implementation -can look like the following code: - -.. code-block:: python - - class BasicAuthManager(balder.Feature): - username = None - password = None - - def set_credentials(self, username, password): - self.username = username - self.password = password - - def reset_credentials(self): - self.username = None - self.password = None - - def request_webpage(self, url): - if self.username is not None or self.password is not None: - auth = HTTPBasicAuth(username=self.username, password=self.password) - else: - auth = None - return requests.get(url, auth=auth) - -We want to use this manager in our specific REST feature file. - -Add the basic auth manager to our REST features ------------------------------------------------ - -We want to use this file as required feature in our specific rest features. As you know, this can be done by simply -instantiating it inside the specific rest features that need it: - -.. code-block:: python - - import balder - from ...lib.features import InsertCredentialsFeature, ViewInternalPageFeature - - # Client features - class MyInsertCredentialsFeature(InsertCredentialsFeature): - class Server(InsertCredentialsFeature.Server): - pass - - basic_auth_manager = BasicAuthManager() - username = None - password = None - - def insert_username(self, username): - self.username = username - - def insert_password(self, password): - self.password = password - - def execute_login(self): - self.basic_auth_manager.set_credentials(self.username, self.password) - return True - - def execute_logout(self): - self.basic_auth_manager.reset_credentials() - return True - - - class MyViewInternalPageFeature(ViewInternalPageFeature): - - class Server(ViewInternalPageFeature.Server): - pass - - basic_auth_manager = BasicAuthManager() - - def check_internal_page_viewable(self): - response = self.basic_auth_manager.request_webpage("TODO add the endpoint") - return response.status_code == 200 - -Nice, we already have the main implementation. The only thing, we still need is the endpoint url of the server. - -Add the server feature ----------------------- - -For this we have to add a feature to the server vDevice, that provides these information. Let's call this the -``UserApiFeature``. It should only contain a property which returns the url here. In order for us to use it, we only -have to instantiate it in our vDevice class: - -.. code-block:: python - - import balder - from ...lib.features import InsertCredentialsFeature, ViewInternalPageFeature - - # Server features - class UserApiFeature(balder.Feature): - @property - def url_users(self): - return "http://localhost:8000/api/users" - - # Client features - class MyInsertCredentialsFeature(InsertCredentialsFeature): - class Server(InsertCredentialsFeature.Server): - pass - - basic_auth_manager = BasicAuthManager() - username = None - password = None - - def insert_username(self, username): - self.username = username - - def insert_password(self, password): - self.password = password - - def execute_login(self): - self.basic_auth_manager.set_credentials(self.username, self.password) - return True - - def execute_logout(self): - self.basic_auth_manager.reset_credentials() - return True - - - class MyViewInternalPageFeature(ViewInternalPageFeature): - - class Server(ViewInternalPageFeature.Server): - api = UserApiFeature() - - basic_auth_manager = BasicAuthManager() - - def check_internal_page_viewable(self): - response = self.basic_auth_manager.request_webpage(self.Server.api.url_users) - return response.status_code == 200 - -Of course we have to add our new helper features in our REST setup too: - -.. code-block:: python - - import balder - from balder.connections import HttpConnection - from tests.lib.features import HasLoginSystemFeature - from tests.setups import setup_features - from tests.setups.rest import rest_features - - - class SetupRestBasicAuth(balder.Setup): - - class Server(balder.Device): - _ = HasLoginSystemFeature() - # our new helper feature - api_route = rest_features.UserApiFeature() - valid_user = setup_features.MyValidRegisteredUserFeature() - - @balder.connect(Server, HttpConnection) - class Client(balder.Device): - # our new helper feature - basicauth_manager = rest_features.BasicAuthManager() - credentials = rest_features.MyInsertCredentialsFeature() - internal = rest_features.MyViewInternalPageFeature() - - -We have made it! We have implemented both setups and manage the common use of feature classes. So let's start Balder. - -Execute Balder with both setups -=============================== - -We can check if Balder resolves our scenario with the both setups correctly. For this, just call Balder with the -argument ``--resolve-only``: - -.. code-block:: - - $ balder --working-dir tests --resolve-only - -.. code-block:: none - - +----------------------------------------------------------------------------------------------------------------------+ - | BALDER Testsystem | - | python version 3.9.7 (default, Sep 3 2021, 12:37:55) [Clang 12.0.5 (clang-1205.0.22.9)] | balder version 0.0.1 | - +----------------------------------------------------------------------------------------------------------------------+ - Collect 2 Setups and 1 Scenarios - resolve them to 2 mapping candidates - - RESOLVING OVERVIEW - - Scenario `ScenarioSimpleLoginOut` <-> Setup `SetupWebBrowser` - ScenarioSimpleLoginOut.ClientDevice = SetupWebBrowser.Client - ScenarioSimpleLoginOut.ServerDevice = SetupWebBrowser.Server - -> Testcase - Scenario `ScenarioSimpleLoginOut` <-> Setup `SetupRestBasicAuth` - ScenarioSimpleLoginOut.ClientDevice = SetupRestBasicAuth.Client - ScenarioSimpleLoginOut.ServerDevice = SetupRestBasicAuth.Server - -> Testcase - - -Great, it works. Balder can find our two possible variations, one using our ``SetupWebBrowser`` and one using our -``SetupRestBasicAuth``. - -Now it is time to really run the Balder session. - -.. note:: - Do not forget to start the django server before: - - .. code-block:: none - - $ python manage.py runserver - -After you have secured that the django server runs, you can run Balder: - -.. code-block:: - - $ balder --working-dir tests - -.. code-block:: none - - +----------------------------------------------------------------------------------------------------------------------+ - | BALDER Testsystem | - | python version 3.9.5 (default, Nov 23 2021, 15:27:38) [GCC 9.3.0] | balder version 0.0.1 | - +----------------------------------------------------------------------------------------------------------------------+ - Collect 2 Setups and 1 Scenarios - resolve them to 2 mapping candidates - - ================================================== START TESTSESSION =================================================== - SETUP SetupRestBasicAuth - SCENARIO ScenarioSimpleLoginOut - VARIATION ScenarioSimpleLoginOut.ClientDevice:SetupRestBasicAuth.Client | ScenarioSimpleLoginOut.ServerDevice:SetupRestBasicAuth.Server - TEST ScenarioSimpleLoginOut.test_valid_login_logout [✓] - SETUP SetupWebBrowser - SCENARIO ScenarioSimpleLoginOut - VARIATION ScenarioSimpleLoginOut.ClientDevice:SetupWebBrowser.Client | ScenarioSimpleLoginOut.ServerDevice:SetupWebBrowser.Server - TEST ScenarioSimpleLoginOut.test_valid_login_logout [✓] - ================================================== FINISH TESTSESSION ================================================== - TOTAL NOT_RUN: 0 | TOTAL FAILURE: 0 | TOTAL ERROR: 0 | TOTAL SUCCESS: 2 | TOTAL SKIP: 0 | TOTAL COVERED_BY: 0 diff --git a/doc/source/tutorial_guide/03_port_web_tests_to_cli.rst b/doc/source/tutorial_guide/03_port_web_tests_to_cli.rst new file mode 100644 index 00000000..af1ce901 --- /dev/null +++ b/doc/source/tutorial_guide/03_port_web_tests_to_cli.rst @@ -0,0 +1,6 @@ +Part 3: Reuse Web Tests for NextCloud CLI +***************************************** + + +.. note:: + This tutorial is coming soon! diff --git a/doc/source/tutorial_guide/04_add_fixtures.rst b/doc/source/tutorial_guide/04_add_fixtures.rst deleted file mode 100644 index 0503f3ba..00000000 --- a/doc/source/tutorial_guide/04_add_fixtures.rst +++ /dev/null @@ -1,8 +0,0 @@ -Part 4: Add fixtures -******************** - -.. warning:: - Please note: This section is currently under development and will be released shortly! - -.. - .. todo \ No newline at end of file diff --git a/doc/source/tutorial_guide/05_one_common_setup.rst b/doc/source/tutorial_guide/05_one_common_setup.rst deleted file mode 100644 index b4fb6616..00000000 --- a/doc/source/tutorial_guide/05_one_common_setup.rst +++ /dev/null @@ -1,8 +0,0 @@ -Part 5: One common setup -************************ - -.. warning:: - Please note: This section is currently under development and will be released shortly! - -.. - .. todo \ No newline at end of file diff --git a/doc/source/tutorial_guide/06_develop_with_connection_trees.rst b/doc/source/tutorial_guide/06_develop_with_connection_trees.rst deleted file mode 100644 index 9eb83909..00000000 --- a/doc/source/tutorial_guide/06_develop_with_connection_trees.rst +++ /dev/null @@ -1,8 +0,0 @@ -Part 6: Develop with connection-trees -************************************* - -.. warning:: - Please note: This section is currently under development and will be released shortly! - -.. - .. todo \ No newline at end of file diff --git a/doc/source/tutorial_guide/07_convert_in_balderhub.rst b/doc/source/tutorial_guide/07_convert_in_balderhub.rst deleted file mode 100644 index e29e26e0..00000000 --- a/doc/source/tutorial_guide/07_convert_in_balderhub.rst +++ /dev/null @@ -1,10 +0,0 @@ -Develop and use BalderHub project -********************************* - -.. warning:: - This section is currently under development and will be released shortly! - If you are interested into developing new BalderHub project, feel free to take a look into - :ref:`BalderHub - the share place of tests`. - -.. - todo diff --git a/doc/source/tutorial_guide/index.rst b/doc/source/tutorial_guide/index.rst index 2a2a40c9..cf619248 100644 --- a/doc/source/tutorial_guide/index.rst +++ b/doc/source/tutorial_guide/index.rst @@ -13,10 +13,6 @@ explains the basic concepts of Balder. :caption: Getting Started 00_intro.rst - 01_create_scenario.rst - 02_single_setup.rst - 03_double_setup.rst - 04_add_fixtures.rst - 05_one_common_setup.rst - 06_develop_with_connection_trees.rst - 07_convert_in_balderhub.rst + 01_develop_first_test.rst + 02_install_first_web_tests.rst + 03_port_web_tests_to_cli.rst