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())