From 7a80a7f8646587e560c399e54c55f4a76e4df24e Mon Sep 17 00:00:00 2001
From: Tony Narlock <tony@git-pull.com>
Date: Sun, 23 Feb 2025 12:12:47 -0600
Subject: [PATCH 1/7] test(pane): Make test_capture_pane shell-agnostic

why: Tests were failing when using fish shell due to prompt differences

what:
- Use PROMPT_COMMAND='' to clear shell-specific prompt commands
- Set custom PS1='READY>' that works across shells
- Add retry logic to wait for prompt and command output
- Make assertions more flexible by checking content not exact matches
---
 tests/test_pane.py | 28 +++++++++++++++++++++++-----
 1 file changed, 23 insertions(+), 5 deletions(-)

diff --git a/tests/test_pane.py b/tests/test_pane.py
index 746467851..1a0760bb8 100644
--- a/tests/test_pane.py
+++ b/tests/test_pane.py
@@ -68,24 +68,42 @@ def test_capture_pane(session: Session) -> None:
     env = shutil.which("env")
     assert env is not None, "Cannot find usable `env` in PATH."
 
+    # Use PROMPT_COMMAND/PS1 to set a consistent prompt across shells
     session.new_window(
         attach=True,
         window_name="capture_pane",
-        window_shell=f"{env} PS1='$ ' sh",
+        window_shell=f"{env} PROMPT_COMMAND='' PS1='READY>' sh",
     )
     pane = session.active_window.active_pane
     assert pane is not None
+
+    def wait_for_prompt() -> bool:
+        pane_contents = "\n".join(pane.capture_pane())
+        return "READY>" in pane_contents
+
+    # Wait for shell to be ready with our custom prompt
+    retry_until(wait_for_prompt, 1, raises=True)
+
     pane_contents = "\n".join(pane.capture_pane())
-    assert pane_contents == "$"
+    assert "READY>" in pane_contents
+
     pane.send_keys(
         r'printf "\n%s\n" "Hello World !"',
         literal=True,
         suppress_history=False,
     )
+
+    def wait_for_output() -> bool:
+        pane_contents = "\n".join(pane.capture_pane())
+        return "Hello World !" in pane_contents and pane_contents.count("READY>") >= 2
+
+    # Wait for command output and new prompt
+    retry_until(wait_for_output, 1, raises=True)
+
     pane_contents = "\n".join(pane.capture_pane())
-    assert pane_contents == r'$ printf "\n%s\n" "Hello World !"{}'.format(
-        "\n\nHello World !\n$",
-    )
+    assert r'READY>printf "\n%s\n" "Hello World !"' in pane_contents
+    assert "Hello World !" in pane_contents
+    assert pane_contents.count("READY>") >= 2
 
 
 def test_capture_pane_start(session: Session) -> None:

From b616fdcdc30022b108abe5e148d3607fddfbe437 Mon Sep 17 00:00:00 2001
From: Tony Narlock <tony@git-pull.com>
Date: Sun, 23 Feb 2025 12:15:58 -0600
Subject: [PATCH 2/7] test(pane): Make test_capture_pane more robust

why: Test was flaky due to timing issues with shell initialization

what:
- Add small delay after window creation
- Add error handling around capture_pane calls
- Increase retry timeouts from 1s to 2s
- Add more comprehensive output checks
- Add check for non-empty pane contents
---
 tests/test_pane.py | 27 +++++++++++++++++++++------
 1 file changed, 21 insertions(+), 6 deletions(-)

diff --git a/tests/test_pane.py b/tests/test_pane.py
index 1a0760bb8..f8d8cf232 100644
--- a/tests/test_pane.py
+++ b/tests/test_pane.py
@@ -4,6 +4,7 @@
 
 import logging
 import shutil
+import time
 import typing as t
 
 import pytest
@@ -74,15 +75,22 @@ def test_capture_pane(session: Session) -> None:
         window_name="capture_pane",
         window_shell=f"{env} PROMPT_COMMAND='' PS1='READY>' sh",
     )
+
+    # Give tmux a moment to create the window and start the shell
+    time.sleep(0.1)
+
     pane = session.active_window.active_pane
     assert pane is not None
 
     def wait_for_prompt() -> bool:
-        pane_contents = "\n".join(pane.capture_pane())
-        return "READY>" in pane_contents
+        try:
+            pane_contents = "\n".join(pane.capture_pane())
+            return "READY>" in pane_contents and len(pane_contents.strip()) > 0
+        except Exception:
+            return False
 
     # Wait for shell to be ready with our custom prompt
-    retry_until(wait_for_prompt, 1, raises=True)
+    retry_until(wait_for_prompt, 2, raises=True)
 
     pane_contents = "\n".join(pane.capture_pane())
     assert "READY>" in pane_contents
@@ -94,11 +102,18 @@ def wait_for_prompt() -> bool:
     )
 
     def wait_for_output() -> bool:
-        pane_contents = "\n".join(pane.capture_pane())
-        return "Hello World !" in pane_contents and pane_contents.count("READY>") >= 2
+        try:
+            pane_contents = "\n".join(pane.capture_pane())
+            return (
+                "Hello World !" in pane_contents
+                and pane_contents.count("READY>") >= 2
+                and r'printf "\n%s\n" "Hello World !"' in pane_contents
+            )
+        except Exception:
+            return False
 
     # Wait for command output and new prompt
-    retry_until(wait_for_output, 1, raises=True)
+    retry_until(wait_for_output, 2, raises=True)
 
     pane_contents = "\n".join(pane.capture_pane())
     assert r'READY>printf "\n%s\n" "Hello World !"' in pane_contents

From 553c1c575df2c1af5e5851ed9b3b657781a63694 Mon Sep 17 00:00:00 2001
From: Tony Narlock <tony@git-pull.com>
Date: Sun, 23 Feb 2025 16:13:42 -0600
Subject: [PATCH 3/7] refactor(tests): add shell-agnostic test helper

Add setup_shell_window helper function to standardize shell setup in tests:
Set consistent shell environment and prompt, add robust waiting for shell
readiness, handle environment variables consistently, prevent shell-specific
prompt modifications.

Update test_new_window_with_environment to use new helper:
Add proper waiting for command output, fix linting issues with loop variables,
make tests more reliable against timing issues.
---
 tests/test_session.py | 79 +++++++++++++++++++++++++++++++++----------
 1 file changed, 62 insertions(+), 17 deletions(-)

diff --git a/tests/test_session.py b/tests/test_session.py
index 88d5f79e6..b9d42433f 100644
--- a/tests/test_session.py
+++ b/tests/test_session.py
@@ -12,9 +12,11 @@
 from libtmux.common import has_gte_version, has_lt_version
 from libtmux.constants import WindowDirection
 from libtmux.pane import Pane
+from libtmux.server import Server
 from libtmux.session import Session
 from libtmux.test.constants import TEST_SESSION_PREFIX
 from libtmux.test.random import namer
+from libtmux.test.retry import retry_until
 from libtmux.window import Window
 
 if t.TYPE_CHECKING:
@@ -23,6 +25,47 @@
 logger = logging.getLogger(__name__)
 
 
+def setup_shell_window(
+    session: Session,
+    window_name: str,
+    environment: dict[str, str] | None = None,
+) -> Window:
+    """Set up a shell window with consistent environment and prompt.
+
+    Args:
+        session: The tmux session to create the window in
+        window_name: Name for the new window
+        environment: Optional environment variables to set in the window
+
+    Returns
+    -------
+        The created Window object with shell ready
+    """
+    env = shutil.which("env")
+    assert env is not None, "Cannot find usable `env` in PATH."
+
+    window = session.new_window(
+        attach=True,
+        window_name=window_name,
+        window_shell=f"{env} PROMPT_COMMAND='' PS1='READY>' sh",
+        environment=environment,
+    )
+
+    pane = window.active_pane
+    assert pane is not None
+
+    # Wait for shell to be ready
+    def wait_for_prompt() -> bool:
+        try:
+            pane_contents = "\n".join(pane.capture_pane())
+            return "READY>" in pane_contents and len(pane_contents.strip()) > 0
+        except Exception:
+            return False
+
+    retry_until(wait_for_prompt, 2, raises=True)
+    return window
+
+
 def test_has_session(server: Server, session: Session) -> None:
     """Server.has_session returns True if has session_name exists."""
     TEST_SESSION_NAME = session.session_name
@@ -328,20 +371,26 @@ def test_new_window_with_environment(
     environment: dict[str, str],
 ) -> None:
     """Verify new window with environment vars."""
-    env = shutil.which("env")
-    assert env is not None, "Cannot find usable `env` in PATH."
-
-    window = session.new_window(
-        attach=True,
-        window_name="window_with_environment",
-        window_shell=f"{env} PS1='$ ' sh",
+    window = setup_shell_window(
+        session,
+        "window_with_environment",
         environment=environment,
     )
     pane = window.active_pane
     assert pane is not None
-    for k, v in environment.items():
-        pane.send_keys(f"echo ${k}")
-        assert pane.capture_pane()[-2] == v
+
+    for k, expected_value in environment.items():
+        pane.send_keys(f"echo ${k}", literal=True)
+
+        # Wait for command output
+        def wait_for_output(value: str = expected_value) -> bool:
+            try:
+                pane_contents = pane.capture_pane()
+                return any(value in line for line in pane_contents)
+            except Exception:
+                return False
+
+        retry_until(wait_for_output, 2, raises=True)
 
 
 @pytest.mark.skipif(
@@ -353,13 +402,9 @@ def test_new_window_with_environment_logs_warning_for_old_tmux(
     caplog: pytest.LogCaptureFixture,
 ) -> None:
     """Verify new window with environment vars create a warning if tmux is too old."""
-    env = shutil.which("env")
-    assert env is not None, "Cannot find usable `env` in PATH."
-
-    session.new_window(
-        attach=True,
-        window_name="window_with_environment",
-        window_shell=f"{env} PS1='$ ' sh",
+    setup_shell_window(
+        session,
+        "window_with_environment",
         environment={"ENV_VAR": "window"},
     )
 

From 35d57ba0c211193fda9bd6793f84ae647a2a1889 Mon Sep 17 00:00:00 2001
From: Tony Narlock <tony@git-pull.com>
Date: Sun, 23 Feb 2025 16:18:13 -0600
Subject: [PATCH 4/7] refactor(tests): standardize shell setup in test_pane.py

---
 tests/test_pane.py | 148 +++++++++++++++++++++++++++------------------
 1 file changed, 89 insertions(+), 59 deletions(-)

diff --git a/tests/test_pane.py b/tests/test_pane.py
index f8d8cf232..3780092d9 100644
--- a/tests/test_pane.py
+++ b/tests/test_pane.py
@@ -4,7 +4,6 @@
 
 import logging
 import shutil
-import time
 import typing as t
 
 import pytest
@@ -15,10 +14,52 @@
 
 if t.TYPE_CHECKING:
     from libtmux.session import Session
+    from libtmux.window import Window
 
 logger = logging.getLogger(__name__)
 
 
+def setup_shell_window(
+    session: Session,
+    window_name: str,
+    environment: dict[str, str] | None = None,
+) -> Window:
+    """Set up a shell window with consistent environment and prompt.
+
+    Args:
+        session: The tmux session to create the window in
+        window_name: Name for the new window
+        environment: Optional environment variables to set in the window
+
+    Returns
+    -------
+        The created Window object with shell ready
+    """
+    env = shutil.which("env")
+    assert env is not None, "Cannot find usable `env` in PATH."
+
+    window = session.new_window(
+        attach=True,
+        window_name=window_name,
+        window_shell=f"{env} PROMPT_COMMAND='' PS1='READY>' sh",
+        environment=environment,
+    )
+
+    pane = window.active_pane
+    assert pane is not None
+
+    # Wait for shell to be ready
+    def wait_for_prompt() -> bool:
+        try:
+            pane_contents = "\n".join(pane.capture_pane())
+            return "READY>" in pane_contents and len(pane_contents.strip()) > 0
+        except Exception:
+            return False
+
+    retry_until(wait_for_prompt, 2, raises=True)
+    return window
+
+
 def test_send_keys(session: Session) -> None:
     """Verify Pane.send_keys()."""
     pane = session.active_window.active_pane
@@ -66,35 +107,10 @@ def test_set_width(session: Session) -> None:
 
 def test_capture_pane(session: Session) -> None:
     """Verify Pane.capture_pane()."""
-    env = shutil.which("env")
-    assert env is not None, "Cannot find usable `env` in PATH."
-
-    # Use PROMPT_COMMAND/PS1 to set a consistent prompt across shells
-    session.new_window(
-        attach=True,
-        window_name="capture_pane",
-        window_shell=f"{env} PROMPT_COMMAND='' PS1='READY>' sh",
-    )
-
-    # Give tmux a moment to create the window and start the shell
-    time.sleep(0.1)
-
-    pane = session.active_window.active_pane
+    window = setup_shell_window(session, "capture_pane")
+    pane = window.active_pane
     assert pane is not None
 
-    def wait_for_prompt() -> bool:
-        try:
-            pane_contents = "\n".join(pane.capture_pane())
-            return "READY>" in pane_contents and len(pane_contents.strip()) > 0
-        except Exception:
-            return False
-
-    # Wait for shell to be ready with our custom prompt
-    retry_until(wait_for_prompt, 2, raises=True)
-
-    pane_contents = "\n".join(pane.capture_pane())
-    assert "READY>" in pane_contents
-
     pane.send_keys(
         r'printf "\n%s\n" "Hello World !"',
         literal=True,
@@ -123,21 +139,27 @@ def wait_for_output() -> bool:
 
 def test_capture_pane_start(session: Session) -> None:
     """Assert Pane.capture_pane() with ``start`` param."""
-    env = shutil.which("env")
-    assert env is not None, "Cannot find usable `env` in PATH."
-
-    session.new_window(
-        attach=True,
-        window_name="capture_pane_start",
-        window_shell=f"{env} PS1='$ ' sh",
-    )
-    pane = session.active_window.active_pane
+    window = setup_shell_window(session, "capture_pane_start")
+    pane = window.active_pane
     assert pane is not None
+
     pane_contents = "\n".join(pane.capture_pane())
-    assert pane_contents == "$"
+    assert "READY>" in pane_contents
+
     pane.send_keys(r'printf "%s"', literal=True, suppress_history=False)
-    pane_contents = "\n".join(pane.capture_pane())
-    assert pane_contents == '$ printf "%s"\n$'
+
+    def wait_for_command() -> bool:
+        try:
+            pane_contents = "\n".join(pane.capture_pane())
+        except Exception:
+            return False
+        else:
+            has_command = r'printf "%s"' in pane_contents
+            has_prompts = pane_contents.count("READY>") >= 2
+            return has_command and has_prompts
+
+    retry_until(wait_for_command, 2, raises=True)
+
     pane.send_keys("clear -x", literal=True, suppress_history=False)
 
     def wait_until_pane_cleared() -> bool:
@@ -148,45 +170,53 @@ def wait_until_pane_cleared() -> bool:
 
     def pane_contents_shell_prompt() -> bool:
         pane_contents = "\n".join(pane.capture_pane())
-        return pane_contents == "$"
+        return "READY>" in pane_contents and len(pane_contents.strip()) > 0
 
     retry_until(pane_contents_shell_prompt, 1, raises=True)
 
     pane_contents_history_start = pane.capture_pane(start=-2)
-    assert pane_contents_history_start[0] == '$ printf "%s"'
-    assert pane_contents_history_start[1] == "$ clear -x"
-    assert pane_contents_history_start[-1] == "$"
+    assert r'READY>printf "%s"' in pane_contents_history_start[0]
+    assert "READY>clear -x" in pane_contents_history_start[1]
+    assert "READY>" in pane_contents_history_start[-1]
 
     pane.send_keys("")
 
     def pane_contents_capture_visible_only_shows_prompt() -> bool:
         pane_contents = "\n".join(pane.capture_pane(start=1))
-        return pane_contents == "$"
+        return "READY>" in pane_contents and len(pane_contents.strip()) > 0
 
     assert retry_until(pane_contents_capture_visible_only_shows_prompt, 1, raises=True)
 
 
 def test_capture_pane_end(session: Session) -> None:
     """Assert Pane.capture_pane() with ``end`` param."""
-    env = shutil.which("env")
-    assert env is not None, "Cannot find usable `env` in PATH."
-
-    session.new_window(
-        attach=True,
-        window_name="capture_pane_end",
-        window_shell=f"{env} PS1='$ ' sh",
-    )
-    pane = session.active_window.active_pane
+    window = setup_shell_window(session, "capture_pane_end")
+    pane = window.active_pane
     assert pane is not None
+
     pane_contents = "\n".join(pane.capture_pane())
-    assert pane_contents == "$"
+    assert "READY>" in pane_contents
+
     pane.send_keys(r'printf "%s"', literal=True, suppress_history=False)
-    pane_contents = "\n".join(pane.capture_pane())
-    assert pane_contents == '$ printf "%s"\n$'
+
+    def wait_for_command() -> bool:
+        try:
+            pane_contents = "\n".join(pane.capture_pane())
+        except Exception:
+            return False
+        else:
+            has_command = r'printf "%s"' in pane_contents
+            has_prompts = pane_contents.count("READY>") >= 2
+            return has_command and has_prompts
+
+    retry_until(wait_for_command, 2, raises=True)
+
     pane_contents = "\n".join(pane.capture_pane(end=0))
-    assert pane_contents == '$ printf "%s"'
+    assert r'READY>printf "%s"' in pane_contents
+
     pane_contents = "\n".join(pane.capture_pane(end="-"))
-    assert pane_contents == '$ printf "%s"\n$'
+    assert r'READY>printf "%s"' in pane_contents
+    assert pane_contents.count("READY>") >= 2
 
 
 @pytest.mark.skipif(

From eccd6f81ae7f7874dfe0386c33be1a03b9aad0be Mon Sep 17 00:00:00 2001
From: Tony Narlock <tony@git-pull.com>
Date: Sun, 23 Feb 2025 16:23:50 -0600
Subject: [PATCH 5/7] !squash test_pane

---
 tests/test_pane.py | 71 ++++++++++++++++++++++++++++++++++++++++------
 1 file changed, 62 insertions(+), 9 deletions(-)

diff --git a/tests/test_pane.py b/tests/test_pane.py
index 3780092d9..19c1e64ac 100644
--- a/tests/test_pane.py
+++ b/tests/test_pane.py
@@ -62,15 +62,42 @@ def wait_for_prompt() -> bool:
 
 def test_send_keys(session: Session) -> None:
     """Verify Pane.send_keys()."""
-    pane = session.active_window.active_pane
+    window = setup_shell_window(session, "test_send_keys")
+    pane = window.active_pane
     assert pane is not None
-    pane.send_keys("c-c", literal=True)
 
-    pane_contents = "\n".join(pane.cmd("capture-pane", "-p").stdout)
-    assert "c-c" in pane_contents
+    # Test literal input
+    pane.send_keys("echo 'test-literal'", literal=True)
 
-    pane.send_keys("c-a", literal=False)
-    assert "c-a" not in pane_contents, "should not print to pane"
+    def wait_for_literal() -> bool:
+        try:
+            pane_contents = "\n".join(pane.capture_pane())
+            return (
+                "test-literal" in pane_contents
+                and "echo 'test-literal'" in pane_contents
+                and pane_contents.count("READY>") >= 2
+            )
+        except Exception:
+            return False
+
+    retry_until(wait_for_literal, 2, raises=True)
+
+    # Test non-literal input (should be interpreted as keystrokes)
+    pane.send_keys("c-c", literal=False)  # Send Ctrl-C
+
+    def wait_for_ctrl_c() -> bool:
+        try:
+            pane_contents = "\n".join(pane.capture_pane())
+            # Ctrl-C should add a new prompt without executing a command
+            return (
+                # Previous prompt + command + new prompt
+                pane_contents.count("READY>") >= 3
+                and "c-c" not in pane_contents  # The literal string should not appear
+            )
+        except Exception:
+            return False
+
+    retry_until(wait_for_ctrl_c, 2, raises=True)
 
 
 def test_set_height(session: Session) -> None:
@@ -389,9 +416,35 @@ def test_split_pane_size(session: Session) -> None:
 
 def test_pane_context_manager(session: Session) -> None:
     """Test Pane context manager functionality."""
-    window = session.new_window()
-    with window.split() as pane:
-        pane.send_keys('echo "Hello"')
+    env = shutil.which("env")
+    assert env is not None, "Cannot find usable `env` in PATH."
+
+    window = setup_shell_window(session, "test_context_manager")
+    with window.split(shell=f"{env} PROMPT_COMMAND='' PS1='READY>' sh") as pane:
+        # Wait for shell to be ready in the split pane
+        def wait_for_shell() -> bool:
+            try:
+                pane_contents = "\n".join(pane.capture_pane())
+                return "READY>" in pane_contents and len(pane_contents.strip()) > 0
+            except Exception:
+                return False
+
+        retry_until(wait_for_shell, 2, raises=True)
+
+        pane.send_keys('echo "Hello"', literal=True)
+
+        def wait_for_output() -> bool:
+            try:
+                pane_contents = "\n".join(pane.capture_pane())
+                return (
+                    'echo "Hello"' in pane_contents
+                    and "Hello" in pane_contents
+                    and pane_contents.count("READY>") >= 2
+                )
+            except Exception:
+                return False
+
+        retry_until(wait_for_output, 2, raises=True)
         assert pane in window.panes
         assert len(window.panes) == 2  # Initial pane + new pane
 

From 9ee8f1be9a9c26fd4bb11b9308161be234dabf35 Mon Sep 17 00:00:00 2001
From: Tony Narlock <tony@git-pull.com>
Date: Sun, 23 Feb 2025 16:44:43 -0600
Subject: [PATCH 6/7] !squash tests

---
 tests/legacy_api/test_pane.py    | 95 ++++++++++++++++++++++----------
 tests/legacy_api/test_session.py | 67 ++++++++++++++++++----
 tests/legacy_api/test_window.py  | 76 ++++++++++++++++++++++---
 tests/test_session.py            |  5 +-
 tests/test_window.py             | 29 ++++++++--
 5 files changed, 216 insertions(+), 56 deletions(-)

diff --git a/tests/legacy_api/test_pane.py b/tests/legacy_api/test_pane.py
index 31200a9b9..5735e41bd 100644
--- a/tests/legacy_api/test_pane.py
+++ b/tests/legacy_api/test_pane.py
@@ -6,16 +6,61 @@
 import shutil
 import typing as t
 
+from libtmux.test.retry import retry_until
+
 if t.TYPE_CHECKING:
     from libtmux.session import Session
+    from libtmux.window import Window
 
 logger = logging.getLogger(__name__)
 
 
+def setup_shell_window(
+    session: Session,
+    window_name: str,
+    environment: dict[str, str] | None = None,
+) -> Window:
+    """Set up a shell window with consistent environment and prompt.
+
+    Args:
+        session: The tmux session to create the window in
+        window_name: Name for the new window
+        environment: Optional environment variables to set in the window
+
+    Returns
+    -------
+        The created Window object with shell ready
+    """
+    env = shutil.which("env")
+    assert env is not None, "Cannot find usable `env` in PATH."
+
+    window = session.new_window(
+        attach=True,
+        window_name=window_name,
+        window_shell=f"{env} PROMPT_COMMAND='' PS1='READY>' sh",
+        environment=environment,
+    )
+
+    pane = window.active_pane
+    assert pane is not None
+
+    # Wait for shell to be ready
+    def wait_for_prompt() -> bool:
+        try:
+            pane_contents = "\n".join(pane.capture_pane())
+            return "READY>" in pane_contents and len(pane_contents.strip()) > 0
+        except Exception:
+            return False
+
+    retry_until(wait_for_prompt, 2, raises=True)
+    return window
+
+
 def test_resize_pane(session: Session) -> None:
-    """Test Pane.resize_pane()."""
-    window = session.attached_window
-    window.rename_window("test_resize_pane")
+    """Verify Pane.resize_pane()."""
+    window = setup_shell_window(session, "test_resize_pane")
+    pane = window.active_pane
+    assert pane is not None
 
     pane1 = window.attached_pane
     assert pane1 is not None
@@ -32,15 +77,24 @@ def test_resize_pane(session: Session) -> None:
 
 def test_send_keys(session: Session) -> None:
     """Verify Pane.send_keys()."""
-    pane = session.attached_window.attached_pane
+    window = setup_shell_window(session, "test_send_keys")
+    pane = window.active_pane
     assert pane is not None
-    pane.send_keys("c-c", literal=True)
 
-    pane_contents = "\n".join(pane.cmd("capture-pane", "-p").stdout)
-    assert "c-c" in pane_contents
+    pane.send_keys("echo 'test'", literal=True)
+
+    def wait_for_echo() -> bool:
+        try:
+            pane_contents = "\n".join(pane.capture_pane())
+            return (
+                "test" in pane_contents
+                and "echo 'test'" in pane_contents
+                and pane_contents.count("READY>") >= 2
+            )
+        except Exception:
+            return False
 
-    pane.send_keys("c-a", literal=False)
-    assert "c-a" not in pane_contents, "should not print to pane"
+    retry_until(wait_for_echo, 2, raises=True)
 
 
 def test_set_height(session: Session) -> None:
@@ -75,24 +129,9 @@ def test_set_width(session: Session) -> None:
 
 def test_capture_pane(session: Session) -> None:
     """Verify Pane.capture_pane()."""
-    env = shutil.which("env")
-    assert env is not None, "Cannot find usable `env` in PATH."
-
-    session.new_window(
-        attach=True,
-        window_name="capture_pane",
-        window_shell=f"{env} PS1='$ ' sh",
-    )
-    pane = session.attached_window.attached_pane
+    window = setup_shell_window(session, "test_capture_pane")
+    pane = window.active_pane
     assert pane is not None
+
     pane_contents = "\n".join(pane.capture_pane())
-    assert pane_contents == "$"
-    pane.send_keys(
-        r'printf "\n%s\n" "Hello World !"',
-        literal=True,
-        suppress_history=False,
-    )
-    pane_contents = "\n".join(pane.capture_pane())
-    assert pane_contents == r'$ printf "\n%s\n" "Hello World !"{}'.format(
-        "\n\nHello World !\n$",
-    )
+    assert "READY>" in pane_contents
diff --git a/tests/legacy_api/test_session.py b/tests/legacy_api/test_session.py
index c756999ea..76c9a5059 100644
--- a/tests/legacy_api/test_session.py
+++ b/tests/legacy_api/test_session.py
@@ -14,6 +14,7 @@
 from libtmux.session import Session
 from libtmux.test.constants import TEST_SESSION_PREFIX
 from libtmux.test.random import namer
+from libtmux.test.retry import retry_until
 from libtmux.window import Window
 
 if t.TYPE_CHECKING:
@@ -264,6 +265,47 @@ def test_cmd_inserts_session_id(session: Session) -> None:
     assert cmd.cmd[-1] == last_arg
 
 
+def setup_shell_window(
+    session: Session,
+    window_name: str,
+    environment: dict[str, str] | None = None,
+) -> Window:
+    """Set up a shell window with consistent environment and prompt.
+
+    Args:
+        session: The tmux session to create the window in
+        window_name: Name for the new window
+        environment: Optional environment variables to set in the window
+
+    Returns
+    -------
+        The created Window object with shell ready
+    """
+    env = shutil.which("env")
+    assert env is not None, "Cannot find usable `env` in PATH."
+
+    window = session.new_window(
+        attach=True,
+        window_name=window_name,
+        window_shell=f"{env} PROMPT_COMMAND='' PS1='READY>' sh",
+        environment=environment,
+    )
+
+    pane = window.active_pane
+    assert pane is not None
+
+    # Wait for shell to be ready
+    def wait_for_prompt() -> bool:
+        try:
+            pane_contents = "\n".join(pane.capture_pane())
+            return "READY>" in pane_contents and len(pane_contents.strip()) > 0
+        except Exception:
+            return False
+
+    retry_until(wait_for_prompt, 2, raises=True)
+    return window
+
+
 @pytest.mark.skipif(
     has_lt_version("3.0"),
     reason="needs -e flag for new-window which was introduced in 3.0",
@@ -280,20 +322,25 @@ def test_new_window_with_environment(
     environment: dict[str, str],
 ) -> None:
     """Verify new window with environment vars."""
-    env = shutil.which("env")
-    assert env is not None, "Cannot find usable `env` in PATH."
-
-    window = session.new_window(
-        attach=True,
-        window_name="window_with_environment",
-        window_shell=f"{env} PS1='$ ' sh",
+    window = setup_shell_window(
+        session,
+        "window_with_environment",
         environment=environment,
     )
-    pane = window.attached_pane
+    pane = window.active_pane
     assert pane is not None
+
     for k, v in environment.items():
-        pane.send_keys(f"echo ${k}")
-        assert pane.capture_pane()[-2] == v
+        pane.send_keys(f"echo ${k}", literal=True)
+
+        def wait_for_output(value: str = v) -> bool:
+            try:
+                pane_contents = pane.capture_pane()
+                return any(value in line for line in pane_contents)
+            except Exception:
+                return False
+
+        retry_until(wait_for_output, 2, raises=True)
 
 
 @pytest.mark.skipif(
diff --git a/tests/legacy_api/test_window.py b/tests/legacy_api/test_window.py
index 23668f45c..28ae83137 100644
--- a/tests/legacy_api/test_window.py
+++ b/tests/legacy_api/test_window.py
@@ -4,7 +4,6 @@
 
 import logging
 import shutil
-import time
 import typing as t
 
 import pytest
@@ -13,6 +12,7 @@
 from libtmux.common import has_gte_version, has_lt_version, has_version
 from libtmux.pane import Pane
 from libtmux.server import Server
+from libtmux.test.retry import retry_until
 from libtmux.window import Window
 
 if t.TYPE_CHECKING:
@@ -389,6 +389,47 @@ def test_empty_window_name(session: Session) -> None:
     assert "''" in cmd.stdout
 
 
+def setup_shell_window(
+    session: Session,
+    window_name: str,
+    environment: dict[str, str] | None = None,
+) -> Window:
+    """Set up a shell window with consistent environment and prompt.
+
+    Args:
+        session: The tmux session to create the window in
+        window_name: Name for the new window
+        environment: Optional environment variables to set in the window
+
+    Returns
+    -------
+        The created Window object with shell ready
+    """
+    env = shutil.which("env")
+    assert env is not None, "Cannot find usable `env` in PATH."
+
+    window = session.new_window(
+        attach=True,
+        window_name=window_name,
+        window_shell=f"{env} PROMPT_COMMAND='' PS1='READY>' sh",
+        environment=environment,
+    )
+
+    pane = window.active_pane
+    assert pane is not None
+
+    # Wait for shell to be ready
+    def wait_for_prompt() -> bool:
+        try:
+            pane_contents = "\n".join(pane.capture_pane())
+            return "READY>" in pane_contents and len(pane_contents.strip()) > 0
+        except Exception:
+            return False
+
+    retry_until(wait_for_prompt, 2, raises=True)
+    return window
+
+
 @pytest.mark.skipif(
     has_lt_version("3.0"),
     reason="needs -e flag for split-window which was introduced in 3.0",
@@ -406,19 +447,36 @@ def test_split_window_with_environment(
 ) -> None:
     """Verify splitting window with environment variables."""
     env = shutil.which("env")
-    assert env is not None, "Cannot find usable `env` in Path."
+    assert env is not None, "Cannot find usable `env` in PATH."
 
-    window = session.new_window(window_name="split_window_with_environment")
-    pane = window.split_window(
-        shell=f"{env} PS1='$ ' sh",
+    window = setup_shell_window(session, "split_with_environment")
+    pane = window.split(
+        shell=f"{env} PROMPT_COMMAND='' PS1='READY>' sh",
         environment=environment,
     )
     assert pane is not None
-    # wait a bit for the prompt to be ready as the test gets flaky otherwise
-    time.sleep(0.05)
+
+    # Wait for shell to be ready
+    def wait_for_prompt() -> bool:
+        try:
+            pane_contents = "\n".join(pane.capture_pane())
+            return "READY>" in pane_contents and len(pane_contents.strip()) > 0
+        except Exception:
+            return False
+
+    retry_until(wait_for_prompt, 2, raises=True)
+
     for k, v in environment.items():
-        pane.send_keys(f"echo ${k}")
-        assert pane.capture_pane()[-2] == v
+        pane.send_keys(f"echo ${k}", literal=True)
+
+        def wait_for_output(value: str = v) -> bool:
+            try:
+                pane_contents = pane.capture_pane()
+                return any(value in line for line in pane_contents)
+            except Exception:
+                return False
+
+        retry_until(wait_for_output, 2, raises=True)
 
 
 @pytest.mark.skipif(
diff --git a/tests/test_session.py b/tests/test_session.py
index b9d42433f..dae5448f1 100644
--- a/tests/test_session.py
+++ b/tests/test_session.py
@@ -379,11 +379,10 @@ def test_new_window_with_environment(
     pane = window.active_pane
     assert pane is not None
 
-    for k, expected_value in environment.items():
+    for k, v in environment.items():
         pane.send_keys(f"echo ${k}", literal=True)
 
-        # Wait for command output
-        def wait_for_output(value: str = expected_value) -> bool:
+        def wait_for_output(value: str = v) -> bool:
             try:
                 pane_contents = pane.capture_pane()
                 return any(value in line for line in pane_contents)
diff --git a/tests/test_window.py b/tests/test_window.py
index 0be62613e..d938d9791 100644
--- a/tests/test_window.py
+++ b/tests/test_window.py
@@ -4,7 +4,6 @@
 
 import logging
 import shutil
-import time
 import typing as t
 
 import pytest
@@ -19,6 +18,7 @@
 )
 from libtmux.pane import Pane
 from libtmux.server import Server
+from libtmux.test.retry import retry_until
 from libtmux.window import Window
 
 if t.TYPE_CHECKING:
@@ -444,15 +444,32 @@ def test_split_with_environment(
 
     window = session.new_window(window_name="split_with_environment")
     pane = window.split(
-        shell=f"{env} PS1='$ ' sh",
+        shell=f"{env} PROMPT_COMMAND='' PS1='READY>' sh",
         environment=environment,
     )
     assert pane is not None
-    # wait a bit for the prompt to be ready as the test gets flaky otherwise
-    time.sleep(0.05)
+
+    # Wait for shell to be ready
+    def wait_for_prompt() -> bool:
+        try:
+            pane_contents = "\n".join(pane.capture_pane())
+            return "READY>" in pane_contents and len(pane_contents.strip()) > 0
+        except Exception:
+            return False
+
+    retry_until(wait_for_prompt, 2, raises=True)
+
     for k, v in environment.items():
-        pane.send_keys(f"echo ${k}")
-        assert pane.capture_pane()[-2] == v
+        pane.send_keys(f"echo ${k}", literal=True)
+
+        def wait_for_output(value: str = v) -> bool:
+            try:
+                pane_contents = pane.capture_pane()
+                return any(value in line for line in pane_contents)
+            except Exception:
+                return False
+
+        retry_until(wait_for_output, 2, raises=True)
 
 
 @pytest.mark.skipif(

From 82da1409965153bebd05eb66f5bb3985781a6d04 Mon Sep 17 00:00:00 2001
From: Tony Narlock <tony@git-pull.com>
Date: Sun, 23 Feb 2025 16:45:53 -0600
Subject: [PATCH 7/7] doctest(Pane) send_keys

---
 src/libtmux/pane.py | 21 ++++++++++++++++-----
 1 file changed, 16 insertions(+), 5 deletions(-)

diff --git a/src/libtmux/pane.py b/src/libtmux/pane.py
index a60bb36f6..c921919f5 100644
--- a/src/libtmux/pane.py
+++ b/src/libtmux/pane.py
@@ -371,19 +371,30 @@ def send_keys(
 
         Examples
         --------
-        >>> pane = window.split(shell='sh')
+        >>> import shutil
+        >>> pane = window.split(
+        ...     shell=f"{shutil.which('env')} PROMPT_COMMAND='' PS1='READY>' sh")
+        >>> from libtmux.test.retry import retry_until
+        >>> def wait_for_prompt() -> bool:
+        ...     try:
+        ...         pane_contents = "\n".join(pane.capture_pane())
+        ...         return "READY>" in pane_contents and len(pane_contents.strip()) > 0
+        ...     except Exception:
+        ...         return False
+        >>> retry_until(wait_for_prompt, 2, raises=True)
+        True
         >>> pane.capture_pane()
-        ['$']
+        ['READY>']
 
         >>> pane.send_keys('echo "Hello world"', enter=True)
 
         >>> pane.capture_pane()
-        ['$ echo "Hello world"', 'Hello world', '$']
+        ['READY>echo "Hello world"', 'Hello world', 'READY>']
 
         >>> print('\n'.join(pane.capture_pane()))  # doctest: +NORMALIZE_WHITESPACE
-        $ echo "Hello world"
+        READY>echo "Hello world"
         Hello world
-        $
+        READY>
         """
         prefix = " " if suppress_history else ""