This code is a little variation of my other trick:
https://code.activestate.com/recipes/580721-tkinter-remote-debugging
It makes more easy to create tests for Tkinter.
Install rpyc:
pip install rpyc
Save the code below to a file named for example tkinter_selenium.py.
This is the usage:
python tkinter_selenium.py [-h] [-p PORT] filename
where filename is the path to main file of Tkinter application, and port is an optional port number for the remote interpreter. Otherwise it uses default port.
Then in another python interpreter you can interact with the application. For example, write:
import rpyc
c = rpyc.classic.connect("localhost")
c.execute("""
from Tkinter import Button, Toplevel
import tkMessageBox
responsive_button = Button(Toplevel(), text="It's responsive", command = lambda:tkMessageBox.showinfo("alert window", "It's responsive!"))
responsive_button.pack()
""")
responsive_button = c.eval("responsive_button")
responsive_button.invoke()
(This example only works for Python 2. For python 3 use "tkinter" instead of "Tkinter" and so on)
Use port keyword argument to "repyc.classic.connect" if you want a different port number than default port. For example:
import rpyc
c = rpyc.classic.connect("localhost", port=8000)
For the selection of tkinter widgets, I have this other trick:
https://code.activestate.com/recipes/580738-tkinter-selectors
Using this remote debugging utility and selectors makes easy to test tkinter applications similar to selenium.
This utility could be used not only for Tkinter applications. It could be used also for wxpython, pygtk and pyqt applications.
NOTE: Interact with remote application using python of same version. If the application is running using a Python 2 interpreter, use a python 2 interpreter for remote interaction. Similarly use a python 3 interpreter for remote interaction with a python 3 application.
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 | # Author: Miguel Martinez Lopez
from rpyc.utils.server import ThreadedServer
from rpyc.utils.classic import DEFAULT_SERVER_PORT
from rpyc.core.service import Service, ModuleNamespace
from rpyc.lib.compat import execute, is_py3k
import sys
import os
import threading
import argparse
EXECUTED_PYTHON_FILE = False
def exec_python(filepath, namespace):
global EXECUTED_PYTHON_FILE
if EXECUTED_PYTHON_FILE:
raise Exception("exec_python can be used only one time")
EXECUTED_PYTHON_FILE = True
filepath = os.path.abspath(filepath)
sys.path = [os.path.dirname(filepath)] + sys.path[1:]
namespace["__file__"] = filepath
namespace["__name__"] = "__main__"
with open(filepath, 'rb') as file:
exec(compile(file.read(), filepath, 'exec'), namespace)
class PublicService(Service):
exposed_namespace = {}
def on_connect(self):
self._conn._config.update(dict(
allow_all_attrs = True,
allow_pickle = True,
allow_getattr = True,
allow_setattr = True,
allow_delattr = True,
import_custom_exceptions = True,
instantiate_custom_exceptions = True,
instantiate_oldstyle_exceptions = True,
))
# shortcuts
self._conn.modules = ModuleNamespace(self._conn.root.getmodule)
self._conn.eval = self._conn.root.eval
self._conn.execute = self._conn.root.execute
self._conn.namespace = self._conn.root.namespace
if is_py3k:
self._conn.builtin = self._conn.modules.builtins
else:
self._conn.builtin = self._conn.modules.__builtin__
self._conn.builtins = self._conn.builtin
def exposed_execute(self, text):
"""execute arbitrary code (using ``exec``)"""
execute(text, self.exposed_namespace)
def exposed_eval(self, text):
"""evaluate arbitrary code (using ``eval``)"""
return eval(text, self.exposed_namespace)
def exposed_getmodule(self, name):
"""imports an arbitrary module"""
return __import__(name, None, None, "*")
def exposed_getconn(self):
"""returns the local connection instance to the other side"""
return self._conn
parser = argparse.ArgumentParser(description='Remote debugging and testing')
parser.add_argument('filename', help="Path to script")
parser.add_argument('-p', '--port', action="store", dest="port", default=DEFAULT_SERVER_PORT, help="Remote interpreter port", type=int)
args = parser.parse_args()
thread = threading.Thread(target=lambda: ThreadedServer(PublicService, hostname = "localhost", port=args.port).start())
thread.daemon=True
thread.start()
exec_python(args.filename, PublicService.exposed_namespace)
|