22
33This module provides utilities for waiting on tmux pane content in tests.
44Inspired 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
538"""
639
740from __future__ import annotations
2356
2457
2558class 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+ """
2763
2864
2965class 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+ """
3180
3281
3382class 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+ """
3598
3699
37100@dataclass
38101class 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+ """
40137
41138 success : bool
42139 value : T | None = None
43140 error : Exception | None = None
44141
45142
46143class 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+ """
48193
49194 def __init__ (self , pane : Pane , timeout : float = 2.0 ) -> None :
50195 """Initialize PaneWaiter.
@@ -66,6 +211,9 @@ def _check_content(
66211 ) -> bool :
67212 """Check pane content against predicate.
68213
214+ This internal method captures the pane content and checks it against
215+ the provided predicate function.
216+
69217 Parameters
70218 ----------
71219 predicate : Callable[[str], bool]
@@ -82,6 +230,16 @@ def _check_content(
82230 ------
83231 WaiterContentError
84232 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
85243 """
86244 try :
87245 content = "\n " .join (self .pane .capture_pane ())
@@ -101,7 +259,56 @@ def wait_for_content(
101259 interval_seconds : float | None = None ,
102260 error_message : str | None = None ,
103261 ) -> 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+ """
105312 result = WaitResult [str ](success = False , value = None , error = None )
106313 try :
107314 # Give the shell a moment to be ready
@@ -134,7 +341,40 @@ def wait_for_prompt(
134341 timeout_seconds : float | None = None ,
135342 error_message : str | None = None ,
136343 ) -> 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+ """
138378 return self .wait_for_content (
139379 lambda content : prompt in content and len (content .strip ()) > 0 ,
140380 timeout_seconds = timeout_seconds ,
@@ -148,7 +388,46 @@ def wait_for_text(
148388 interval_seconds : float | None = None ,
149389 error_message : str | None = None ,
150390 ) -> 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+ """
152431 if error_message is None :
153432 error_message = f"Text '{ text } ' not found in pane"
154433 return self .wait_for_content (
0 commit comments