1

I have a load of Python tkinter GUI tests that run fine locally (on Windows) but do not run using GitHub Actions. The error I get is:

_tkinter.TclError: no display name and no $DISPLAY environment variable

I believe this is because there is no logical graphical interface for tkinter to use under GitHub Actions. Is there a way to indicate that I don't care about the display being visible so that my tests will run?

Paul D Smith
  • 639
  • 5
  • 16
  • Try splitting your GUI and other scripts in separate files and use a `try`/`except` when importing your GUI file. – TheLizzard May 30 '21 at 10:57
  • You could try [this](https://stackoverflow.com/a/66952101/13629335). – Thingamabobs May 30 '21 at 11:01
  • @Atlas435 Many thanks, that worked a treat. I did have to `sudo` the commands but other than that, worked first time. Now I just have to write some better tests of the GUI code :-). – Paul D Smith Jun 02 '21 at 07:29
  • Does this answer your question? [TclError: no display name and no $DISPLAY environment variable in Google Colab](https://stackoverflow.com/questions/49478228/tclerror-no-display-name-and-no-display-environment-variable-in-google-colab) – Thingamabobs Jun 02 '21 at 15:18
  • @PaulDSmith you are welcome. Im glad it worked out this way. – Thingamabobs Jun 02 '21 at 15:19
  • hi i am encountering the same issue but I didn't was able to solve or understand exactly what todo , can someone please explain in simple terms and what to do and where to do to solve this problem ? – Dolev Dublon Dec 29 '22 at 20:10

1 Answers1

0

Add this to yml file:

- name: Install dependencies
  run: |
    # ...
    sudo apt-get install -y xvfb 

Add this before root = tk.Tk():

if os.name != "nt" and os.getenv("GITHUB_ACTIONS"):
    os.system('Xvfb :1 -screen 0 1600x1200x16  &')
    os.environ["DISPLAY"] = ":1.0"

Patch the mainloop functions of tkinter and the tkinter widgets you use. If you use winfo_* functions patch those too. Example:

"""
TkConsole Test Patches

This module provides fake classes to patch certain Tkinter classes for testing purposes.
These fake classes allow for mocking the behavior of Tkinter's classes during unit tests.

Classes:
    FakeTk (class): A fake Tkinter root window class for testing.
    FakeScrolledText (class): A fake ScrolledText class for testing.

Usage:
    These fake classes are used in unit tests to mock the behavior of Tkinter classes and methods,
    ensuring that the unit tests are isolated and independent of the actual Tkinter functionality.
"""

import tkinter as tk
from tkinter import scrolledtext


class FakeTk(tk.Tk):
    """
    FakeTk class

    This class provides a fake implementation of the Tkinter root window class for testing purposes.

    Methods:
        mainloop(self, **kwargs): Mocks the mainloop method of Tkinter's root window.
    """
    def __init__(self):
        """
        Initialize a fake Tkinter root window for testing purposes.

        Returns:
            None
        """
        super(FakeTk, self).__init__()

    def mainloop(self, **kwargs):
        """
        Mocks the mainloop method of Tkinter's root window.

        Args:
            **kwargs: Additional keyword arguments (ignored).

        Returns:
            None
        """
        pass

    class Entry(tk.Entry):
        """
        Fake Entry class

        This class provides a fake implementation of the Entry widget for testing purposes.

        Methods:
            mainloop(self, n: int = ...) -> None: Mocks the mainloop method of Tkinter's Entry widget.
        """
        def __init__(self):
            """
            Initialize a fake Entry widget for testing purposes.

            Returns:
                None
            """
            super(FakeTk.Entry, self).__init__()

        def mainloop(self, n: int = ...) -> None:
            """
            Mocks the mainloop method of Tkinter's Entry widget.

            Args:
                n (int): Number of iterations (ignored).

            Returns:
                None
            """
            pass


class FakeScrolledText(scrolledtext.ScrolledText):
    """
    FakeScrolledText class

    This class provides a fake implementation of the ScrolledText class for testing purposes.

    Methods:
        mainloop(self, n: int = ...) -> None: Mocks the mainloop method of Tkinter's ScrolledText.
        winfo_height(self) -> int: Mocks the winfo_height method of Tkinter's ScrolledText.
    """
    def __init__(self, master=None, **kw):
        """
        Initialize a fake ScrolledText widget for testing purposes.

        Args:
            master: The master widget (ignored).
            **kw: Additional keyword arguments (ignored).

        Returns:
            None
        """
        super(FakeScrolledText, self).__init__()

    def mainloop(self, n: int = ...) -> None:
        """
        Mocks the mainloop method of Tkinter's ScrolledText.

        Args:
            n (int): Number of iterations (ignored).

        Returns:
            None
        """
        pass

    def winfo_height(self) -> int:
        """
        Mocks the winfo_height method of Tkinter's ScrolledText.

        Returns:
            int: Height of the ScrolledText.
        """
        return 500

Use the patched functions as in the following example:

def setUp(self):
    """
    Set up the test environment.

    This method creates a Tkinter root window and initializes a Console instance for testing.
    It also initializes attributes for accessing the text area, entry widget, and user_input_var.

    Returns:
        None
    """
    if os.name != "nt" and os.getenv("GITHUB_ACTIONS"):
        os.system('Xvfb :1 -screen 0 1600x1200x16  &')
        os.environ["DISPLAY"] = ":1.0"
    self.root = FakeTk()
    self.console = Console(self.root)
    self.console.text_area = FakeScrolledText(
        self.console.parent, wrap=tk.WORD, font=self.console.font, background=self.console.background,
        foreground=self.console.foreground, padx=0,
        pady=0, borderwidth=0, border=0.0, insertborderwidth=0, selectborderwidth=0
    )
    self.text_area = self.console.text_area
    self.entry = self.console.entry
    self.user_input_var = self.console.user_input_var

And use threading and queue modules for threading. If you don't do this all tkinter windows open in same time and causes error.

Eftal Gezer
  • 191
  • 1
  • 8