Textualize/textual

`margin` CSS property leads to `textual.pilot.OutOfBounds` during testing.

Closed this issue · 6 comments

I am having trouble when testing clicking widgets on a fairly busy TUI screen, resulting in the error:

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

when trying to click widgets e.g.:

widget = pilot.app.screen.query_one(id)
widget.scroll_visible(animate=False)
await pilot.pause()
await pilot.click(id, control=control)
await pilot.pause()

Interestingly, this seems to be caused by the margin CSS property. Often removing or changing the margins results in the test working without issue. I will post my current example here, happy to work on an MRE if no obvious solutions come to mind.

image

The issue occurs when trying to click the 'Upload' / 'Download switch underneath the 'Transfer' button. This actually seems to occur whenever trying to click widgets that are below other widgets, when there is a margin set.

The TCSS for the relevant widgets are:

#transfer_switch_container {
    align-vertical: middle;
    margin: 0 0 0 1
}
#transfer_switch_container > Label{
    margin: 1 0 0 0;
}#transfer_transfer_button {
    width: 25;
    margin: 2 0 0 0;
}

Changing the first entry of the margin CSS for all three widgets solves the problem. However in practice the TUI looks poorly aligned so this is not an option for deployed version.

Thanks for your help!
Joe

We found the following entry in the FAQ which you may find helpful:

Feel free to close this issue if you found an answer in the FAQ. Otherwise, please give us a little time to review.

This is an automated reply, generated by FAQtory

I would imagine that the issue is what the exception says:

Target offset is outside of currently-visible screen region.

The click is happening outside of the screen; that that happens when adding/removing margin is, I would imagine, coincidental, and just happens to be bumping where the click would happen outside of the screen. Consider this extreme example:

from asyncio import run
from textual.app import App, ComposeResult
from textual.widget import Widget

class PilotMarginApp(App[None]):

    CSS = """
    #click-me {
        margin: 200;
        width: 20;
        height: 10;
        border: solid red;
        background: green;
    }
    """

    def compose(self) -> ComposeResult:
        yield Widget(id="click-me")

async def test_app():
    async with PilotMarginApp().run_test() as pilot:
        await pilot.click("#click-me")

if __name__ == "__main__":
    run(test_app())

This throws the same exception. Remove the margin from this, or drop to something like 1, and it runs fine.

You probably want to make your "terminal" big enough to handle the display you're attempting to test. For example, I can make my code above work by making the "terminal" 400x400 (note the size parameter for run_test):

from asyncio import run
from textual.app import App, ComposeResult
from textual.widget import Widget

class PilotMarginApp(App[None]):

    CSS = """
    #click-me {
        margin: 200;
        width: 20;
        height: 10;
        border: solid red;
        background: green;
    }
    """

    def compose(self) -> ComposeResult:
        yield Widget(id="click-me")

async def test_app():
    async with PilotMarginApp().run_test(size=(400, 400)) as pilot:
        await pilot.click("#click-me")

if __name__ == "__main__":
    run(test_app())

Long story short: your test seems to be trying to do something the user can't do; click on a widget that isn't visible to click.

Thanks @davep! that did the trick, I had played around with that parameter but in an inconsistent way and with too small I size I believe. Yet again a super quick response describing an easy-to-implement solution to an issue, thanks!!

Don't forget to star the repository!

Follow @textualizeio for Textual updates.

Glad to hear that solved it for you. :-)

@JoeZiminski you can do pilot.app.save_screenshot() and have a look at the resulting SVG file, to see if the thing you are clicking is really on-screen at that moment.