We are currently solving the following issue within our repository. Here is the issue text:
--- BEGIN ISSUE ---
Testing Using Pilot - `pilot.click()` Doesn't Have Effect On `Static` Widget
Hi,

I am trying to write tests for the example calculator app present in the repo (at the same time, expanding it to add a few more operators). This is my `CalculatorApp` class:

```python
class CalculatorApp(App):
    CSS_PATH = "calculator.css"

    viewport = var("0")
    show_ac = var(True)
    left = var(Decimal("0"))
    right = var(Decimal("0"))
    value = var("")
    operator = var("plus")

    def watch_viewport(self, value: str) -> None:
        self.query_one("#viewport", Static).update(value)

    def compute_show_ac(self) -> bool:
        return self.value in ("", "0") and self.viewport == "0"

    def watch_show_ac(self, show_ac: bool) -> None:
        self.query_one("#ac").display = show_ac
        self.query_one("#c").display = not show_ac

    def compose(self) -> ComposeResult:
        with Container(id="calculator"):
            yield Static(id="viewport")
            yield Button("AC", id="ac", variant="primary")
            yield Button("C", id="c", variant="primary")
            yield Button("+/-", id="negation", variant="primary")
            yield Button("%", id="percent", variant="primary")
            yield Button("sin(x)", id="sine", variant="warning")
            yield Button("cos(x)", id="cosine", variant="warning")
            yield Button("7", id="number-7", variant="primary")
            yield Button("8", id="number-8", variant="primary")
            yield Button("9", id="number-9", variant="primary")
            yield Button("+", id="plus", variant="warning")
            yield Button("x^y", id="exponent", variant="warning")
            yield Button("4", id="number-4", variant="primary")
            yield Button("5", id="number-5", variant="primary")
            yield Button("6", id="number-6", variant="primary")
            yield Button("-", id="minus", variant="warning")
            yield Button("ln(x)", id="logarithm", variant="warning")
            yield Button("1", id="number-1", variant="primary")
            yield Button("2", id="number-2", variant="primary")
            yield Button("3", id="number-3", variant="primary")
            yield Button("*", id="multiply", variant="warning")
            yield Button("x!", id="factorial", variant="warning")
            yield Button("0", id="number-0", variant="primary")
            yield Button(".", id="point", variant="primary")
            yield Button("÷", id="divide", variant="warning")
            yield Button("=", id="equals", variant="warning")

    def on_button_pressed(self, event: Button.Pressed) -> None:
        button_id = event.button.id

        assert button_id is not None

        if button_id.startswith("number-"):
            number = button_id.split("-")[-1]
            self.viewport = self.value = self.value.lstrip("0") + number
```

I wrote the following test to check that clicking the number buttons results in the calculator's display (`Static(id="viewport")`) accumulating digits to make a number:

```python
async def test_number_buttons():
    async with CalculatorApp().run_test() as pilot:
        app = pilot.app

        await pilot.click("#number-1")

        display_content = app.query_one("#viewport").render()
        assert str(display_content) == "1"

        await pilot.click("#number-2")

        display_content = app.query_one("#viewport").render()
        assert str(display_content) == "12"

        await pilot.click("#number-3")

        display_content = app.query_one("#viewport").render()
        assert str(display_content) == "123"
```

While the GUI gets updated correctly on clicking the buttons, the test always fails since `app.query_one("#viewport").render()` always returns `"0"`. I've also tried replacing `app.query_one("#viewport").render()` with `app.query_one("#viewport", Static).render()` but that hasn't helped either.

Is this supposed to happen?

# Textual Diagnostics

## Versions

| Name    | Value  |
|---------|--------|
| Textual | 0.14.0 |
| Rich    | 13.3.2 |

## Python

| Name           | Value                                         |
|----------------|-----------------------------------------------|
| Version        | 3.10.9                                        |
| Implementation | CPython                                       |
| Compiler       | GCC 11.2.0                                    |
| Executable     | /home/malay_agr/anaconda3/envs/spe/bin/python |

## Operating System

| Name    | Value                                       |
|---------|---------------------------------------------|
| System  | Linux                                       |
| Release | 5.15.0-52-generic                           |
| Version | #58-Ubuntu SMP Thu Oct 13 08:03:55 UTC 2022 |

## Terminal

| Name                 | Value           |
|----------------------|-----------------|
| Terminal Application | vscode (1.76.1) |
| TERM                 | xterm-256color  |
| COLORTERM            | truecolor       |
| FORCE_COLOR          | *Not set*       |
| NO_COLOR             | *Not set*       |

## Rich Console options

| Name           | Value                |
|----------------|----------------------|
| size           | width=197, height=19 |
| legacy_windows | False                |
| min_width      | 1                    |
| max_width      | 197                  |
| is_terminal    | True                 |
| encoding       | utf-8                |
| max_height     | 19                   |
| justify        | None                 |
| overflow       | None                 |
| no_wrap        | False                |
| highlight      | None                 |
| markup         | None                 |
| height         | None                 |


--- END ISSUE ---

Below are some code segments, each from a relevant file. One or more of these files may contain bugs.
--- BEGIN FILES ---
Path: `src/textual/pilot.py`
Content:
```
1 from __future__ import annotations
2 
3 import asyncio
4 from typing import Any, Generic
5 
6 import rich.repr
7 
8 from ._wait import wait_for_idle
9 from .app import App, ReturnType
10 from .css.query import QueryType
11 from .events import Click, MouseDown, MouseMove, MouseUp
12 from .geometry import Offset
13 from .widget import Widget
14 
15 
16 def _get_mouse_message_arguments(
17     target: Widget, offset: Offset = Offset(), button: int = 0
18 ) -> dict[str, Any]:
19     """Get the arguments to pass into mouse messages for the click and hover methods."""
20     x, y = offset
21     click_x, click_y, _, _ = target.region.translate(offset)
22     message_arguments = {
23         "x": x,
24         "y": y,
25         "delta_x": 0,
26         "delta_y": 0,
27         "button": button,
28         "shift": False,
29         "meta": False,
30         "ctrl": False,
31         "screen_x": click_x,
32         "screen_y": click_y,
33     }
34     return message_arguments
35 
36 
37 @rich.repr.auto(angular=True)
38 class Pilot(Generic[ReturnType]):
39     """Pilot object to drive an app."""
40 
41     def __init__(self, app: App[ReturnType]) -> None:
42         self._app = app
43 
44     def __rich_repr__(self) -> rich.repr.Result:
45         yield "app", self._app
46 
47     @property
48     def app(self) -> App[ReturnType]:
49         """App: A reference to the application."""
50         return self._app
51 
52     async def press(self, *keys: str) -> None:
53         """Simulate key-presses.
54 
55         Args:
56             *keys: Keys to press.
57 
58         """
59         if keys:
60             await self._app._press_keys(keys)
61 
62     async def click(
63         self, selector: QueryType | None = None, offset: Offset = Offset()
64     ) -> None:
65         """Simulate clicking with the mouse.
66 
67         Args:
68             selector: The widget that should be clicked. If None, then the click
69                 will occur relative to the screen. Note that this simply causes
70                 a click to occur at the location of the widget. If the widget is
71                 currently hidden or obscured by another widget, then the click may
72                 not land on it.
73             offset: The offset to click within the selected widget.
74         """
75         app = self.app
76         screen = app.screen
77         if selector is not None:
78             target_widget = screen.query_one(selector)
79         else:
80             target_widget = screen
81 
82         message_arguments = _get_mouse_message_arguments(
83             target_widget, offset, button=1
84         )
85         app.post_message(MouseDown(**message_arguments))
86         app.post_message(MouseUp(**message_arguments))
87         app.post_message(Click(**message_arguments))
88         await self.pause()
89 
90     async def hover(
91         self, selector: QueryType | None = None, offset: Offset = Offset()
92     ) -> None:
93         """Simulate hovering with the mouse cursor.
94 
95         Args:
96             selector: The widget that should be hovered. If None, then the click
97                 will occur relative to the screen. Note that this simply causes
98                 a hover to occur at the location of the widget. If the widget is
99                 currently hidden or obscured by another widget, then the hover may
100                 not land on it.
101             offset: The offset to hover over within the selected widget.
102         """
103         app = self.app
104         screen = app.screen
105         if selector is not None:
106             target_widget = screen.query_one(selector)
107         else:
108             target_widget = screen
109 
110         message_arguments = _get_mouse_message_arguments(
111             target_widget, offset, button=0
112         )
113         app.post_message(MouseMove(**message_arguments))
114         await self.pause()
115 
116     async def pause(self, delay: float | None = None) -> None:
117         """Insert a pause.
118 
119         Args:
120             delay: Seconds to pause, or None to wait for cpu idle.
121         """
122         # These sleep zeros, are to force asyncio to give up a time-slice,
123         if delay is None:
124             await wait_for_idle(0)
125         else:
126             await asyncio.sleep(delay)
127 
128     async def wait_for_animation(self) -> None:
129         """Wait for any current animation to complete."""
130         await self._app.animator.wait_for_idle()
131 
132     async def wait_for_scheduled_animations(self) -> None:
133         """Wait for any current and scheduled animations to complete."""
134         await self._app.animator.wait_until_complete()
135         await wait_for_idle()
136 
137     async def exit(self, result: ReturnType) -> None:
138         """Exit the app with the given result.
139 
140         Args:
141             result: The app result returned by `run` or `run_async`.
142         """
143         await wait_for_idle()
144         self.app.exit(result)
145
```
--- END FILES ---

Please first localize the bug based on the issue statement, and then generate a patch according to the `git diff` format fenced by three backticks.

Here is an example:

```diff
diff --git a/examples/server_async.py b/examples/server_async.py
--- a/examples/server_async.py
+++ b/examples/server_async.py
@@ -313,4 +313,4 @@
 
 
 if __name__ == "__main__":
-    asyncio.run(run_async_server("."), debug=True)
+    asyncio.run(run_async_server(), debug=True)
diff --git a/examples/server_sync.py b/examples/server_sync.py
--- a/examples/server_sync.py
+++ b/examples/server_sync.py
@@ -313,5 +313,5 @@
 
 
 if __name__ == "__main__":
-    server = run_sync_server(".")
+    server = run_sync_server()
     server.shutdown()

```

diff --git a/src/textual/pilot.py b/src/textual/pilot.py
--- a/src/textual/pilot.py
+++ b/src/textual/pilot.py
@@ -14,20 +14,24 @@
 
 
 def _get_mouse_message_arguments(
-    target: Widget, offset: Offset = Offset(), button: int = 0
+    target: Widget,
+    offset: Offset = Offset(),
+    button: int = 0,
+    shift: bool = False,
+    meta: bool = False,
+    control: bool = False,
 ) -> dict[str, Any]:
     """Get the arguments to pass into mouse messages for the click and hover methods."""
-    x, y = offset
     click_x, click_y, _, _ = target.region.translate(offset)
     message_arguments = {
-        "x": x,
-        "y": y,
+        "x": click_x,
+        "y": click_y,
         "delta_x": 0,
         "delta_y": 0,
         "button": button,
-        "shift": False,
-        "meta": False,
-        "ctrl": False,
+        "shift": shift,
+        "meta": meta,
+        "ctrl": control,
         "screen_x": click_x,
         "screen_y": click_y,
     }
@@ -60,7 +64,12 @@
             await self._app._press_keys(keys)
 
     async def click(
-        self, selector: QueryType | None = None, offset: Offset = Offset()
+        self,
+        selector: QueryType | None = None,
+        offset: Offset = Offset(),
+        shift: bool = False,
+        meta: bool = False,
+        control: bool = False,
     ) -> None:
         """Simulate clicking with the mouse.
 
@@ -71,6 +80,9 @@
                 currently hidden or obscured by another widget, then the click may
                 not land on it.
             offset: The offset to click within the selected widget.
+            shift: Click with the shift key held down.
+            meta: Click with the meta key held down.
+            control: Click with the control key held down.
         """
         app = self.app
         screen = app.screen
@@ -80,7 +92,7 @@
             target_widget = screen
 
         message_arguments = _get_mouse_message_arguments(
-            target_widget, offset, button=1
+            target_widget, offset, button=1, shift=shift, meta=meta, control=control
         )
         app.post_message(MouseDown(**message_arguments))
         app.post_message(MouseUp(**message_arguments))

{"golden_diff": "diff --git a/src/textual/pilot.py b/src/textual/pilot.py\n--- a/src/textual/pilot.py\n+++ b/src/textual/pilot.py\n@@ -14,20 +14,24 @@\n \n \n def _get_mouse_message_arguments(\n-    target: Widget, offset: Offset = Offset(), button: int = 0\n+    target: Widget,\n+    offset: Offset = Offset(),\n+    button: int = 0,\n+    shift: bool = False,\n+    meta: bool = False,\n+    control: bool = False,\n ) -> dict[str, Any]:\n     \"\"\"Get the arguments to pass into mouse messages for the click and hover methods.\"\"\"\n-    x, y = offset\n     click_x, click_y, _, _ = target.region.translate(offset)\n     message_arguments = {\n-        \"x\": x,\n-        \"y\": y,\n+        \"x\": click_x,\n+        \"y\": click_y,\n         \"delta_x\": 0,\n         \"delta_y\": 0,\n         \"button\": button,\n-        \"shift\": False,\n-        \"meta\": False,\n-        \"ctrl\": False,\n+        \"shift\": shift,\n+        \"meta\": meta,\n+        \"ctrl\": control,\n         \"screen_x\": click_x,\n         \"screen_y\": click_y,\n     }\n@@ -60,7 +64,12 @@\n             await self._app._press_keys(keys)\n \n     async def click(\n-        self, selector: QueryType | None = None, offset: Offset = Offset()\n+        self,\n+        selector: QueryType | None = None,\n+        offset: Offset = Offset(),\n+        shift: bool = False,\n+        meta: bool = False,\n+        control: bool = False,\n     ) -> None:\n         \"\"\"Simulate clicking with the mouse.\n \n@@ -71,6 +80,9 @@\n                 currently hidden or obscured by another widget, then the click may\n                 not land on it.\n             offset: The offset to click within the selected widget.\n+            shift: Click with the shift key held down.\n+            meta: Click with the meta key held down.\n+            control: Click with the control key held down.\n         \"\"\"\n         app = self.app\n         screen = app.screen\n@@ -80,7 +92,7 @@\n             target_widget = screen\n \n         message_arguments = _get_mouse_message_arguments(\n-            target_widget, offset, button=1\n+            target_widget, offset, button=1, shift=shift, meta=meta, control=control\n         )\n         app.post_message(MouseDown(**message_arguments))\n         app.post_message(MouseUp(**message_arguments))\n", "issue": "Testing Using Pilot - `pilot.click()` Doesn't Have Effect On `Static` Widget\nHi,\r\n\r\nI am trying to write tests for the example calculator app present in the repo (at the same time, expanding it to add a few more operators). This is my `CalculatorApp` class:\r\n\r\n```python\r\nclass CalculatorApp(App):\r\n    CSS_PATH = \"calculator.css\"\r\n\r\n    viewport = var(\"0\")\r\n    show_ac = var(True)\r\n    left = var(Decimal(\"0\"))\r\n    right = var(Decimal(\"0\"))\r\n    value = var(\"\")\r\n    operator = var(\"plus\")\r\n\r\n    def watch_viewport(self, value: str) -> None:\r\n        self.query_one(\"#viewport\", Static).update(value)\r\n\r\n    def compute_show_ac(self) -> bool:\r\n        return self.value in (\"\", \"0\") and self.viewport == \"0\"\r\n\r\n    def watch_show_ac(self, show_ac: bool) -> None:\r\n        self.query_one(\"#ac\").display = show_ac\r\n        self.query_one(\"#c\").display = not show_ac\r\n\r\n    def compose(self) -> ComposeResult:\r\n        with Container(id=\"calculator\"):\r\n            yield Static(id=\"viewport\")\r\n            yield Button(\"AC\", id=\"ac\", variant=\"primary\")\r\n            yield Button(\"C\", id=\"c\", variant=\"primary\")\r\n            yield Button(\"+/-\", id=\"negation\", variant=\"primary\")\r\n            yield Button(\"%\", id=\"percent\", variant=\"primary\")\r\n            yield Button(\"sin(x)\", id=\"sine\", variant=\"warning\")\r\n            yield Button(\"cos(x)\", id=\"cosine\", variant=\"warning\")\r\n            yield Button(\"7\", id=\"number-7\", variant=\"primary\")\r\n            yield Button(\"8\", id=\"number-8\", variant=\"primary\")\r\n            yield Button(\"9\", id=\"number-9\", variant=\"primary\")\r\n            yield Button(\"+\", id=\"plus\", variant=\"warning\")\r\n            yield Button(\"x^y\", id=\"exponent\", variant=\"warning\")\r\n            yield Button(\"4\", id=\"number-4\", variant=\"primary\")\r\n            yield Button(\"5\", id=\"number-5\", variant=\"primary\")\r\n            yield Button(\"6\", id=\"number-6\", variant=\"primary\")\r\n            yield Button(\"-\", id=\"minus\", variant=\"warning\")\r\n            yield Button(\"ln(x)\", id=\"logarithm\", variant=\"warning\")\r\n            yield Button(\"1\", id=\"number-1\", variant=\"primary\")\r\n            yield Button(\"2\", id=\"number-2\", variant=\"primary\")\r\n            yield Button(\"3\", id=\"number-3\", variant=\"primary\")\r\n            yield Button(\"*\", id=\"multiply\", variant=\"warning\")\r\n            yield Button(\"x!\", id=\"factorial\", variant=\"warning\")\r\n            yield Button(\"0\", id=\"number-0\", variant=\"primary\")\r\n            yield Button(\".\", id=\"point\", variant=\"primary\")\r\n            yield Button(\"\u00f7\", id=\"divide\", variant=\"warning\")\r\n            yield Button(\"=\", id=\"equals\", variant=\"warning\")\r\n\r\n    def on_button_pressed(self, event: Button.Pressed) -> None:\r\n        button_id = event.button.id\r\n\r\n        assert button_id is not None\r\n\r\n        if button_id.startswith(\"number-\"):\r\n            number = button_id.split(\"-\")[-1]\r\n            self.viewport = self.value = self.value.lstrip(\"0\") + number\r\n```\r\n\r\nI wrote the following test to check that clicking the number buttons results in the calculator's display (`Static(id=\"viewport\")`) accumulating digits to make a number:\r\n\r\n```python\r\nasync def test_number_buttons():\r\n    async with CalculatorApp().run_test() as pilot:\r\n        app = pilot.app\r\n\r\n        await pilot.click(\"#number-1\")\r\n\r\n        display_content = app.query_one(\"#viewport\").render()\r\n        assert str(display_content) == \"1\"\r\n\r\n        await pilot.click(\"#number-2\")\r\n\r\n        display_content = app.query_one(\"#viewport\").render()\r\n        assert str(display_content) == \"12\"\r\n\r\n        await pilot.click(\"#number-3\")\r\n\r\n        display_content = app.query_one(\"#viewport\").render()\r\n        assert str(display_content) == \"123\"\r\n```\r\n\r\nWhile the GUI gets updated correctly on clicking the buttons, the test always fails since `app.query_one(\"#viewport\").render()` always returns `\"0\"`. I've also tried replacing `app.query_one(\"#viewport\").render()` with `app.query_one(\"#viewport\", Static).render()` but that hasn't helped either.\r\n\r\nIs this supposed to happen?\r\n\r\n# Textual Diagnostics\r\n\r\n## Versions\r\n\r\n| Name    | Value  |\r\n|---------|--------|\r\n| Textual | 0.14.0 |\r\n| Rich    | 13.3.2 |\r\n\r\n## Python\r\n\r\n| Name           | Value                                         |\r\n|----------------|-----------------------------------------------|\r\n| Version        | 3.10.9                                        |\r\n| Implementation | CPython                                       |\r\n| Compiler       | GCC 11.2.0                                    |\r\n| Executable     | /home/malay_agr/anaconda3/envs/spe/bin/python |\r\n\r\n## Operating System\r\n\r\n| Name    | Value                                       |\r\n|---------|---------------------------------------------|\r\n| System  | Linux                                       |\r\n| Release | 5.15.0-52-generic                           |\r\n| Version | #58-Ubuntu SMP Thu Oct 13 08:03:55 UTC 2022 |\r\n\r\n## Terminal\r\n\r\n| Name                 | Value           |\r\n|----------------------|-----------------|\r\n| Terminal Application | vscode (1.76.1) |\r\n| TERM                 | xterm-256color  |\r\n| COLORTERM            | truecolor       |\r\n| FORCE_COLOR          | *Not set*       |\r\n| NO_COLOR             | *Not set*       |\r\n\r\n## Rich Console options\r\n\r\n| Name           | Value                |\r\n|----------------|----------------------|\r\n| size           | width=197, height=19 |\r\n| legacy_windows | False                |\r\n| min_width      | 1                    |\r\n| max_width      | 197                  |\r\n| is_terminal    | True                 |\r\n| encoding       | utf-8                |\r\n| max_height     | 19                   |\r\n| justify        | None                 |\r\n| overflow       | None                 |\r\n| no_wrap        | False                |\r\n| highlight      | None                 |\r\n| markup         | None                 |\r\n| height         | None                 |\r\n\n", "before_files": [{"content": "from __future__ import annotations\n\nimport asyncio\nfrom typing import Any, Generic\n\nimport rich.repr\n\nfrom ._wait import wait_for_idle\nfrom .app import App, ReturnType\nfrom .css.query import QueryType\nfrom .events import Click, MouseDown, MouseMove, MouseUp\nfrom .geometry import Offset\nfrom .widget import Widget\n\n\ndef _get_mouse_message_arguments(\n    target: Widget, offset: Offset = Offset(), button: int = 0\n) -> dict[str, Any]:\n    \"\"\"Get the arguments to pass into mouse messages for the click and hover methods.\"\"\"\n    x, y = offset\n    click_x, click_y, _, _ = target.region.translate(offset)\n    message_arguments = {\n        \"x\": x,\n        \"y\": y,\n        \"delta_x\": 0,\n        \"delta_y\": 0,\n        \"button\": button,\n        \"shift\": False,\n        \"meta\": False,\n        \"ctrl\": False,\n        \"screen_x\": click_x,\n        \"screen_y\": click_y,\n    }\n    return message_arguments\n\n\n@rich.repr.auto(angular=True)\nclass Pilot(Generic[ReturnType]):\n    \"\"\"Pilot object to drive an app.\"\"\"\n\n    def __init__(self, app: App[ReturnType]) -> None:\n        self._app = app\n\n    def __rich_repr__(self) -> rich.repr.Result:\n        yield \"app\", self._app\n\n    @property\n    def app(self) -> App[ReturnType]:\n        \"\"\"App: A reference to the application.\"\"\"\n        return self._app\n\n    async def press(self, *keys: str) -> None:\n        \"\"\"Simulate key-presses.\n\n        Args:\n            *keys: Keys to press.\n\n        \"\"\"\n        if keys:\n            await self._app._press_keys(keys)\n\n    async def click(\n        self, selector: QueryType | None = None, offset: Offset = Offset()\n    ) -> None:\n        \"\"\"Simulate clicking with the mouse.\n\n        Args:\n            selector: The widget that should be clicked. If None, then the click\n                will occur relative to the screen. Note that this simply causes\n                a click to occur at the location of the widget. If the widget is\n                currently hidden or obscured by another widget, then the click may\n                not land on it.\n            offset: The offset to click within the selected widget.\n        \"\"\"\n        app = self.app\n        screen = app.screen\n        if selector is not None:\n            target_widget = screen.query_one(selector)\n        else:\n            target_widget = screen\n\n        message_arguments = _get_mouse_message_arguments(\n            target_widget, offset, button=1\n        )\n        app.post_message(MouseDown(**message_arguments))\n        app.post_message(MouseUp(**message_arguments))\n        app.post_message(Click(**message_arguments))\n        await self.pause()\n\n    async def hover(\n        self, selector: QueryType | None = None, offset: Offset = Offset()\n    ) -> None:\n        \"\"\"Simulate hovering with the mouse cursor.\n\n        Args:\n            selector: The widget that should be hovered. If None, then the click\n                will occur relative to the screen. Note that this simply causes\n                a hover to occur at the location of the widget. If the widget is\n                currently hidden or obscured by another widget, then the hover may\n                not land on it.\n            offset: The offset to hover over within the selected widget.\n        \"\"\"\n        app = self.app\n        screen = app.screen\n        if selector is not None:\n            target_widget = screen.query_one(selector)\n        else:\n            target_widget = screen\n\n        message_arguments = _get_mouse_message_arguments(\n            target_widget, offset, button=0\n        )\n        app.post_message(MouseMove(**message_arguments))\n        await self.pause()\n\n    async def pause(self, delay: float | None = None) -> None:\n        \"\"\"Insert a pause.\n\n        Args:\n            delay: Seconds to pause, or None to wait for cpu idle.\n        \"\"\"\n        # These sleep zeros, are to force asyncio to give up a time-slice,\n        if delay is None:\n            await wait_for_idle(0)\n        else:\n            await asyncio.sleep(delay)\n\n    async def wait_for_animation(self) -> None:\n        \"\"\"Wait for any current animation to complete.\"\"\"\n        await self._app.animator.wait_for_idle()\n\n    async def wait_for_scheduled_animations(self) -> None:\n        \"\"\"Wait for any current and scheduled animations to complete.\"\"\"\n        await self._app.animator.wait_until_complete()\n        await wait_for_idle()\n\n    async def exit(self, result: ReturnType) -> None:\n        \"\"\"Exit the app with the given result.\n\n        Args:\n            result: The app result returned by `run` or `run_async`.\n        \"\"\"\n        await wait_for_idle()\n        self.app.exit(result)\n", "path": "src/textual/pilot.py"}], "after_files": [{"content": "from __future__ import annotations\n\nimport asyncio\nfrom typing import Any, Generic\n\nimport rich.repr\n\nfrom ._wait import wait_for_idle\nfrom .app import App, ReturnType\nfrom .css.query import QueryType\nfrom .events import Click, MouseDown, MouseMove, MouseUp\nfrom .geometry import Offset\nfrom .widget import Widget\n\n\ndef _get_mouse_message_arguments(\n    target: Widget,\n    offset: Offset = Offset(),\n    button: int = 0,\n    shift: bool = False,\n    meta: bool = False,\n    control: bool = False,\n) -> dict[str, Any]:\n    \"\"\"Get the arguments to pass into mouse messages for the click and hover methods.\"\"\"\n    click_x, click_y, _, _ = target.region.translate(offset)\n    message_arguments = {\n        \"x\": click_x,\n        \"y\": click_y,\n        \"delta_x\": 0,\n        \"delta_y\": 0,\n        \"button\": button,\n        \"shift\": shift,\n        \"meta\": meta,\n        \"ctrl\": control,\n        \"screen_x\": click_x,\n        \"screen_y\": click_y,\n    }\n    return message_arguments\n\n\n@rich.repr.auto(angular=True)\nclass Pilot(Generic[ReturnType]):\n    \"\"\"Pilot object to drive an app.\"\"\"\n\n    def __init__(self, app: App[ReturnType]) -> None:\n        self._app = app\n\n    def __rich_repr__(self) -> rich.repr.Result:\n        yield \"app\", self._app\n\n    @property\n    def app(self) -> App[ReturnType]:\n        \"\"\"App: A reference to the application.\"\"\"\n        return self._app\n\n    async def press(self, *keys: str) -> None:\n        \"\"\"Simulate key-presses.\n\n        Args:\n            *keys: Keys to press.\n\n        \"\"\"\n        if keys:\n            await self._app._press_keys(keys)\n\n    async def click(\n        self,\n        selector: QueryType | None = None,\n        offset: Offset = Offset(),\n        shift: bool = False,\n        meta: bool = False,\n        control: bool = False,\n    ) -> None:\n        \"\"\"Simulate clicking with the mouse.\n\n        Args:\n            selector: The widget that should be clicked. If None, then the click\n                will occur relative to the screen. Note that this simply causes\n                a click to occur at the location of the widget. If the widget is\n                currently hidden or obscured by another widget, then the click may\n                not land on it.\n            offset: The offset to click within the selected widget.\n            shift: Click with the shift key held down.\n            meta: Click with the meta key held down.\n            control: Click with the control key held down.\n        \"\"\"\n        app = self.app\n        screen = app.screen\n        if selector is not None:\n            target_widget = screen.query_one(selector)\n        else:\n            target_widget = screen\n\n        message_arguments = _get_mouse_message_arguments(\n            target_widget, offset, button=1, shift=shift, meta=meta, control=control\n        )\n        app.post_message(MouseDown(**message_arguments))\n        app.post_message(MouseUp(**message_arguments))\n        app.post_message(Click(**message_arguments))\n        await self.pause()\n\n    async def hover(\n        self, selector: QueryType | None = None, offset: Offset = Offset()\n    ) -> None:\n        \"\"\"Simulate hovering with the mouse cursor.\n\n        Args:\n            selector: The widget that should be hovered. If None, then the click\n                will occur relative to the screen. Note that this simply causes\n                a hover to occur at the location of the widget. If the widget is\n                currently hidden or obscured by another widget, then the hover may\n                not land on it.\n            offset: The offset to hover over within the selected widget.\n        \"\"\"\n        app = self.app\n        screen = app.screen\n        if selector is not None:\n            target_widget = screen.query_one(selector)\n        else:\n            target_widget = screen\n\n        message_arguments = _get_mouse_message_arguments(\n            target_widget, offset, button=0\n        )\n        app.post_message(MouseMove(**message_arguments))\n        await self.pause()\n\n    async def pause(self, delay: float | None = None) -> None:\n        \"\"\"Insert a pause.\n\n        Args:\n            delay: Seconds to pause, or None to wait for cpu idle.\n        \"\"\"\n        # These sleep zeros, are to force asyncio to give up a time-slice,\n        if delay is None:\n            await wait_for_idle(0)\n        else:\n            await asyncio.sleep(delay)\n\n    async def wait_for_animation(self) -> None:\n        \"\"\"Wait for any current animation to complete.\"\"\"\n        await self._app.animator.wait_for_idle()\n\n    async def wait_for_scheduled_animations(self) -> None:\n        \"\"\"Wait for any current and scheduled animations to complete.\"\"\"\n        await self._app.animator.wait_until_complete()\n        await wait_for_idle()\n\n    async def exit(self, result: ReturnType) -> None:\n        \"\"\"Exit the app with the given result.\n\n        Args:\n            result: The app result returned by `run` or `run_async`.\n        \"\"\"\n        await wait_for_idle()\n        self.app.exit(result)\n", "path": "src/textual/pilot.py"}]}