Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

robot: build a WaylandRobot wrapper #92

Closed
wants to merge 6 commits into from
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
29 changes: 27 additions & 2 deletions mir-ci/mir_ci/apps.py
Original file line number Diff line number Diff line change
@@ -1,8 +1,9 @@
from textwrap import dedent
from typing import Any, Collection, Literal, Optional, Union

import pytest

AppType = Literal["snap", "deb", "pip"]
AppType = Optional[Literal["snap", "deb", "pip"]]


class App:
Expand All @@ -16,7 +17,7 @@ def __init__(self, command: Collection[str], app_type: Optional[AppType] = None)

def _dependency(
cmd: Collection[str],
app_type: AppType,
app_type: AppType = None,
snap: Optional[str] = None,
channel: str = "latest/stable",
debs: Collection[str] = (),
Expand Down Expand Up @@ -63,6 +64,30 @@ def qterminal(*args: str, debs: Collection[str] = ("qterminal", "qtwayland5"), m
return deb("qterminal", *args, debs=debs, marks=marks, **kwargs)


def gtkapp(*cmd, marks=(), **kwargs):
marks = (
pytest.mark.xdg(
XDG_CONFIG_HOME={
"glib-2.0/settings/keyfile": dedent(
"""\
[org/gnome/desktop/interface]
color-scheme='prefer-light'
gtk-theme='Adwaita'
icon-theme='Adwaita'
font-name='Ubuntu 11'
cursor-theme='Adwaita'
cursor-size=24
font-antialiasing='grayscale'
"""
),
},
),
pytest.mark.env(GSETTINGS_BACKEND="keyfile"),
*marks,
)
return _dependency(cmd=cmd, marks=marks, **kwargs)


def pluma(*args: str, **kwargs):
return deb("pluma", *args, **kwargs)

Expand Down
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
38 changes: 38 additions & 0 deletions mir-ci/mir_ci/robot_wrapper.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,38 @@
from pathlib import Path

from mir_ci import apps
from mir_ci.screencopy_tracker import ScreencopyTracker
from mir_ci.virtual_pointer import VirtualPointer

MIR_CI_PATH = Path(__file__).parent
ROBOT_RESOURCES = MIR_CI_PATH / "robot_resources"

ROBOT_TEMPLATE = """\
*** Settings ***
{settings}

*** Variables ***
{variables}

*** Test Cases ***
{test_cases}
"""

WAYLAND_SETTINGS = f"Resource {ROBOT_RESOURCES}/screencopy.resource"
WAYLAND_VARIABLES = f"${{template_path}}= {MIR_CI_PATH}/robot_templates"


class Robot(apps.App):
def __init__(self, request, test_cases, settings="", variables=""):
tmp_path = request.getfixturevalue("tmp_path")
procedure = tmp_path / f"{request.node.name}.robot"

Check warning on line 28 in mir-ci/mir_ci/robot_wrapper.py

View check run for this annotation

Codecov / codecov/patch

mir-ci/mir_ci/robot_wrapper.py#L27-L28

Added lines #L27 - L28 were not covered by tests
with open(procedure, "w") as f:
f.write(ROBOT_TEMPLATE.format(test_cases=test_cases, settings=settings, variables=variables))
super().__init__(("robot", "--outputdir", tmp_path, procedure))

Check warning on line 31 in mir-ci/mir_ci/robot_wrapper.py

View check run for this annotation

Codecov / codecov/patch

mir-ci/mir_ci/robot_wrapper.py#L30-L31

Added lines #L30 - L31 were not covered by tests


class WaylandRobot(Robot):
required_extensions = VirtualPointer.required_extensions + ScreencopyTracker.required_extensions

def __init__(self, request, test_cases, settings="", variables=""):
super().__init__(request, test_cases, f"{WAYLAND_SETTINGS}\n{settings}", f"{WAYLAND_VARIABLES}\n{variables}")

Check warning on line 38 in mir-ci/mir_ci/robot_wrapper.py

View check run for this annotation

Codecov / codecov/patch

mir-ci/mir_ci/robot_wrapper.py#L38

Added line #L38 was not covered by tests
97 changes: 27 additions & 70 deletions mir-ci/mir_ci/test_drag_and_drop.py
Original file line number Diff line number Diff line change
@@ -1,51 +1,16 @@
import tempfile
from pathlib import Path
from textwrap import dedent

import pytest
from mir_ci import apps
from mir_ci.display_server import DisplayServer
from mir_ci.robot_wrapper import WaylandRobot
from mir_ci.screencopy_tracker import ScreencopyTracker
from mir_ci.virtual_pointer import VirtualPointer

MIR_CI_PATH = Path(__file__).parent
APP_PATH = MIR_CI_PATH / "clients/drag_and_drop_demo.py"
APP_PATH = Path(__file__).parent / "clients/drag_and_drop_demo.py"

ROBOT_TEMPLATE = """\
*** Settings ***
{settings}

*** Variables ***
{variables}

*** Test Cases ***
{test_case}
"""
ROBOT_SETTINGS = f"Resource {MIR_CI_PATH}/robot_resources/screencopy.resource"
ROBOT_VARIABLES = f"""\
${{SRC_TEMPLATE}} {MIR_CI_PATH}/robot_templates/drag_and_drop_src.png
${{DST_TEMPLATE}} {MIR_CI_PATH}/robot_templates/drag_and_drop_dst.png
${{END_TEMPLATE}} {MIR_CI_PATH}/robot_templates/drag_and_drop_end.png
"""


@pytest.mark.xdg(
XDG_CONFIG_HOME={
"glib-2.0/settings/keyfile": dedent(
"""\
[org/gnome/desktop/interface]
color-scheme='prefer-light'
gtk-theme='Adwaita'
icon-theme='Adwaita'
font-name='Ubuntu 11'
cursor-theme='Adwaita'
cursor-size=24
font-antialiasing='grayscale'
"""
),
},
)
@pytest.mark.env(GSETTINGS_BACKEND="keyfile")
@pytest.mark.parametrize(
"modern_server",
[
Expand Down Expand Up @@ -74,68 +39,60 @@ class TestDragAndDrop:
@pytest.mark.parametrize(
"app",
[
("python3", APP_PATH, "--source", "pixbuf", "--target", "pixbuf", "--expect", "pixbuf"),
("python3", APP_PATH, "--source", "text", "--target", "text", "--expect", "text"),
apps.gtkapp("python3", APP_PATH, "--source", "pixbuf", "--target", "pixbuf", "--expect", "pixbuf"),
apps.gtkapp("python3", APP_PATH, "--source", "text", "--target", "text", "--expect", "text"),
],
)
async def test_source_and_dest_match(self, modern_server, app, tmp_path) -> None:
async def test_source_and_dest_match(self, modern_server, app, request) -> None:
extensions = VirtualPointer.required_extensions + ScreencopyTracker.required_extensions
server_instance = DisplayServer(modern_server, add_extensions=extensions)
program = server_instance.program(apps.App(app))
program = server_instance.program(app)

robot_test_case = dedent(
"""\
Source and Destination Match
${pos}= Move Pointer To ${SRC_TEMPLATE}
${pos}= Move Pointer To ${template_path}/drag_and_drop_src.png
Press LEFT Button
Walk Pointer From ${pos} To ${DST_TEMPLATE}
Walk Pointer From ${pos} To ${template_path}/drag_and_drop_dst.png
Release LEFT Button
"""
)

with tempfile.NamedTemporaryFile(mode="w+", suffix=".robot", buffering=1) as robot_file:
robot_file.write(
ROBOT_TEMPLATE.format(settings=ROBOT_SETTINGS, variables=ROBOT_VARIABLES, test_case=robot_test_case)
)
robot = server_instance.program(apps.App(("robot", "-d", tmp_path, robot_file.name)))
robot = server_instance.program(WaylandRobot(request, robot_test_case))

async with server_instance, program, robot:
await robot.wait(60)
await program.wait()
async with server_instance, program, robot:
await robot.wait(60)
await program.wait()

@pytest.mark.parametrize(
"app",
[
("python3", "-u", APP_PATH, "--source", "pixbuf", "--target", "text", "--expect", "pixbuf"),
("python3", "-u", APP_PATH, "--source", "text", "--target", "pixbuf", "--expect", "text"),
("python3", "-u", APP_PATH, "--source", "pixbuf", "--target", "text", "--expect", "text"),
("python3", "-u", APP_PATH, "--source", "text", "--target", "pixbuf", "--expect", "pixbuf"),
apps.gtkapp("python3", "-u", APP_PATH, "--source", "pixbuf", "--target", "text", "--expect", "pixbuf"),
apps.gtkapp("python3", "-u", APP_PATH, "--source", "text", "--target", "pixbuf", "--expect", "text"),
apps.gtkapp("python3", "-u", APP_PATH, "--source", "pixbuf", "--target", "text", "--expect", "text"),
apps.gtkapp("python3", "-u", APP_PATH, "--source", "text", "--target", "pixbuf", "--expect", "pixbuf"),
],
)
async def test_source_and_dest_mismatch(self, modern_server, app, tmp_path) -> None:
async def test_source_and_dest_mismatch(self, modern_server, app, request) -> None:
extensions = VirtualPointer.required_extensions + ScreencopyTracker.required_extensions
server_instance = DisplayServer(modern_server, add_extensions=extensions)
program = server_instance.program(apps.App(app))
program = server_instance.program(app)

robot_test_case = dedent(
"""\
Source and Destination Mismatch
${pos}= Move Pointer To ${SRC_TEMPLATE}
${pos}= Move Pointer To ${template_path}/drag_and_drop_src.png
Press LEFT Button
${pos}= Walk Pointer From ${pos} To ${DST_TEMPLATE}
${pos}= Walk Pointer From ${pos} To ${template_path}/drag_and_drop_dst.png
Release LEFT Button
Walk Pointer From ${pos} To ${END_TEMPLATE}
Walk Pointer From ${pos} To ${template_path}/drag_and_drop_end.png
"""
)

with tempfile.NamedTemporaryFile(mode="w+", suffix=".robot", buffering=1) as robot_file:
robot_file.write(
ROBOT_TEMPLATE.format(settings=ROBOT_SETTINGS, variables=ROBOT_VARIABLES, test_case=robot_test_case)
)
robot = server_instance.program(apps.App(("robot", "-d", tmp_path, robot_file.name)))
robot = server_instance.program(WaylandRobot(request, robot_test_case))

async with server_instance, program, robot:
await robot.wait(60)
assert program.is_running()
await program.kill()
assert "drag-begin\ndrag-failed\nenter-notify-event: dropbox" in program.output
async with server_instance, program, robot:
await robot.wait(60)
assert program.is_running()
await program.kill()
assert "drag-begin\ndrag-failed\nenter-notify-event: dropbox" in program.output
58 changes: 38 additions & 20 deletions mir-ci/mir_ci/test_screencopy_bandwidth.py
Original file line number Diff line number Diff line change
@@ -1,12 +1,13 @@
import asyncio
import os
from pathlib import Path
from textwrap import dedent

import pytest
from mir_ci import SLOWDOWN, apps
from mir_ci.display_server import DisplayServer
from mir_ci.robot_wrapper import WaylandRobot
from mir_ci.screencopy_tracker import ScreencopyTracker
from mir_ci.virtual_pointer import Button, VirtualPointer

long_wait_time = 10

Expand Down Expand Up @@ -68,7 +69,15 @@ async def test_inactive_app(self, record_property, server, app) -> None:
await asyncio.sleep(long_wait_time)
_record_properties(record_property, server, tracker, 2)

@pytest.mark.deps(debs=("libgtk-4-dev",), pip_pkgs=(("pygobject", "gi"),))
@pytest.mark.deps(
debs=("libgtk-4-dev",),
pip_pkgs=(
("pygobject", "gi"),
("robotframework~=6.1.1", "robot"),
("rpaframework", "RPA"),
("rpaframework-recognition", "RPA.recognition"),
),
)
@pytest.mark.parametrize(
"local_server",
[
Expand All @@ -77,24 +86,33 @@ async def test_inactive_app(self, record_property, server, app) -> None:
apps.mir_demo_server(),
],
)
async def test_app_dragged_around(self, record_property, local_server) -> None:
async def pause():
await asyncio.sleep(0.2)

extensions = ScreencopyTracker.required_extensions + VirtualPointer.required_extensions
app_path = Path(__file__).parent / "clients" / "maximizing_gtk_app.py"
@pytest.mark.parametrize(
"client",
[apps.gtkapp("python3", Path(__file__).parent / "clients" / "maximizing_gtk_app.py")],
)
async def test_app_dragged_around(self, record_property, local_server, client, request) -> None:
extensions = ScreencopyTracker.required_extensions + WaylandRobot.required_extensions
server = DisplayServer(local_server, add_extensions=extensions)
app = server.program(apps.App(("python3", str(app_path))))
tracker = ScreencopyTracker(server.display_name)
pointer = VirtualPointer(server.display_name)
async with server, tracker, app, pointer:
await asyncio.sleep(2 * SLOWDOWN) # TODO: detect when the window is drawn instead
pointer.move_to_absolute(pointer.output_width / 2, 10)
await pause()
pointer.button(Button.LEFT, True)
await pause()
for y in range(4):
for x in range(4):
pointer.move_to_proportional((x + 0.5) / 4, (y + 0.5) / 4)
await pause()

robot_test_case = dedent(
"""\
Drag app
${pos}= Move Pointer To Template ${template_path}/dragged_app_titlebar.png
Press LEFT Button
FOR ${y} IN RANGE 4
FOR ${x} IN RANGE 4
${px} evaluate ($x + 0.5) / 4
${py} evaluate ($y + 0.5) / 4
Move Pointer To Proportional ${px} ${py}
Sleep 0.2
END
END
"""
)

robot = server.program(WaylandRobot(request, robot_test_case))

async with server, tracker, server.program(client), robot:
await robot.wait()
_record_properties(record_property, server, tracker, 16)
Loading