Textualize / pytest-textual-snapshot

Snapshot testing for Textual applications
MIT License
21 stars 4 forks source link

Is there a way to "show" an app that is being tested (i.e. `headless = False`)? #12

Closed asmith26 closed 1 month ago

asmith26 commented 1 month ago

Hi there/@darrenburns , I've created a simple app to show/hide a LoadingIndicator on a click:

from textual.app import App, ComposeResult
from textual.widgets import LoadingIndicator

class MyApp(App):  # type: ignore
    listening_indicator = LoadingIndicator()
    listening_indicator.display = False

    def compose(self) -> ComposeResult:
        yield self.listening_indicator

    def on_click(self) -> None:
        self.listening_indicator.display = not self.listening_indicator.display

if __name__ == "__main__":
    app = MyApp()
    app.run()

I'm trying to test this using this lib using the following code:

import pytest
from textual.pilot import Pilot

from main import MyApp

@pytest.mark.asyncio
def test_on_click(snap_compare):

    async def run_before(pilot: Pilot):
        await pilot.click()

    app = MyApp()
    assert snap_compare(app, run_before=run_before)

To generate the expected result, I'm trying to run pytest --snapshot-update, but unfortunately this seems to yield:

textual.pilot.OutOfBounds: Target offset is outside of currently-visible screen region.

I think this means my test is clicking outside of the textual app. I'm running this with the PyCharm debugger but I'm finding it hard to debug because I can't see what the test see - this gave me a idea: it would be helpful to be able to view a test/textual app being ran to help debug this.

In case helpful, I've used Python Selenium previously, and I think with Selenium it's called headless mode (and in the case of when I'm debugging my problems, I would set headless=False).

Not sure if this feature already exists? I would also welcome any help on how to fix my problem.

Thanks!

darrenburns commented 1 month ago

Hey 👋

Firstly, you should move the creation and setting of display=False inside your compose method:

    self.listening_indicator = LoadingIndicator()
    self.listening_indicator.display = False
    yield self.listening_indicator

Doing it as you currently are is undefined behaviour and will likely cause issues.

For your question:

There's no "built-in" way of doing what you're asking, but here's a great comment from our Discord from @davidfokkema which might help:

Do you want to see things happening while you're writing your test just to check on things? Once your test is written and you run it through pytest there's no way to do that. While you're testing your test, so to speak, you can call your test manually using e.g.:

import asyncio
asyncio.run(test_keys())

(method name taken from the Textual testing guide). In your test, make sure to call run_test with headless=False:

    async with app.run_test(headless=False) as pilot:

if you run your test script (not through pytest!) you'll see your app. You can insert a few await pilot.pause(delay=0.5) to actually see what's going on.

asmith26 commented 1 month ago

This works well for me, many thanks for your help! :)