diff --git a/.gitignore b/.gitignore
index 725e98b..3c5145b 100644
--- a/.gitignore
+++ b/.gitignore
@@ -4,3 +4,5 @@ apparser.egg-info
dist
__pycache__
_build*/
+_tmp*/
+.pypirc
diff --git a/README.md b/README.md
index f3d6b0d..8a42147 100644
--- a/README.md
+++ b/README.md
@@ -14,7 +14,7 @@ Apparser is a Python library for automating desktop applications and interacting
# Installation
```bash
-# Base Apparser package with base ocr model
+# Base Apparser package with base OCR model
pip install apparser
# Apparser with text recognition support
@@ -78,7 +78,7 @@ Full documentation is available PyPI
# Donation
-If you'd like to financially support the developers for their work:
+If you'd like to financially support the developers' work:
Donation link
diff --git a/apparser/core/app.py b/apparser/core/app.py
index eeee884..c003c75 100644
--- a/apparser/core/app.py
+++ b/apparser/core/app.py
@@ -77,7 +77,8 @@ def start_app(self):
self.__find_window_by_process_id(i.get_process_id())
if self.__ui is not None:
return
- self.__find_window_by_title()
+ if self.__window_title_name is not None:
+ self.__find_window_by_title()
if self.__ui is None:
raise WindowDoesNotValidException()
diff --git a/apparser/core/ui/coordinates.py b/apparser/core/ui/coordinates.py
index a9a3c30..47aaf9c 100644
--- a/apparser/core/ui/coordinates.py
+++ b/apparser/core/ui/coordinates.py
@@ -30,10 +30,10 @@ def __init__(
raise TypeError('from_ui must be BaseUi')
if not isinstance(point_one, (Point, RelativelyPoint)):
- raise TypeError('point1 must be Point or RelativelyPoint')
+ raise TypeError('point_one must be Point or RelativelyPoint')
elif not isinstance(point_two, (Point, RelativelyPoint)):
- raise TypeError('point2 must be Point, RelativelyPoint')
+ raise TypeError('point_two must be Point or RelativelyPoint')
self.__from_ui = from_ui
self.__point_one = point_one
diff --git a/apparser/cv/events/undetected.py b/apparser/cv/events/undetected.py
index ca0d1cb..f248209 100644
--- a/apparser/cv/events/undetected.py
+++ b/apparser/cv/events/undetected.py
@@ -10,4 +10,4 @@ def __str__(self) -> str:
:return: Undetected event name.
:rtype: str
"""
- return "UnDetected"
+ return "Undetected"
diff --git a/apparser/exceptions/instruction_not_found.py b/apparser/exceptions/instruction_not_found.py
index bfd38ee..45dc73e 100644
--- a/apparser/exceptions/instruction_not_found.py
+++ b/apparser/exceptions/instruction_not_found.py
@@ -1,7 +1,7 @@
class InstructionNotFoundException(Exception):
"""Represent a failure to resolve an instruction."""
- def __init__(self, text: str):
+ def __init__(self, text: str | None):
"""Initialize an instruction lookup exception.
:param text: Error message text.
diff --git a/apparser/exceptions/text_not_found.py b/apparser/exceptions/text_not_found.py
index 87d7813..4ed712d 100644
--- a/apparser/exceptions/text_not_found.py
+++ b/apparser/exceptions/text_not_found.py
@@ -5,12 +5,12 @@ def __init__(self, min_similarity: float):
"""Initialize a text lookup exception.
:param min_similarity: Minimum accepted similarity value.
- :type min_similarity: float
+ :type min_similarity: float | int
:raises TypeError: If ``min_similarity`` has an invalid type.
:raises ValueError: If ``min_similarity`` is outside the inclusive range from 0 to 1.
"""
- if not isinstance(min_similarity, float):
- raise TypeError("min_similarity must be float")
+ if not isinstance(min_similarity, (float, int)):
+ raise TypeError("min_similarity must be a number")
if min_similarity < 0 or min_similarity > 1:
raise ValueError("min_similarity must be between 0 and 1")
diff --git a/apparser/geometry/relatively_point.py b/apparser/geometry/relatively_point.py
index d325391..6665b36 100644
--- a/apparser/geometry/relatively_point.py
+++ b/apparser/geometry/relatively_point.py
@@ -18,10 +18,10 @@ def __init__(self, x_percent: float, y_percent: float):
raise TypeError('y_percent must be number')
if x_percent < -1 or x_percent > 1:
- raise ValueError('x must be between -1 and 1')
+ raise ValueError('x_percent must be between -1 and 1')
if y_percent < -1 or y_percent > 1:
- raise ValueError('y must be between -1 and 1')
+ raise ValueError('y_percent must be between -1 and 1')
self.__x_percent = x_percent
self.__y_percent = y_percent
diff --git a/apparser/instructions/default/click.py b/apparser/instructions/default/click.py
index e3d2e99..b982545 100644
--- a/apparser/instructions/default/click.py
+++ b/apparser/instructions/default/click.py
@@ -35,7 +35,7 @@ def __init__(self, click_type: BaseKeyCode = LeftClick()):
:param click_type: Mouse button to press down.
:type click_type: BaseKeyCode
- :raises TypeError: If ``click_type`` is neither :class:`BaseKeyCode`.
+ :raises TypeError: If ``click_type`` is not a :class:`BaseKeyCode`.
"""
if not isinstance(click_type, BaseKeyCode):
raise TypeError('click_type must be BaseKeyCode')
@@ -58,7 +58,7 @@ def __init__(self, click_type: BaseKeyCode = LeftClick()):
:param click_type: Mouse button to release.
:type click_type: BaseKeyCode
- :raises TypeError: If ``click_type`` is neither :class:`BaseKeyCode`.
+ :raises TypeError: If ``click_type`` is not a :class:`BaseKeyCode`.
"""
if not isinstance(click_type, BaseKeyCode):
raise TypeError('click_type must be BaseKeyCode')
diff --git a/apparser/instructions/default/press.py b/apparser/instructions/default/press.py
index 025fb45..4970983 100644
--- a/apparser/instructions/default/press.py
+++ b/apparser/instructions/default/press.py
@@ -30,17 +30,20 @@ def perform(self, *args, **kwargs):
class PressKeysCombination(BaseInstruction):
"""Send a keyboard shortcut as a pressed combination."""
- def __init__(self, keys: list[BaseKeyCode | str] | str):
+ def __init__(self, keys: list[BaseKeyCode | str]):
"""Initialize a key combination instruction.
:param keys: Keys to press together.
- :type keys: list[BaseKeyCode | str] | str
+ :type keys: list[BaseKeyCode | str]
+ :raises TypeError: If ``keys`` or any key has an invalid type.
"""
+ if not isinstance(keys, list):
+ raise TypeError('keys must be list')
+
self.__keys = keys
self.__validate()
def __validate(self):
-
for key in self.__keys:
if not (isinstance(key, BaseKeyCode) or isinstance(key, str)):
raise TypeError('key_code must be BaseKeyCode or str')
@@ -52,7 +55,6 @@ def id(self) -> int:
def perform(self, *args, **kwargs):
for key in self.__keys:
pyautogui.keyDown(str(key))
-
for key in self.__keys:
pyautogui.keyUp(str(key))
diff --git a/apparser/instructions/default/sleep.py b/apparser/instructions/default/sleep.py
index bd22f52..be5da03 100644
--- a/apparser/instructions/default/sleep.py
+++ b/apparser/instructions/default/sleep.py
@@ -6,11 +6,11 @@
class Sleep(BaseInstruction):
"""Pause execution for a fixed amount of time."""
- def __init__(self, sleep_time: float):
+ def __init__(self, sleep_time: float | int):
"""Initialize a sleep instruction.
:param sleep_time: Delay duration in seconds.
- :type sleep_time: float
+ :type sleep_time: float | int
:raises ValueError: If ``sleep_time`` is not greater than zero.
:raises TypeError: If ``sleep_time`` is not a number.
"""
diff --git a/apparser/instructions/ocr/click_on_text.py b/apparser/instructions/ocr/click_on_text.py
index 62c1a0b..dfb9292 100644
--- a/apparser/instructions/ocr/click_on_text.py
+++ b/apparser/instructions/ocr/click_on_text.py
@@ -29,7 +29,7 @@ def __init__(self, text: str,
:type min_similarity: float
:param offset: Offset relative to the detected text center.
:type offset: Point | RelativelyPoint
- :param text_getter: Instruction used to extract text from the screen. If None use GetText()
+ :param text_getter: Instruction used to extract text from the screen. If None, use GetText().
:type text_getter: GetText | None
:param sleep_time_after_move: Delay before the click is performed.
:type sleep_time_after_move: float
diff --git a/apparser/instructions/ocr/move_to_text.py b/apparser/instructions/ocr/move_to_text.py
index a909fc2..b98580f 100644
--- a/apparser/instructions/ocr/move_to_text.py
+++ b/apparser/instructions/ocr/move_to_text.py
@@ -26,7 +26,7 @@ def __init__(self, text: str,
:type min_similarity: float
:param offset: Offset relative to the detected text center.
:type offset: Point | RelativelyPoint
- :param text_getter: Instruction used to extract text from the screen. If None use GetText()
+ :param text_getter: Instruction used to extract text from the screen. If None, use GetText().
:type text_getter: GetText | None
"""
if text_getter is None:
@@ -58,11 +58,11 @@ def perform(self, ui: BaseUi, text_reader: BaseTextReader, *args, **kwargs):
needed_data, rating = self.find_text(self.__text_getter.local_answer)
if self.__min_similarity > rating:
raise TextNotFoundException(self.__min_similarity)
- y_cords = [needed_data.coordinates.right_top.y, needed_data.coordinates.right_bottom.y]
- x_cords = [needed_data.coordinates.left_top.x, needed_data.coordinates.right_top.x]
+ y_coords = [needed_data.coordinates.right_top.y, needed_data.coordinates.right_bottom.y]
+ x_coords = [needed_data.coordinates.left_top.x, needed_data.coordinates.right_top.x]
offset_point = self.__get_local_offset(ui)
- x_center = round((x_cords[0] - x_cords[1]) / 2 + x_cords[1]) + offset_point.x
- y_center = round((y_cords[0] - y_cords[1]) / 2 + y_cords[1]) + offset_point.y
+ x_center = round((x_coords[0] - x_coords[1]) / 2 + x_coords[1]) + offset_point.x
+ y_center = round((y_coords[0] - y_coords[1]) / 2 + y_coords[1]) + offset_point.y
MouseMove(Point(x_center, y_center)).perform(ui)
@property
diff --git a/apparser/instructions/ocr/plot_text.py b/apparser/instructions/ocr/plot_text.py
index 21af8d0..a309fe2 100644
--- a/apparser/instructions/ocr/plot_text.py
+++ b/apparser/instructions/ocr/plot_text.py
@@ -29,14 +29,14 @@ def __init__(self, draw: ImageDraw.Draw, color: Tuple[int, int, int, int],
def draw(self, bboxes: list[TextData]):
for data in bboxes:
- self.__paint_cords(data)
+ self.__paint_coords(data)
self.__paint_lines(data)
def __paint_lines(self, data: TextData):
shape = [(data.coordinates.left_top.x, data.coordinates.left_top.y), (data.coordinates.right_bottom.x, data.coordinates.right_bottom.y)]
self.__draw.rectangle(shape, outline=self.__color, width=1)
- def __paint_cords(self, data: TextData):
+ def __paint_coords(self, data: TextData):
y = data.coordinates.left_top.y + self.__text_move.y
if y < 0:
y = data.coordinates.right_bottom.y - self.__text_move.y
@@ -54,7 +54,7 @@ def __init__(self, text_getter: GetText | None = None,
text_move: Point = Point(0, 20)):
"""Initialize an OCR plotting instruction.
- :param text_getter: Instruction used to extract text from the screen. If None use GetText()
+ :param text_getter: Instruction used to extract text from the screen. If None, use GetText().
:type text_getter: GetText | None
:param color_rgba: RGBA color used for the rendered overlays.
:type color_rgba: tuple[int, int, int, int]
@@ -72,7 +72,7 @@ def id(self) -> int:
def perform(self, ui: BaseUi, text_reader: BaseTextReader, *args, **kwargs):
self.__text_getter.perform(ui, text_reader)
- texts = self.__text_getter.global_answer
+ texts = self.__text_getter.local_answer
image = self.__text_getter.screenshot
image = Image.fromarray(image)
draw = ImageDraw.Draw(image)
diff --git a/apparser/instructions/ocr/print_all_text.py b/apparser/instructions/ocr/print_all_text.py
index 9c78abb..a554803 100644
--- a/apparser/instructions/ocr/print_all_text.py
+++ b/apparser/instructions/ocr/print_all_text.py
@@ -12,7 +12,7 @@ class PrintAllText(OCRInstruction):
def __init__(self, text_getter: GetText | None = None):
"""Initialize an OCR text printing instruction.
- :param text_getter: Instruction used to extract text from the screen. If None use GetText()
+ :param text_getter: Instruction used to extract text from the screen. If None, use GetText().
:type text_getter: GetText | None
"""
if text_getter is None:
diff --git a/apparser/instructions/ocr/text_getter.py b/apparser/instructions/ocr/text_getter.py
index 8e2ab2c..5defacc 100644
--- a/apparser/instructions/ocr/text_getter.py
+++ b/apparser/instructions/ocr/text_getter.py
@@ -30,42 +30,43 @@ def __init__(self,
self.__reload_every_try = reload_every_try
self.__local_answer = []
self.__global_answer = []
- self.__left_top_point_global = left_top_point
+ self.__left_top_point_local = left_top_point
self.__screenshot = None
@property
def id(self) -> int:
return 2000
- def __text_coordinates_to_local(self, text: TextData) -> TextData:
+ @staticmethod
+ def __shift_text_coordinates(text: TextData, offset: Point) -> TextData:
new_coordinates = QuadPoints(
- text.coordinates.left_top + self.__left_top_point_global,
- text.coordinates.right_top + self.__left_top_point_global,
- text.coordinates.right_bottom + self.__left_top_point_global,
- text.coordinates.left_bottom + self.__left_top_point_global,
+ text.coordinates.left_top + offset,
+ text.coordinates.right_top + offset,
+ text.coordinates.right_bottom + offset,
+ text.coordinates.left_bottom + offset,
)
return TextData(text.text, new_coordinates)
- def __texts_coordinates_to_local(self, texts: list[TextData]) -> list[TextData]:
+ def __shift_texts_coordinates(self, texts: list[TextData], offset: Point) -> list[TextData]:
returned_data = []
for text in texts:
- returned_data.append(self.__text_coordinates_to_local(text))
+ returned_data.append(self.__shift_text_coordinates(text, offset))
return returned_data
def perform(self, ui: BaseUi, text_reader: BaseTextReader, *args, **kwargs):
if len(self.__local_answer) != 0 and not self.__reload_every_try:
return
right_bottom_point = ui.point_to_local(ui.point_to_global(self.__right_bottom_point))
- self.__left_top_point_global = ui.point_to_local(ui.point_to_global(self.__left_top_point))
+ self.__left_top_point_local = ui.point_to_local(ui.point_to_global(self.__left_top_point))
+ left_top_point_global = ui.point_to_global(self.__left_top_point_local)
screen = Image.fromarray(ui.get_screenshot())
- screen = screen.crop((self.__left_top_point_global.x, self.__left_top_point_global.y, right_bottom_point.x,
+ screen = screen.crop((self.__left_top_point_local.x, self.__left_top_point_local.y, right_bottom_point.x,
right_bottom_point.y))
screen = numpy.array(screen)
self.__screenshot = screen
ai_answer = text_reader.read_image(screen)
- self.__global_answer = ai_answer.copy()
- ai_answer = self.__texts_coordinates_to_local(ai_answer)
- self.__local_answer = ai_answer
+ self.__global_answer = self.__shift_texts_coordinates(ai_answer, left_top_point_global)
+ self.__local_answer = self.__shift_texts_coordinates(ai_answer, self.__left_top_point_local)
@property
def local_answer(self) -> list[TextData]:
diff --git a/apparser/instructions/ui/algorithms/ids.py b/apparser/instructions/ui/algorithms/ids.py
index 605294e..4004bd7 100644
--- a/apparser/instructions/ui/algorithms/ids.py
+++ b/apparser/instructions/ui/algorithms/ids.py
@@ -11,14 +11,17 @@
def _check_instruction(instruction: tuple[int, list[Any]]) -> tuple[int, list[Any]]:
if not isinstance(instruction, tuple):
- raise TypeError(f"{instruction} must be tuple")
+ raise TypeError(f"instruction must be tuple")
+
+ if len(instruction) < 2:
+ raise TypeError(f"instruction must have at least 2 arguments")
instruction_id, instruction_args = instruction
if not isinstance(instruction_id, int):
- raise TypeError(f"{instruction_id} must be int")
+ raise TypeError(f"instruction_id must be int")
if not isinstance(instruction_args, list):
- raise TypeError(f"{instruction_args} must be list")
+ raise TypeError(f"instruction_args must be list")
return instruction_id, instruction_args
diff --git a/apparser/instructions/ui/algorithms/names.py b/apparser/instructions/ui/algorithms/names.py
index 4bdae59..44ca2d1 100644
--- a/apparser/instructions/ui/algorithms/names.py
+++ b/apparser/instructions/ui/algorithms/names.py
@@ -12,14 +12,17 @@
def _check_instruction(instruction: tuple[str, list[Any]]) -> tuple[str, list[Any]]:
if not isinstance(instruction, tuple):
- raise TypeError(f"{instruction} must be tuple")
+ raise TypeError(f"instruction must be tuple")
+
+ if len(instruction) < 2:
+ raise TypeError(f"instruction must have at least 2 arguments")
instruction_name, instruction_args = instruction
if not isinstance(instruction_name, str):
- raise TypeError(f"{instruction_name} must be str")
+ raise TypeError(f"instruction_name must be str")
if not isinstance(instruction_args, list):
- raise TypeError(f"{instruction_args} must be list")
+ raise TypeError(f"instruction_args must be list")
return instruction_name, instruction_args
diff --git a/apparser/instructions/ui/algorithms/unique.py b/apparser/instructions/ui/algorithms/unique.py
index 2021a58..be5a62e 100644
--- a/apparser/instructions/ui/algorithms/unique.py
+++ b/apparser/instructions/ui/algorithms/unique.py
@@ -56,10 +56,8 @@ def __init__(self,
elif debugger == False:
debugger = None
- attributes.reverse()
-
self.__instructions = instructions
- self.__attributes = attributes
+ self.__attributes = list(reversed(attributes))
self.__debugger = debugger
def __form_args(self, instruction: BaseInstruction) -> dict[str, Any]:
diff --git a/apparser/instructions/ui/click.py b/apparser/instructions/ui/click.py
index 6356cb1..a121f53 100644
--- a/apparser/instructions/ui/click.py
+++ b/apparser/instructions/ui/click.py
@@ -23,11 +23,11 @@ def __init__(self, coordinates: Point | RelativelyPoint,
:type click_type: RightClick | LeftClick
:param mover: Mouse movement strategy used before the click.
:type mover: BaseMover
- :raises ValueError: If ``coordinates`` has an invalid type.
+ :raises TypeError: If ``coordinates`` has an invalid type.
"""
if (not isinstance(coordinates, Point)
and not isinstance(coordinates, RelativelyPoint)):
- raise ValueError('coordinates must be Point or RelativelyPoint')
+ raise TypeError('coordinates must be Point or RelativelyPoint')
self.__click = MouseClick(click_type)
self.__move = MouseMove(coordinates, mover=mover)
diff --git a/apparser/movers/default.py b/apparser/movers/default.py
index 3c65f10..45b4590 100644
--- a/apparser/movers/default.py
+++ b/apparser/movers/default.py
@@ -17,10 +17,10 @@ def __init__(self,
:raises ValueError: If ``duration`` is negative.
"""
if not (isinstance(duration, float) or isinstance(duration, int)):
- raise TypeError("Duration must be a number")
+ raise TypeError("duration must be a number")
if duration < 0:
- raise ValueError("Duration must be >= 0")
+ raise ValueError("duration must be >= 0")
self.__duration = duration
@@ -31,4 +31,4 @@ def move(self, position: Point):
:type position: Point
"""
pyautogui.moveTo(position.x, position.y,
- duration=self.__duration)
+ duration=self.__duration)
diff --git a/apparser/text_readers/readers/compound.py b/apparser/text_readers/readers/compound.py
index 9efdded..1314ddb 100644
--- a/apparser/text_readers/readers/compound.py
+++ b/apparser/text_readers/readers/compound.py
@@ -34,7 +34,7 @@ def _cut_by_coordinates(image: numpy.ndarray, coordinates: QuadPoints) -> numpy.
class CompoundReader(BaseTextReader):
"""Detect text regions and scan each detected image fragment."""
- def __init__(self, detector: BaseTextDetector, scanner: BaseTextScanner ):
+ def __init__(self, detector: BaseTextDetector, scanner: BaseTextScanner):
"""Initialize a compound text reader.
:param detector: Detector used to find text regions in an image.
@@ -62,7 +62,7 @@ def read_image(self, image: numpy.ndarray) -> list[TextData]:
"""
result = []
for coordinates in self.__detector.read_image(image):
- cuted_image = _cut_by_coordinates(image, coordinates)
- text = self.__scanner.read_image(cuted_image)
+ cropped_image = _cut_by_coordinates(image, coordinates)
+ text = self.__scanner.read_image(cropped_image)
result.append(TextData(text=text, coordinates=coordinates))
return result
diff --git a/docs/api/cv/events/index.rst b/docs/api/cv/events/index.rst
index 3a73949..b8663a4 100644
--- a/docs/api/cv/events/index.rst
+++ b/docs/api/cv/events/index.rst
@@ -16,7 +16,7 @@ events
:show-inheritance:
:member-order: bysource
-.. autoclass:: UnDetected
+.. autoclass:: Undetected
:members:
:undoc-members:
:show-inheritance:
diff --git a/docs/index.rst b/docs/index.rst
index c6d746e..62030a8 100644
--- a/docs/index.rst
+++ b/docs/index.rst
@@ -26,7 +26,7 @@ Repository on `GitHub `__
Donation
===========
-If you'd like to financially support the developers for their work:
+If you'd like to financially support the developers' work:
.. raw:: html
diff --git a/docs/info/about.rst b/docs/info/about.rst
index 2826f6e..3fd71be 100644
--- a/docs/info/about.rst
+++ b/docs/info/about.rst
@@ -27,7 +27,7 @@ Repository on `GitHub `__
Donation
----------
-If you'd like to financially support the developers for their work:
+If you'd like to financially support the developers' work:
.. raw:: html
diff --git a/pyproject.toml b/pyproject.toml
index 99d3528..d009d10 100644
--- a/pyproject.toml
+++ b/pyproject.toml
@@ -10,11 +10,11 @@ dependencies = [
"onnxruntime>=1.20.1"
]
name = "apparser"
-version = "1.1.1"
+version = "1.1.2"
authors = [
{ name = "Terochkin A.S", email = "apparser.development@gmail.com" },
]
-description = "Apparser is a Python library designed for automating desktop applications and managing UI interfaces using artificial intelligence, such as OCR or object detection models. "
+description = "Apparser is a Python library for automating desktop applications and interacting with UIs using AI-powered tools such as OCR and object detection models."
readme = "README.md"
requires-python = ">=3.10"
classifiers = [
diff --git a/tests/apparser/cv/events/test_undetected.py b/tests/apparser/cv/events/test_undetected.py
index 52cbb37..b90286b 100644
--- a/tests/apparser/cv/events/test_undetected.py
+++ b/tests/apparser/cv/events/test_undetected.py
@@ -4,4 +4,4 @@
def test_undetected_string_representation() -> None:
- assert str(Undetected()) == "UnDetected"
+ assert str(Undetected()) == "Undetected"
diff --git a/tests/apparser/exceptions/test_text_not_found.py b/tests/apparser/exceptions/test_text_not_found.py
index 931c684..c5af75d 100644
--- a/tests/apparser/exceptions/test_text_not_found.py
+++ b/tests/apparser/exceptions/test_text_not_found.py
@@ -7,13 +7,14 @@
from apparser.exceptions.text_not_found import TextNotFoundException
-def test_text_not_found_accepts_valid_similarity() -> None:
- error = TextNotFoundException(0.5)
+@pytest.mark.parametrize("value", [0, 0.5, 1])
+def test_text_not_found_accepts_valid_similarity(value: float | int) -> None:
+ error = TextNotFoundException(value)
assert isinstance(error, Exception)
-@pytest.mark.parametrize("value", [0, "0.5", None, object()])
+@pytest.mark.parametrize("value", ["0.5", None, object()])
def test_text_not_found_rejects_invalid_similarity_type(value: Any) -> None:
with pytest.raises(TypeError):
TextNotFoundException(value)
diff --git a/tests/apparser/instructions/default/test_press.py b/tests/apparser/instructions/default/test_press.py
index f9b496a..db9f9e6 100644
--- a/tests/apparser/instructions/default/test_press.py
+++ b/tests/apparser/instructions/default/test_press.py
@@ -32,6 +32,18 @@ def test_press_keys_combination_presses_and_releases_keys() -> None:
assert instruction.id == 3
+def test_press_keys_combination_treats_string_as_one_key() -> None:
+ instruction = PressKeysCombination(["ctrl"])
+ instruction.perform()
+ assert pyautogui_stub.press_calls == ["ctrl"]
+ assert pyautogui_stub.release_calls == ["ctrl"]
+
+
+def test_press_keys_combination_rejects_invalid_keys_type() -> None:
+ with pytest.raises(TypeError):
+ PressKeysCombination(object())
+
+
def test_press_keys_combination_rejects_invalid_key_on_perform() -> None:
with pytest.raises(TypeError):
PressKeysCombination([object()])
diff --git a/tests/apparser/instructions/ocr/test_plot_text.py b/tests/apparser/instructions/ocr/test_plot_text.py
index 9cea6b6..f4b2d9d 100644
--- a/tests/apparser/instructions/ocr/test_plot_text.py
+++ b/tests/apparser/instructions/ocr/test_plot_text.py
@@ -37,6 +37,6 @@ def test_plot_all_text_draws_and_shows_image(monkeypatch: pytest.MonkeyPatch) ->
instruction.perform(FakeUi(), FakeTextReader())
- assert painter_calls == [getter.global_answer]
+ assert painter_calls == [getter.local_answer]
assert shown == [True]
assert instruction.id == 2004
diff --git a/tests/apparser/instructions/ocr/test_text_getter.py b/tests/apparser/instructions/ocr/test_text_getter.py
index d16ba0c..06ee4c6 100644
--- a/tests/apparser/instructions/ocr/test_text_getter.py
+++ b/tests/apparser/instructions/ocr/test_text_getter.py
@@ -20,12 +20,12 @@ def test_text_getter_reads_and_converts_coordinates() -> None:
]
)
instruction = GetText(Point(1, 1), Point(4, 3))
- ui = FakeUi(screenshot=screenshot)
+ ui = FakeUi(offset=Point(10, 20), screenshot=screenshot)
instruction.perform(ui, reader)
assert reader.images[0].shape == (2, 3, 3)
- assert instruction.global_answer[0].coordinates.left_top == Point(0, 0)
+ assert instruction.global_answer[0].coordinates.left_top == Point(11, 21)
assert instruction.local_answer[0].coordinates.left_top == Point(1, 1)
assert instruction.screenshot.shape == (2, 3, 3)
diff --git a/tests/apparser/instructions/ui/algorithms/test_unique.py b/tests/apparser/instructions/ui/algorithms/test_unique.py
index 83b960d..be86d15 100644
--- a/tests/apparser/instructions/ui/algorithms/test_unique.py
+++ b/tests/apparser/instructions/ui/algorithms/test_unique.py
@@ -29,6 +29,14 @@ def test_unique_algorithm_injects_attributes_by_type() -> None:
assert second.calls == [{"args": (ui,), "kwargs": {"name": "alex"}}]
+def test_unique_algorithm_does_not_mutate_attributes() -> None:
+ attributes = [10, "alex"]
+
+ UniqueAlgorithm([], attributes=attributes, debugger=False)
+
+ assert attributes == [10, "alex"]
+
+
def test_unique_algorithm_uses_debugger() -> None:
debugger = FakeDebugger(call_inner=False)
instruction = FakeIntAttributeInstruction(1)
diff --git a/tests/apparser/instructions/ui/test_click.py b/tests/apparser/instructions/ui/test_click.py
index d1c75fb..e0740c8 100644
--- a/tests/apparser/instructions/ui/test_click.py
+++ b/tests/apparser/instructions/ui/test_click.py
@@ -10,7 +10,7 @@
def test_mouse_click_to_rejects_invalid_coordinates() -> None:
- with pytest.raises(ValueError):
+ with pytest.raises(TypeError):
MouseClickTo(object())