# Python Testing API

## Introduction

As of version **2024.1**, **Incari** now provides developers with another powerful tool to manage the entire *HMI* development process, from prototyping to mass production. With the new **Python Testing** **API**, the user can perform automated application testing and quality assurance utilizing *Python* in conjunction with **Incari Studio**. This allows for seamless integration between **Incari Studio** and any scripts using the **API** that have been created externally.

The **Python Testing API** gives the user everything required to understand the tools necessary for automating as well as creating their own tests in *Python* in order to check their **Incari Projects**. The following four sections provide the details:

* [**Requirements**](#requirements)
* [**Types**](#types)
* [**Template**](#template)
* [**Example**](#example)

## Requirements

It is mandatory to install *Python* to access the **incari Module**, as tests will be written in *Python* outside of **Incari Studio**.

### Installation Requirements

*Python* version:

* *3.10.6*, which can be installed [here](https://www.python.org/downloads/release/python-3106/).

### Adding the PATH

*PYTHONPATH* is a type of *PATH*, which is an environment variable, and is required for *Python* to function. More on *PATHs* can be found [here](https://en.wikipedia.org/wiki/PATH_\(variable\)).

To make the *PATH* permanently available in *Windows*:

* Press `Win + R`, type `sysdm.cpl`, and press `Enter`
* In the *System Properties* window, go to the *Advanced* tab and click *Environment Variables*.
* Under *System Variables*, locate and select `PYTHONPATH`.
  * If it doesn't exist, click `New` and then add:
    * *Variable name*: `PYTHONPATH`
    * *Variable value*: `C:\Program Files\Incari\IncariStudio\2024.1\bin`
* Click `OK` to save and close all windows.
* Finally, if applicable, restart the *Command Prompt* or *IDE* for the changes to take effect.

## Types

There are several different *Method Types* handled by the **incari Module**. **Object** *Types* contain certain *Properties* as well.

The types are:

* [**Object**](#object)
  * [**Properties**](#properties)
* [**Screen**](#screen)
* [**Scene**](#scene)
* [**Mouse**](#mouse)
* [**Keyboard**](#keyboard)

### Object

The **Object** *Methods* are described below. The `propertyName:Strings` are given in the [**Properties**](#properties) section.

| Method Name    | Method Usage                                 | Parameter                    | Return Type   | Method Definition                                                                                                                                                                                                                                                                                  |
| -------------- | -------------------------------------------- | ---------------------------- | ------------- | -------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
| `get_id`       | `object.get_id()`                            | --                           | `incari.UUID` | `get_id` returns the [**Debug ID** ](/incari-studio/objects-and-types/attributes/common-attributes/debug-id.md)of an **Object**.                                                                                                                                                                   |
| `get_property` | `object.get_property("propertyName")`        | `propertyName:String`        | *value*       | `get_property` returns the value of the chosen [**Property**](#properties) of an **Object**. The returned *value* is of the *type* of the desired **Property**. This *type* is displayed in the **Property** table in the next section under **Type** (for example, `Name` is a **String**).       |
| `set_property` | `object.set_property("propertyName", value)` | `propertyName:String, value` | --            | `set_property` allows the user to set a *value* for a certain [**Property**](#properties). The second parameter `value` is of the *type* of the desired **Property**. This *type* is displayed in the **Property** table in the next section under **Type** (for example, `Name` is a **String**). |

#### Properties

The **Property Names** which correspond to the `propertyName:Strings` given in the section above.

| Property | Property Name      | Type                 | Property Definition                                                                                            |
| -------- | ------------------ | -------------------- | -------------------------------------------------------------------------------------------------------------- |
| Name     | `_name`            | **String**           | The name of the **Object** in **String** format.                                                               |
| Text     | `_text`            | **TranslatableText** | The text of a **Text Object**. Only works with **Text Objects**.                                               |
| Size     | `_currentSize`     | **Vec2**             | The `X` and `Y` values of any **Object's** size.                                                               |
| Rotation | `_currentRotation` | **Float**            | The current rotation of an **Object** in **Float** format.                                                     |
| Position | `_currentPosition` | **Vec2**             | The current position of an **Object** in `X` and `Y` values in *two-dimensional space*.                        |
| Opacity  | `_currentOpacity`  | **Float**            | The current opacity of an **Object** in **Float** format.                                                      |
| Visible  | `_enabled`         | **Bool**             | The current visibility of an **Object**. *True* corresponds to visible and *False* corresponds to not visible. |

### Screen

The *Screen Methods* are described below.

| Method Name    | Method Usage            | Parameter | Return Type       | Method Definition                                                                                                               |
| -------------- | ----------------------- | --------- | ----------------- | ------------------------------------------------------------------------------------------------------------------------------- |
| `get_id`       | `screen.get_id()`       | --        | `incari.UUID`     | `get_id` returns the [**Debug ID** ](/incari-studio/objects-and-types/attributes/common-attributes/debug-id.md)of a **Screen**. |
| `get_keyboard` | `screen.get_keyboard()` | --        | `incari.Keyboard` | `get_keyboard` returns the **Keyboard** associated with the **Screen**.                                                         |
| `get_mouse`    | `screen.get_mouse()`    | --        | `incari.Mouse`    | `get_mouse` returns the **Mouse** associated with the **Screen**.                                                               |

### Scene

The *Scene Methods* are described below.

| Method Name        | Method Usage               | Parameter     | Return Type     | Method Definition                                                                                                                                |
| ------------------ | -------------------------- | ------------- | --------------- | ------------------------------------------------------------------------------------------------------------------------------------------------ |
| `get_id`           | `scene.get_id()`           | --            | `incari.UUID`   | `get_id` returns the [**Debug ID** ](/incari-studio/objects-and-types/attributes/common-attributes/debug-id.md)of a **Scene**.                   |
| `get_object_by_id` | `scene.get_object_by_id()` | `incari.UUID` | `incari.Object` | Returns the **Object** of a specified [**Debug ID**](/incari-studio/objects-and-types/attributes/common-attributes/debug-id.md) for a **Scene**. |
| `get_root_object`  | `scene.get_root_object()`  | --            | `incari.Object` | Returns the [**Root Object**](/incari-studio/objects-and-types/scene-objects.md#root-object) of a **Scene**.                                     |

### Mouse

The *Mouse Methods* are described below.

| Method Name | Method Usage                           | Parameter                                                       | Return Type | Method Definition                                              |
| ----------- | -------------------------------------- | --------------------------------------------------------------- | ----------- | -------------------------------------------------------------- |
| `move`      | `mouse.move(incari.Vec2(x,y))`         | `incari.Vec2(x1,y1)`                                            | --          | `move` chooses where the **Mouse** should move to.             |
| `press`     | `mouse.press(incari.Mouse.Button.X)`   | `X` = `incari.Mouse.Button.LEFT` OR `incari.Mouse.Button.RIGHT` | --          | `press` defines if a **Mouse** press is *left* or *right*.     |
| `release`   | `mouse.release(incari.Mouse.Button.X)` | `X` = `incari.Mouse.Button.LEFT` OR `incari.Mouse.Button.RIGHT` | --          | `release` defines if a **Mouse** release is *left* or *right*. |

### Keyboard

The *Keyboard Methods* are described below.

| Method Name | Method Usage                                    | Parameter                                                               | Return Type | Method Definition                                     |
| ----------- | ----------------------------------------------- | ----------------------------------------------------------------------- | ----------- | ----------------------------------------------------- |
| `press`     | `keyboard.press(incari.Keyboard.Key.KEY_NAME)`  | `incari.Keyboard.Key.KEY_NAME` (example: KEY\_A if `A` is desired key.) | --          | `press` defines the key pressed on a **Keyboard**.    |
| `release`   | `keyboard.release(incari.Keyboard.Key.KEY_NAME` | `incari.Keyboard.Key.KEY_NAME` (example: Key\_A if `A` is desired key.) | --          | `release` defines the key released on a **Keyboard**. |

## Template

The following template provides all the method definitions in the **incari Module**.

Please note that to use the **incari Module** in *Python*, one needs to makes sure all requirements are met (as mentioned [above](#requirements)).

Additionally, it is imperative to first use `import incari` to import the **Module**.

```
import incari

def set_up(ip, port):
    """
    eg: (ip, port) = ("127.0.0.1", 52001)
    """
    intercom = incari.Intercom(ip, port)
    return intercom


def get_screen(screen_id, intercom):
    """
    screen_id: string
    intercom: returned from set_up()
    """
    screen = intercom.get_screen_by_id(incari.UUID(screen_id))
    return screen


def get_scene(scene_id, intercom):
    """
    screen_id: string
    intercom: returned from set_up()
    """
    scene = intercom.get_scene_by_id(incari.UUID(scene_id))
    return scene


def get_mouse(screen):
    """
    screen: returned from get_screen()
    """
    mouse = screen.get_mouse()
    return mouse


def get_keyboard(screen):
    """
    screen: returned from get_screen()
    """
    keyboard = screen.get_keyboard()
    return keyboard
    
def mouse_click_left(mouse, x, y):
    """
    Mouse click on Left button.
    x, y: are the exact position to click
    """
    mouse.move(incari.Vec2(x, y))
    mouse.press(incari.Mouse.Button.LEFT)
    mouse.release(incari.Mouse.Button.LEFT)
def mouse_click_right(mouse, x, y):
    """
    Mouse click on right button.
    x, y: are the exact position to click
    """
    mouse.move(incari.Vec2(x, y))
    mouse.press(incari.Mouse.Button.RIGHT)
    mouse.release(incari.Mouse.Button.RIGHT)
def swipe_leftbutton(mouse, x1, y1, x2, y2):
    '''
    swiping from (x1, y1) to (x2, y2) using left mouse button
    '''
    mouse.move(incari.Vec2(x1, y1))
    mouse.press(incari.Mouse.Button.LEFT)
    time.sleep(0.1)
    mouse.move(incari.Vec2(x2, y2))
    mouse.release(incari.Mouse.Button.LEFT)
def long_press_mouse_left(mouse, x, y):
    """
    long Mouse click on Left button.
    x, y: the exact position where to click
    """
    mouse.move(incari.Vec2(x, y))
    mouse.press(incari.Mouse.Button.LEFT)
    time.sleep(1)
    mouse.release(incari.Mouse.Button.LEFT)
    
# Class IncariObject is a wrapper class for IncariObject returned from get_object().
# It contains additional methods to interact with the object.
# It takes an IncariObject as an argument.   
class IncariObject:
    def __init__(self, incari_object):
        self.incari_object = incari_object

    def getPosition(self):
        pos_vec = self.incari_object.get_property("_currentPosition") # incari.vector in format <Vec2: x=0, y=0>
        return (int(pos_vec.x), int(pos_vec.y))

    def setPosition(self, x, y):
        self.incari_object.set_property("_currentPosition", f"({x} {y})")
        
    def getRotation(self):
        return self.incari_object.get_property("_currentRotation")

    def setRotation(self, rotation):
        self.incari_object.set_property("_currentRotation", rotation)

    def getText(self):
        return self.incari_object.get_property("_text").get_text()
    
    def getOpacity(self):
        return self.incari_object.get_property("_currentOpacity")
    
    def setOpacity(self, opacity):
        self.incari_object.set_property("_currentOpacity", opacity)
    
    def getVisibility(self):
        return self.incari_object.get_property("_enabled")
    
    def getSize(self):
        size = self.incari_object.get_property("_currentSize") # incari.vector in format <Vec2: x=0, y=0>
        return (int(size.x), int(size.y))
    
    def getName(self):
        name = self.incari_object.get_property("_name")
        return name
    
    def getId(self):
        return self.incari_object.get_id()
        
        
        
```

## Example

To better clarify how to use the **Python Testing API**, here is an example use-case:

```
def rotate_object_with_mouse_and_keyboard(object, mouse, keyboard):
    """
    Rotate an object continuously, interacting with mouse and keyboard.
    Args:
        object (IncariObject): The extended Incari object.
        mouse: Mouse object.
        keyboard: Keyboard object.
    """
    rotation = object.get_rotation()
    press_mouse, press_key = True, True
    try:
        while True:
            rotation += 6.0
            object.set_rotation(rotation)
            if press_mouse:
                mouse.press(incari.Mouse.Button.LEFT)
            else:
                mouse.release(incari.Mouse.Button.LEFT)
            if press_key:
                keyboard.press(incari.Keyboard.Key.KEY_A)
            else:
                keyboard.release(incari.Keyboard.Key.KEY_A)
            press_mouse = not press_mouse
            press_key = not press_key
            time.sleep(0.5)
    except KeyboardInterrupt:
        print("Rotation loop interrupted by user.")
# Configuration
config = {
    "ip": "127.0.0.1",
    "port": 52001,
    "screen_id": "c967b33f-67fd-4cf1-936a-0bbf4db9931f",
    "scene_id": "31983131-4aba-403e-b97b-656fd5df51d2",
    "object_id": "dedc4c26-6a8b-4a7c-b786-4c6db7bd5c8c",
}
# Setup and execution
if __name__ == "__main__":
    intercom = set_up(config["ip"], config["port"])
    screen = get_screen(config["screen_id"], intercom)
    scene = get_scene(config["scene_id"], intercom)
    incari_object = scene.get_object_by_id(incari.UUID(config["object_id"]))
    object = IncariObject(incari_object)
    mouse = get_mouse(screen)
    keyboard = get_keyboard(screen)
    print("Starting rotation...")
    rotation = object.get_rotation()
    press_mouse, press_key = True, True
    while True:
        rotation += 6.0
        object.set_rotation(rotation)
        if press_mouse:
            mouse.press(incari.Mouse.Button.LEFT)
        else:
            mouse.release(incari.Mouse.Button.LEFT)
        if press_key:
            keyboard.press(incari.Keyboard.Key.KEY_A)
        press_mouse = not press_mouse
        press_key = not press_key
        time.sleep(0.5)
```

To follow along, please create a **Project** which contains a **Rectangle** in a **Scene2D** as well as copy and paste the above script to the desired *Python* location. In addition, recreate the **Logic** shown below in the **Logic Editor**.

![](/files/zls5FqOBa6ZBJg6UrLom)

This will set the color of the **Rectangle** to blue if `A` is pressed.

The script (shown above) continuously rotates the **Rectangle** by 6 degrees while the **Player** is running. It also executes the key press in the **Logic** without requiring the user to physically press `A`. While the **Logic** is capable of this, the provided script handles the action required for the color change.

Furthermore, the alphanumerics shown in the script lines describing the various **IDs** as:

```
  "screen_id": "c967b33f-67fd-4cf1-936a-0bbf4db9931f",
  "scene_id": "31983131-4aba-403e-b97b-656fd5df51d2",
  "object_id": "dedc4c26-6a8b-4a7c-b786-4c6db7bd5c8c",
```

should be changed to what is displayed in the [**Debug IDs**](/incari-studio/objects-and-types/attributes/common-attributes/debug-id.md) of the respective object types in the **Project** previously created by the user (i.e. the **Debug ID** of the **Project's** **Screen**, **Scene2D**, and **Rectangle**).

The **Incari Project** and script are connected by the *port number*, which is given with the line `"port": 52001,`. In order to make sure this connection is understood by **Incari Studio**, one must open the **Player** using the *Windows PowerShell* from the *bin* folder of the desired **Incari Studio** version. Please be aware that the **Python Testing API** is only compatible from versions **2024.1** onward.

To do this, after opening the *Windows PowerShell* from the appropriate *bin* folder, the user must run the following command:

`.\IncariPlayer.exe /p="C:ProjectPathName" /test-api /listener-port=52001`

When **Incari Player** is finally running, the **Rectangle** will be blue *and* will rotate. The **Player** *must* be run before running the script.

This shows that the **Python Testing API** aids in automation and can be extended to testing.


---

# Agent Instructions: Querying This Documentation

If you need additional information that is not directly available in this page, you can query the documentation dynamically by asking a question.

Perform an HTTP GET request on the current page URL with the `ask` query parameter:

```
GET https://docs.incari.com/incari-studio/demo-projects/testing-api.md?ask=<question>
```

The question should be specific, self-contained, and written in natural language.
The response will contain a direct answer to the question and relevant excerpts and sources from the documentation.

Use this mechanism when the answer is not explicitly present in the current page, you need clarification or additional context, or you want to retrieve related documentation sections.
