diff --git a/.coveragerc b/.coveragerc
new file mode 100644
index 0000000000000000000000000000000000000000..58c84c7994feb962663602fbd7150f06c6f312b7
--- /dev/null
+++ b/.coveragerc
@@ -0,0 +1,4 @@
+[report]
+exclude_lines =
+    if TYPE_CHECKING:
+    if __name__ == .__main__.:
\ No newline at end of file
diff --git a/.gitlab-ci.yml b/.gitlab-ci.yml
index 20c922cf496d3f850f66a511a0f29e4c35b6d89c..d7fbaa1bf993c21c9d51759ebe9e56487eaabe6a 100644
--- a/.gitlab-ci.yml
+++ b/.gitlab-ci.yml
@@ -3,13 +3,14 @@ pytest:
   script:
     - apt-get update -qy
     - apt-get install -y python3-dev python3-pip graphviz graphviz-dev
-    - pip install pytest
-    - pip install .
-    - pytest --junitxml=report.xml
+    - pip install '.[test]'
+    - pytest --cov --cov-report term-missing --cov-report xml:./coverage.xml --junitxml=./test.xml --cov-config=.coveragerc
+  coverage: '/^TOTAL .+?(\d+%)$/'
   artifacts:
-    when: always
     reports:
-      junit: report.xml
+      coverage_report:
+        coverage_format: cobertura
+        path: coverage.xml
 
 pages:
   script:
diff --git a/README.md b/README.md
index 2798a4f0bf10be8d1b5f63dccb2ce0f29106497d..4573a8452a3005f43388f9e8f1441c754496c7e0 100644
--- a/README.md
+++ b/README.md
@@ -1,10 +1,18 @@
+<div align="center">
+![Main Pipeline Report](https://gitlab.ub.uni-bielefeld.de/scs/cocosy/overcooked-simulator/badges/main/pipeline.svg?key_text=Main+Pipeline&key_width=82)
+![Main Coverage Report](https://gitlab.ub.uni-bielefeld.de/scs/cocosy/overcooked-simulator/badges/main/coverage.svg?job=pytest&key_text=Main+Test+Coverage&key_width=114)
+![Dev Pipeline Report](https://gitlab.ub.uni-bielefeld.de/scs/cocosy/overcooked-simulator/badges/dev/pipeline.svg?key_text=Dev+Pipeline&key_width=80)
+![Dev Coverage Report](https://gitlab.ub.uni-bielefeld.de/scs/cocosy/overcooked-simulator/badges/dev/coverage.svg?job=pytest&key_text=Dev+Test+Coverage&key_width=112)
+</div>
+
 # Cooperative Cuisine Environment
 
 [Documentation](https://scs.pages.ub.uni-bielefeld.de/cocosy/overcooked-simulator)
 
 The overcooked-like cooperative cuisine environment for real-time human cooperative interactions and artificial agents.
-
-**The name ist still work in progress and we will probably change it.**
+<div align="center">
+<img src="cooperative_cuisine/pygame_2d_vis/images/cooperative_cuisine.png"  width="800">
+</div>
 
 ## Installation
 
diff --git a/cooperative_cuisine/configs/layouts/basic.layout b/cooperative_cuisine/configs/layouts/basic.layout
index 4b887651b95f07aaa0251f04279bd04c0936e995..060ea99b0826eaf1ac7dfba761db774513c455ba 100644
--- a/cooperative_cuisine/configs/layouts/basic.layout
+++ b/cooperative_cuisine/configs/layouts/basic.layout
@@ -1,5 +1,5 @@
 #QU#FO#TNLB#
-#__________M
+@__________M
 |__________K
 $__________I
 #__A_____A_D
diff --git a/cooperative_cuisine/pygame_2d_vis/images/cooperative_cuisine.png b/cooperative_cuisine/pygame_2d_vis/images/cooperative_cuisine.png
new file mode 100644
index 0000000000000000000000000000000000000000..bfa9b99ed8ebe0eb063de05d5e5d60c91be86c77
Binary files /dev/null and b/cooperative_cuisine/pygame_2d_vis/images/cooperative_cuisine.png differ
diff --git a/setup.py b/setup.py
index 658851b4e072e83add01ebbf22717eb837a7801a..db8f170a0466a1aabca8db8554b369640b1dd150 100644
--- a/setup.py
+++ b/setup.py
@@ -30,9 +30,7 @@ requirements = [
     "pydot>=2.0.0",
 ]
 
-test_requirements = [
-    "pytest>=3",
-]
+test_requirements = ["pytest>=3", "pytest-cov>=4.1"]
 
 setup(
     author="Annika Österdiekhoff, Dominik Battefeld, Fabian Heinrich, Florian Schröder",
@@ -58,7 +56,6 @@ setup(
     name="cooperative_cuisine",
     packages=find_packages(include=["cooperative_cuisine", "cooperative_cuisine.*"]),
     test_suite="tests",
-    tests_require=test_requirements,
     url="https://gitlab.ub.uni-bielefeld.de/scs/cocosy/overcooked-simulator",
     version="0.1.0",
     zip_safe=False,
@@ -68,6 +65,7 @@ setup(
             "stable-baselines3[extra]>=2.2.1",
             "opencv-python>=4.9",
             "wandb>=0.16.3",
-        ]
+        ],
+        "test": test_requirements,
     },
 )
diff --git a/tests/test_cooking_equipment.py b/tests/test_cooking_equipment.py
new file mode 100644
index 0000000000000000000000000000000000000000..5bc9fa3bb4dc16a8464d7a2284d2cdbd9b0a67a5
--- /dev/null
+++ b/tests/test_cooking_equipment.py
@@ -0,0 +1,186 @@
+import pytest
+
+from cooperative_cuisine.game_items import ItemInfo, CookingEquipment, Item, ItemType
+
+
+def test_can_combine_single_other_item():
+    """Test the 'can_combine' method with single other item"""
+
+    item_info = ItemInfo(type=ItemType.Meal, name="Soup", seconds=5.0)
+    cooking_equipment = CookingEquipment(
+        transitions={}, name="Pot", item_info=item_info
+    )
+    other_item = Item("Onion", ItemInfo(type=ItemType.Ingredient, name="Onion"))
+
+    assert cooking_equipment.can_combine(other_item) == False
+
+
+def test_can_combine_list_of_other_items():
+    """Test the 'can_combine' method with list of other items"""
+
+    item_info = ItemInfo(type=ItemType.Meal, name="Soup", seconds=5.0)
+    cooking_equipment = CookingEquipment(
+        transitions={}, name="Pot", item_info=item_info
+    )
+    other_item = CookingEquipment(
+        name="Pan",
+        transitions={},
+        item_info=ItemInfo(type=ItemType.Equipment, name="Pan"),
+    )
+
+    assert cooking_equipment.can_combine(other_item) == False
+
+
+def test_can_combine_without_other_item():
+    """Test the 'can_combine' method without other item"""
+
+    item_info = ItemInfo(type=ItemType.Meal, name="Soup", seconds=5.0)
+    cooking_equipment = CookingEquipment(
+        transitions={}, name="Pot", item_info=item_info
+    )
+
+    assert cooking_equipment.can_combine(None) == False
+
+
+def test_combine():
+    """Test the 'combine' method"""
+
+    item_info = ItemInfo(type=ItemType.Meal, name="Soup", seconds=5.0)
+    cooking_equipment = CookingEquipment(
+        transitions={}, name="Pot", item_info=item_info
+    )
+    other_item = Item("Onion", ItemInfo(type=ItemType.Ingredient, name="Onion"))
+
+    assert cooking_equipment.combine(other_item) is None
+
+
+def test_progress():
+    """Test the 'progress' method"""
+
+    item_info = ItemInfo(type=ItemType.Meal, name="Soup", seconds=5.0)
+    cooking_equipment = CookingEquipment(
+        transitions={}, name="Pot", item_info=item_info
+    )
+    result = Item(name="TestResult", item_info=None)
+    cooking_equipment.active_transition = {
+        "seconds": 5.0,
+        "result": result,
+    }
+    from datetime import datetime, timedelta
+
+    cooking_equipment.progress(passed_time=timedelta(seconds=5.0), now=datetime.now())
+
+    assert cooking_equipment.content_list == [result]
+
+
+def test_reset_content():
+    """Test the 'reset_content' method"""
+
+    item_info = ItemInfo(type=ItemType.Meal, name="Soup", seconds=5.0)
+    cooking_equipment = CookingEquipment(
+        transitions={}, name="Pot", item_info=item_info
+    )
+    cooking_equipment.content_list = [1, 2, 3, 4, 5, 6, 7, 8, 9, 10]
+    cooking_equipment.reset_content()
+
+    assert cooking_equipment.content_list == []
+
+
+def test_release():
+    """Test the 'release' method"""
+
+    item_info = ItemInfo(type=ItemType.Meal, name="Soup", seconds=5.0)
+    cooking_equipment = CookingEquipment(
+        transitions={}, name="Pot", item_info=item_info
+    )
+
+    cooking_equipment.content_list = ["Content1", "Content2"]
+
+    assert cooking_equipment.release() == ["Content1", "Content2"]
+    assert cooking_equipment.content_list == []
+
+
+def test_extra_repr_without_content():
+    """Test the 'extra_repr' method without content"""
+
+    item_info = ItemInfo(type=ItemType.Meal, name="Soup", seconds=5.0)
+    cooking_equipment = CookingEquipment(
+        transitions={}, name="Pot", item_info=item_info
+    )
+
+    assert cooking_equipment.extra_repr == "[], None"
+
+
+def test_extra_repr_with_content():
+    """Test the 'extra_repr' method with content"""
+
+    item_info = ItemInfo(type=ItemType.Meal, name="Soup", seconds=5.0)
+    cooking_equipment = CookingEquipment(
+        transitions={}, name="Pot", item_info=item_info
+    )
+
+    item_1 = Item(
+        name="Tomato", item_info=ItemInfo(type=ItemType.Ingredient, name="Tomato")
+    )
+    item_2 = Item(
+        name="Potato", item_info=ItemInfo(type=ItemType.Ingredient, name="Potato")
+    )
+    cooking_equipment.content_list.extend([item_1, item_2])
+
+    assert cooking_equipment.extra_repr == "[Tomato(), Potato()], None"
+
+
+def test_get_potential_meal_without_content():
+    """Test the 'get_potential_meal' method without content"""
+
+    item_info = ItemInfo(type=ItemType.Meal, name="Soup", seconds=5.0)
+    cooking_equipment = CookingEquipment(
+        transitions={}, name="Pot", item_info=item_info
+    )
+
+    assert cooking_equipment.get_potential_meal() is None
+
+
+def test_get_potential_meal_with_content():
+    """Test the 'get_potential_meal' method with content"""
+
+    item_info = ItemInfo(type=ItemType.Meal, name="Soup", seconds=5.0)
+    cooking_equipment = CookingEquipment(
+        transitions={}, name="Pot", item_info=item_info
+    )
+
+    item_1 = Item(
+        name="Tomato", item_info=ItemInfo(type=ItemType.Ingredient, name="Tomato")
+    )
+    cooking_equipment.content_list.append(item_1)
+
+    assert cooking_equipment.get_potential_meal() == item_1
+
+    item_2 = Item(
+        name="TomatoSoup", item_info=ItemInfo(type=ItemType.Meal, name="TomatoSoup")
+    )
+    cooking_equipment.content_ready = item_2
+    assert cooking_equipment.get_potential_meal() == item_2
+
+
+@pytest.fixture
+def cooking_equipment():
+    item_info = ItemInfo(type=ItemType.Meal, name="Soup", seconds=5.0)
+    return CookingEquipment(transitions={}, name="Pot", item_info=item_info)
+
+
+def test_reset(cooking_equipment):
+    """Test the 'reset' method"""
+
+    cooking_equipment.active_transition = {"1": 2}
+    cooking_equipment.progress_percentage = 1.0
+    cooking_equipment.progress_equipment = "Here"
+
+    cooking_equipment.reset()
+
+    assert cooking_equipment.progress_percentage == 0.0
+    assert cooking_equipment.active_transition is None
+    assert cooking_equipment.progress_equipment is None
+
+
+# TODO full transition test with combine, progress etc.
diff --git a/tests/test_counter.py b/tests/test_counter.py
new file mode 100644
index 0000000000000000000000000000000000000000..b0fb93a9bdd3f63d4d98f0377da8e9060fe3c88d
--- /dev/null
+++ b/tests/test_counter.py
@@ -0,0 +1,101 @@
+import numpy as np
+
+from cooperative_cuisine.counters import ServingWindow, Dispenser
+from cooperative_cuisine.game_items import Item, Plate, ItemInfo, ItemType
+from cooperative_cuisine.hooks import Hooks
+from cooperative_cuisine.utils import create_init_env_time
+
+
+def test_serving_window():
+    class DummyOrderManager:
+        def serve_meal(self, item, env_time, player) -> bool:
+            return (isinstance(item, str) and item == "Test123") or item.content_list[
+                0
+            ].name == "TestMeal"
+
+    class DummyPlateDispenser:
+        plate_received = False
+
+        def update_plate_out_of_kitchen(self, env_time):
+            self.plate_received = True
+
+    plate_dispenser = DummyPlateDispenser()
+
+    serving_window = ServingWindow(
+        order_manager=DummyOrderManager(),
+        meals={"TestMeal", "TestMeal2"},
+        env_time_func=create_init_env_time,
+        plate_dispenser=plate_dispenser,
+        pos=np.array([1.0, 1.0]),
+        hook=Hooks(None),
+    )
+
+    serving_window.drop_off(item="Test123")
+    assert (
+        plate_dispenser.plate_received
+    ), "ServingWindow needs to update plate out of kitchen for ordered meal."
+    plate_dispenser.plate_received = False
+    plate = Plate(transitions={}, clean=True, item_info=None)
+    plate.content_list = [Item(name="TestMeal", item_info=None)]
+    assert serving_window.can_drop_off(
+        item=plate
+    ), "ServingWindow could drop off a known meal."
+    assert (
+        serving_window.drop_off(item=plate) is None
+    ), "ServingWindow drop_off should return None for a served meal."
+    assert (
+        plate_dispenser.plate_received
+    ), "ServingWindow needs to update plate out of kitchen for ordered meal."
+    plate_dispenser.plate_received = False
+
+    plate.content_list = [Item(name="TestMeal2", item_info=None)]
+    assert serving_window.can_drop_off(
+        item=plate
+    ), "ServingWindow could drop off a known meal."
+    assert (
+        serving_window.drop_off(item=plate) == plate
+    ), "ServingWindow should return the item for not ordered meals."
+
+    assert (
+        serving_window.pick_up() is None
+    ), "Player should not be able to pick something from the ServingWindow."
+
+
+def test_dispenser():
+    dispenser = Dispenser(
+        dispensing=ItemInfo(
+            type=ItemType.Ingredient,
+            name="MyIngredient",
+            seconds=0,
+            needs=["MySecondIngredient"],
+            equipment=None,
+        ),
+        pos=np.array([1.0, 1.0]),
+        hook=Hooks(None),
+        undo_dispenser_pickup=False,
+    )
+    assert (
+        dispenser.occupied_by.name == "MyIngredient"
+    ), "Initialized dispenser should be occupied by dispensing item"
+    assert (
+        dispenser.pick_up().name == "MyIngredient"
+    ), "Picked up item should be the dispensing item"
+    assert (
+        dispenser.occupied_by is not None
+    ), "After pickup a new occupied by should be generated"
+    assert (
+        dispenser.occupied_by.name == "MyIngredient"
+    ), "After pick up a new occupied by item should be generated"
+
+    assert not dispenser.can_drop_off(
+        dispenser.pick_up()
+    ), "Config undo_dispenser_pickup==False should stop the player to drop off picked up items"
+
+    dispenser.undo_dispenser_pickup = True
+    assert dispenser.can_drop_off(
+        dispenser.pick_up()
+    ), "Config undo_dispenser_pickup==True should allow the player to drop off picked up items"
+    assert (
+        dispenser.drop_off(dispenser.pick_up()) is None
+    ), "Config undo_dispenser_pickup==True should allow the player to drop off picked up items"
+    # check combine?
diff --git a/tests/test_item.py b/tests/test_item.py
new file mode 100644
index 0000000000000000000000000000000000000000..9b3cdb64deb08be2331a1570198dd14c6577b372
--- /dev/null
+++ b/tests/test_item.py
@@ -0,0 +1,64 @@
+import pytest
+
+from cooperative_cuisine.game_items import ItemInfo, Item, ItemType
+
+
+@pytest.fixture
+def items():
+    item1_info = ItemInfo(type=ItemType.Ingredient, name="Tomato")
+    item1 = Item("Tomato", item1_info)
+    item2_info = ItemInfo(type=ItemType.Ingredient, name="Onion")
+    item2 = Item("Onion", item2_info)
+    item1_duplicate = Item("Tomato", item1_info)
+
+    return item1, item1_info, item1_duplicate, item2, item2_info
+
+
+def test_init(items):
+    item1, item1_info, _, _, _ = items
+    assert item1.name == "Tomato"
+    assert item1.item_info == item1_info
+    assert item1.progress_equipment is None
+    assert item1.progress_percentage == 0.0
+
+
+def test_repr(items):
+    item1, _, _, _, _ = items
+    assert str(item1) == "Tomato()"
+
+
+def test_eq(items):
+    item1, _, item1_duplicate, item2, _ = items
+    assert item1 == item1_duplicate
+    assert item1 != item2
+
+
+def test_can_combine(items):
+    item1, _, _, item2, _ = items
+    assert not item1.can_combine(item2)
+
+
+def test_combine(items):
+    item1, _, _, item2, _ = items
+    assert item1.combine(item2) is None
+
+
+def test_progress(items):
+    item1, _, _, _, _ = items
+    item1.progress_equipment = "Oven"
+    item1.progress("Oven", 0.2)
+    assert item1.progress_percentage == 0.2
+    item1.progress("Oven", 0.1)
+    assert item1.progress_percentage == pytest.approx(0.3)
+    item1.progress("NotOven", 0.2)
+    assert item1.progress_percentage == pytest.approx(0.3)
+    assert item1.progress_equipment == "Oven"
+
+
+def test_reset(items):
+    item1, _, _, _, _ = items
+    item1.progress_equipment = "Oven"
+    item1.progress("Oven", 0.2)
+    item1.reset()
+    assert item1.progress_equipment is None
+    assert item1.progress_percentage == 0.0
diff --git a/tests/test_start.py b/tests/test_start.py
index eb4746dfb41dc33db2a548ef5b675adfc46e9d7d..e69b1ac95c1590adff21abcaa9291f7352ddb8d6 100644
--- a/tests/test_start.py
+++ b/tests/test_start.py
@@ -12,7 +12,17 @@ from cooperative_cuisine.environment import (
     InterActionData,
 )
 from cooperative_cuisine.game_items import Item, ItemInfo, ItemType
+from cooperative_cuisine.game_server import PlayerRequestType
 from cooperative_cuisine.hooks import Hooks
+from cooperative_cuisine.server_results import (
+    PlayerInfo,
+    CreateEnvResult,
+    PlayerRequestResult,
+)
+from cooperative_cuisine.state_representation import (
+    StateRepresentation,
+    create_json_schema,
+)
 from cooperative_cuisine.utils import create_init_env_time
 
 layouts_folder = ROOT_DIR / "configs" / "layouts"
@@ -118,6 +128,10 @@ def test_player_movement_speed(env_config, layout_empty_config, item_info):
         np.linalg.norm(expected - env.players[player_name].pos), 0
     ), "Performed movement do not move the player as expected."
 
+    assert StateRepresentation.model_validate_json(
+        json_data=env.get_json_state(player_id="1")
+    ), "json state does not match expected StateRepresentation."
+
 
 def test_player_reach(env_config, layout_empty_config, item_info):
     env = Environment(env_config, layout_empty_config, item_info, as_files=False)
@@ -281,3 +295,18 @@ def test_time_limit():
     env.step(passed_time_2)
 
     assert env.game_ended, "Game has ended now."
+
+
+def test_json_schema():
+    assert isinstance(create_json_schema(), dict)
+
+
+def test_server_result_definition():
+    plater_info = PlayerInfo(client_id="123", player_hash="234567890", player_id="0")
+    CreateEnvResult(env_id="123344", player_info={"0": plater_info}, recipe_graphs=[])
+    PlayerRequestResult(
+        request_type=PlayerRequestType.READY,
+        status=200,
+        msg="123",
+        player_hash="1234324",
+    )