Bootstrap Chameleon Logo

Writing Test case

As our tools become more complex, we may need some advanced features and functionality, such as unit testing our own tools, simulating users' continuous but non-interrupting editor operations, etc. PythonTestLib is developed for unit testing TAPython's editor extension APIs

PythonTestLib

In PythonTestLib, functionalities like getting editor logs, delaying and repeating Python commands are provided

Logs

Getting Logs from Output

Now we can get the contents of the Output Log, which can be used to verify the operation results, get warnings and error messages from the log, etc.

NOTE
Note that Log and Output Log are different. Clearing the Output Log does not affect the content returned by PythonTestLib.get_log(), and vice versa.

Clearing Log Buffer

When we no longer need the previous log content, we can clear the buffer using clear_log_buffer(). This way, we can get only the latest log content in subsequent get_logs.

By default, the log buffer size is 10240 logs. This size can be set through the LogNumberLimit parameter in Config.ini.

Delayed Command Invocation

TestLib's delay_call is designed to simulate user operations in the editor, and it differs from asynchronous timed calls.

The main differences are:

Main thread execution Similar to executing in the editor console, it can access resources that are only available in the main thread.

Extra
Thread-related content: PythonBPLib.exec_python_command can specify whether to force Python code execution in the GameThread. By default, it is executed in the current thread. For example, if we start a thread and call PythonBPLib.exec_python_command in this thread, the Python code will be executed in this thread. If we want to execute it in the GameThread (e.g., modifying UI content must be done in the GameThread), we can specify the forece_game_thread parameter in PythonBPLib.exec_python_command.

  • "Delay Time" equals "Available Time in Editor"

This way, we don't have to consider the editor's actual execution lag. For example, if we push a 2-second delayed Python call before loading a map, no matter how long it takes to load the map, our Python code will be called 2 seconds after the map is fully loaded.

There are many practical examples in the UE_TAPython_Plugin_TestCases@github repository.

In the example below, we wait 0.1 seconds after loading the map, and then run our test case _testcase_get_all_worlds.

push_call is a wrapper for delay_call, which calculates the overall delay time based on the previously accumulated delay time, so we don't have to consider the delay time for each call.

def test_category_level_actor(self, id:int):
    self.test_being(id=id)
    # 1
    level_path = '/Game/StarterContent/Maps/StarterMap'
    self.push_call(py_task(unreal.EditorLevelLibrary.load_level, level_path), delay_seconds=0.1)
    self.push_call(py_task(self._testcase_get_all_worlds), delay_seconds=0.5)
    self.push_call(py_task(self.check_log_by_str, logs_target=[f"Test Result: StarterMap"]), delay_seconds=0.1)
    # 2
    self.push_call(py_task(self._testcase_get_all_objects), delay_seconds=0.1)
A GIF of running test case in unreal engine

Verifying Other Data

Capturing Editor Window

The editor's debugging command EditorShot can capture the content displayed in the current editor window. We can use this command to verify whether our tool correctly displays the required Notifications, etc.

In the following code snippet, we execute the Cmd debugging command in Python through execute_console_command

 def editor_snapshot(window_name):
    if window_name is None:
        print("\tTake editor shot")
        unreal.PythonBPLib.execute_console_command(f"EditorShot")
    else:
        print(f'\tEditorShot Name="{window_name}"')
        unreal.PythonBPLib.execute_console_command(f'EditorShot Name="{window_name}"')

TIP
When specifying the window name, you can capture the content in the specified window.

Capturing Chameleon Window

For Chameleon Tools created through TAPython, we can also use unreal.ChameleonData.snapshot_chameleon_window(json_path, override_window_size=override_size) to capture the content in the Chameleon window. This command allows specifying the window size, and it can capture the entire content even if it's not fully displayed. For more details, see Take UI Snapshot

OCR

Using OCR, we can easily recognize and extract text content from images. There are many third-party libraries in Python, such as easyocr used in the previously mentioned TestCases.

Using it is quite simple:

orc_result = easyocr.Reader(['en'], gpu=False, verbose=False).readtext(image_file_path)