2
2
3
3
This module provides utilities for waiting on tmux pane content in tests.
4
4
Inspired by Playwright's sync API for waiting on page content.
5
+
6
+ The main class is :class:`PaneWaiter` which provides methods to wait for specific
7
+ content to appear in a tmux pane. This is particularly useful for testing shell
8
+ commands and their output.
9
+
10
+ Examples
11
+ --------
12
+ >>> from libtmux.test.waiter import PaneWaiter
13
+ >>> # Create a new window and get its pane
14
+ >>> window = session.new_window(window_name="test_waiter")
15
+ >>> pane = window.active_pane
16
+ >>> # Create a waiter for the pane
17
+ >>> waiter = PaneWaiter(pane)
18
+ >>> # Wait for a specific prompt
19
+ >>> result = waiter.wait_for_prompt("$ ")
20
+ >>> result.success
21
+ True
22
+ >>> # Send a command and wait for its output
23
+ >>> pane.send_keys("echo 'Hello World'")
24
+ >>> result = waiter.wait_for_text("Hello World")
25
+ >>> result.success
26
+ True
27
+ >>> "Hello World" in result.value
28
+ True
29
+
30
+ The waiter also handles timeouts and errors gracefully:
31
+
32
+ >>> # Wait for text that won't appear (times out)
33
+ >>> result = waiter.wait_for_text("this won't appear", timeout_seconds=0.1)
34
+ >>> result.success
35
+ False
36
+ >>> isinstance(result.error, WaiterTimeoutError)
37
+ True
5
38
"""
6
39
7
40
from __future__ import annotations
23
56
24
57
25
58
class WaiterError (LibTmuxException ):
26
- """Base exception for waiter errors."""
59
+ """Base exception for waiter errors.
60
+
61
+ This is the parent class for all waiter-specific exceptions.
62
+ """
27
63
28
64
29
65
class WaiterTimeoutError (WaiterError ):
30
- """Exception raised when waiting for content times out."""
66
+ """Exception raised when waiting for content times out.
67
+
68
+ This exception is raised when the content being waited for does not appear
69
+ within the specified timeout period.
70
+
71
+ Examples
72
+ --------
73
+ >>> waiter = PaneWaiter(pane, timeout=0.1) # Short timeout
74
+ >>> result = waiter.wait_for_text("won't appear")
75
+ >>> isinstance(result.error, WaiterTimeoutError)
76
+ True
77
+ >>> str(result.error)
78
+ "Text 'won't appear' not found in pane"
79
+ """
31
80
32
81
33
82
class WaiterContentError (WaiterError ):
34
- """Exception raised when there's an error getting or checking content."""
83
+ r"""Exception raised when there's an error getting or checking content.
84
+
85
+ This exception is raised when there's an error accessing or reading the
86
+ pane content, for example if the pane is no longer available.
87
+
88
+ Examples
89
+ --------
90
+ >>> # Example of handling content errors
91
+ >>> try:
92
+ ... content = "\\n".join(pane.capture_pane())
93
+ ... except Exception as e:
94
+ ... error = WaiterContentError("Error capturing pane content")
95
+ ... error.__cause__ = e
96
+ ... raise error from e
97
+ """
35
98
36
99
37
100
@dataclass
38
101
class WaitResult (t .Generic [T ]):
39
- """Result of a wait operation."""
102
+ """Result of a wait operation.
103
+
104
+ This class encapsulates the result of a wait operation, including whether it
105
+ succeeded, the value found (if any), and any error that occurred.
106
+
107
+ Parameters
108
+ ----------
109
+ success : bool
110
+ Whether the wait operation succeeded
111
+ value : T | None
112
+ The value found, if any
113
+ error : Exception | None
114
+ The error that occurred, if any
115
+
116
+ Examples
117
+ --------
118
+ >>> # Successful wait result
119
+ >>> result = WaitResult[str](success=True, value="found content")
120
+ >>> result.success
121
+ True
122
+ >>> result.value
123
+ 'found content'
124
+ >>> result.error is None
125
+ True
126
+
127
+ >>> # Failed wait result with error
128
+ >>> error = WaiterTimeoutError("Timed out")
129
+ >>> result = WaitResult[str](success=False, error=error)
130
+ >>> result.success
131
+ False
132
+ >>> result.value is None
133
+ True
134
+ >>> isinstance(result.error, WaiterTimeoutError)
135
+ True
136
+ """
40
137
41
138
success : bool
42
139
value : T | None = None
43
140
error : Exception | None = None
44
141
45
142
46
143
class PaneWaiter :
47
- """Utility class for waiting on tmux pane content."""
144
+ """Utility class for waiting on tmux pane content.
145
+
146
+ This class provides methods to wait for specific content to appear in a tmux pane.
147
+ It supports waiting for exact text matches, prompts, and custom predicates.
148
+
149
+ Parameters
150
+ ----------
151
+ pane : Pane
152
+ The tmux pane to wait on
153
+ timeout : float, optional
154
+ Default timeout in seconds, by default 2.0
155
+
156
+ Examples
157
+ --------
158
+ Basic usage with text:
159
+
160
+ >>> waiter = PaneWaiter(pane)
161
+ >>> pane.send_keys("echo 'test'")
162
+ >>> result = waiter.wait_for_text("test")
163
+ >>> result.success
164
+ True
165
+ >>> "test" in result.value
166
+ True
167
+
168
+ Waiting for a prompt:
169
+
170
+ >>> waiter = PaneWaiter(pane)
171
+ >>> result = waiter.wait_for_prompt("$ ")
172
+ >>> result.success
173
+ True
174
+ >>> "$ " in result.value
175
+ True
176
+
177
+ Custom predicate:
178
+
179
+ >>> waiter = PaneWaiter(pane)
180
+ >>> result = waiter.wait_for_content(lambda content: "error" not in content.lower())
181
+ >>> result.success
182
+ True
183
+
184
+ Handling timeouts:
185
+
186
+ >>> waiter = PaneWaiter(pane, timeout=0.1) # Short timeout
187
+ >>> result = waiter.wait_for_text("won't appear")
188
+ >>> result.success
189
+ False
190
+ >>> isinstance(result.error, WaiterTimeoutError)
191
+ True
192
+ """
48
193
49
194
def __init__ (self , pane : Pane , timeout : float = 2.0 ) -> None :
50
195
"""Initialize PaneWaiter.
@@ -66,6 +211,9 @@ def _check_content(
66
211
) -> bool :
67
212
"""Check pane content against predicate.
68
213
214
+ This internal method captures the pane content and checks it against
215
+ the provided predicate function.
216
+
69
217
Parameters
70
218
----------
71
219
predicate : Callable[[str], bool]
@@ -82,6 +230,16 @@ def _check_content(
82
230
------
83
231
WaiterContentError
84
232
If there's an error capturing pane content
233
+
234
+ Examples
235
+ --------
236
+ >>> waiter = PaneWaiter(pane)
237
+ >>> result = WaitResult[str](success=False)
238
+ >>> success = waiter._check_content(lambda c: "test" in c, result)
239
+ >>> success # True if "test" is found in pane content
240
+ True
241
+ >>> result.value is not None
242
+ True
85
243
"""
86
244
try :
87
245
content = "\n " .join (self .pane .capture_pane ())
@@ -101,7 +259,56 @@ def wait_for_content(
101
259
interval_seconds : float | None = None ,
102
260
error_message : str | None = None ,
103
261
) -> WaitResult [str ]:
104
- """Wait for content in the pane to match a predicate."""
262
+ """Wait for content in the pane to match a predicate.
263
+
264
+ This is the core waiting method that other methods build upon. It repeatedly
265
+ checks the pane content against a predicate function until it returns True
266
+ or times out.
267
+
268
+ Parameters
269
+ ----------
270
+ predicate : Callable[[str], bool]
271
+ Function that takes pane content as string and returns bool
272
+ timeout_seconds : float | None, optional
273
+ Maximum time to wait in seconds, by default None (uses instance timeout)
274
+ interval_seconds : float | None, optional
275
+ Time between checks in seconds, by default None (uses 0.05)
276
+ error_message : str | None, optional
277
+ Custom error message for timeout, by default None
278
+
279
+ Returns
280
+ -------
281
+ WaitResult[str]
282
+ Result of the wait operation
283
+
284
+ Examples
285
+ --------
286
+ >>> waiter = PaneWaiter(pane)
287
+ >>> # Wait for content containing "success" but not "error"
288
+ >>> result = waiter.wait_for_content(
289
+ ... lambda content: "success" in content and "error" not in content
290
+ ... )
291
+ >>> result.success
292
+ True
293
+
294
+ >>> # Wait with custom timeout and interval
295
+ >>> result = waiter.wait_for_content(
296
+ ... lambda content: "test" in content,
297
+ ... timeout_seconds=5.0,
298
+ ... interval_seconds=0.1,
299
+ ... )
300
+ >>> result.success
301
+ True
302
+
303
+ >>> # Wait with custom error message
304
+ >>> result = waiter.wait_for_content(
305
+ ... lambda content: False, # Never succeeds
306
+ ... timeout_seconds=0.1,
307
+ ... error_message="Custom timeout message",
308
+ ... )
309
+ >>> str(result.error)
310
+ 'Custom timeout message'
311
+ """
105
312
result = WaitResult [str ](success = False , value = None , error = None )
106
313
try :
107
314
# Give the shell a moment to be ready
@@ -134,7 +341,40 @@ def wait_for_prompt(
134
341
timeout_seconds : float | None = None ,
135
342
error_message : str | None = None ,
136
343
) -> WaitResult [str ]:
137
- """Wait for a specific prompt to appear in the pane."""
344
+ """Wait for a specific prompt to appear in the pane.
345
+
346
+ This method waits for a specific shell prompt to appear in the pane.
347
+ It ensures the prompt is at the end of non-empty content.
348
+
349
+ Parameters
350
+ ----------
351
+ prompt : str
352
+ The prompt text to wait for
353
+ timeout_seconds : float | None, optional
354
+ Maximum time to wait in seconds, by default None (uses instance timeout)
355
+ error_message : str | None, optional
356
+ Custom error message for timeout, by default None
357
+
358
+ Returns
359
+ -------
360
+ WaitResult[str]
361
+ Result of the wait operation
362
+
363
+ Examples
364
+ --------
365
+ >>> waiter = PaneWaiter(pane)
366
+ >>> # Wait for bash prompt
367
+ >>> result = waiter.wait_for_prompt("$ ")
368
+ >>> result.success
369
+ True
370
+ >>> "$ " in result.value
371
+ True
372
+
373
+ >>> # Wait for custom prompt
374
+ >>> result = waiter.wait_for_prompt("my_prompt> ")
375
+ >>> result.success
376
+ True
377
+ """
138
378
return self .wait_for_content (
139
379
lambda content : prompt in content and len (content .strip ()) > 0 ,
140
380
timeout_seconds = timeout_seconds ,
@@ -148,7 +388,46 @@ def wait_for_text(
148
388
interval_seconds : float | None = None ,
149
389
error_message : str | None = None ,
150
390
) -> WaitResult [str ]:
151
- """Wait for text to appear in the pane."""
391
+ """Wait for text to appear in the pane.
392
+
393
+ This method waits for specific text to appear anywhere in the pane content.
394
+
395
+ Parameters
396
+ ----------
397
+ text : str
398
+ The text to wait for
399
+ timeout_seconds : float | None, optional
400
+ Maximum time to wait in seconds, by default None (uses instance timeout)
401
+ interval_seconds : float | None, optional
402
+ Time between checks in seconds, by default None (uses 0.05)
403
+ error_message : str | None, optional
404
+ Custom error message for timeout, by default None
405
+
406
+ Returns
407
+ -------
408
+ WaitResult[str]
409
+ Result of the wait operation
410
+
411
+ Examples
412
+ --------
413
+ >>> waiter = PaneWaiter(pane)
414
+ >>> # Send a command and wait for its output
415
+ >>> pane.send_keys("echo 'Hello World'")
416
+ >>> result = waiter.wait_for_text("Hello World")
417
+ >>> result.success
418
+ True
419
+ >>> "Hello World" in result.value
420
+ True
421
+
422
+ >>> # Wait with custom timeout
423
+ >>> result = waiter.wait_for_text(
424
+ ... "test output",
425
+ ... timeout_seconds=5.0,
426
+ ... error_message="Failed to find test output",
427
+ ... )
428
+ >>> result.success
429
+ True
430
+ """
152
431
if error_message is None :
153
432
error_message = f"Text '{ text } ' not found in pane"
154
433
return self .wait_for_content (
0 commit comments