This repository has been archived on 2025-01-25. You can view files and clone it, but cannot push or open issues or pull requests.
PyBitmessage-2025-01-25/mockenv/lib/python3.6/site-packages/telenium/web.py
2022-07-22 16:13:59 +05:30

660 lines
20 KiB
Python

# coding=utf-8
import os
import re
import sys
import functools
import threading
import cherrypy
import json
import subprocess
import traceback
import webbrowser
import argparse
import shlex
from mako.template import Template
from uuid import uuid4
import telenium
from telenium.client import TeleniumHttpClient
from ws4py.server.cherrypyserver import WebSocketPlugin, WebSocketTool
from ws4py.websocket import WebSocket
from os.path import dirname, join, realpath
from time import time, sleep
SESSION_FN = ".telenium.dat"
TPL_EXPORT_UNITTEST = u"""<%!
def capitalize(text):
return text.capitalize()
def camelcase(text):
return "".join([x.strip().capitalize() for x in text.split()])
def funcname(text):
if text == "init":
return "init"
import re
suffix = re.sub(r"[^a-z0-9_]", "_", text.lower().strip())
return "test_{}".format(suffix)
def getarg(text):
import re
return re.match("^(\w+)", text).groups()[0]
%># coding=utf-8
import time
from telenium.tests import TeleniumTestCase
class ${settings["project"]|camelcase}TestCase(TeleniumTestCase):
% if env:
cmd_env = ${ env }
% endif
cmd_entrypoint = [u'${ settings["entrypoint"] }']
% for test in tests:
% if test["name"] == "setUpClass":
<% vself = "cls" %>
@classmethod
def setUpClass(cls):
super(${settings["project"]|camelcase}TestCase, cls).setUpClass()
% else:
<% vself = "self" %>
def ${test["name"]|funcname}(self):
% if not test["steps"]:
pass
% endif
% endif
% for key, value, arg1, arg2 in test["steps"]:
% if key == "wait":
${vself}.cli.wait('${value}', timeout=${settings["command-timeout"]})
% elif key == "wait_click":
${vself}.cli.wait_click('${value}', timeout=${settings["command-timeout"]})
% elif key == "assertExists":
${vself}.assertExists('${value}', timeout=${settings["command-timeout"]})
% elif key == "assertNotExists":
${vself}.assertNotExists('${value}', timeout=${settings["command-timeout"]})
% elif key == "assertAttributeValue":
attr_name = '${arg1|getarg}'
attr_value = ${vself}.cli.getattr('${value}', attr_name)
${vself}.assertTrue(eval('${arg1}', {attr_name: attr_value}))
% elif key == "setAttribute":
${vself}.cli.setattr('${value}', '${arg1}', ${arg2})
% elif key == "sendKeycode":
${vself}.cli.send_keycode('${value}')
% elif key == "sleep":
time.sleep(${value})
% elif key == "executeCode":
${vself}.assertTrue(self.cli.execute('${value}'))
% endif
% endfor
% endfor
"""
FILE_API_VERSION = 3
local_filename = None
def threaded(f):
@functools.wraps(f)
def _threaded(*args, **kwargs):
thread = threading.Thread(target=f, args=args, kwargs=kwargs)
thread.daemon = True
thread.start()
return thread
return _threaded
def funcname(text):
return text.lower().replace(" ", "_").strip()
def getarg(text):
return re.match("^(\w+)", text).groups()[0]
class ApiWebSocket(WebSocket):
t_process = None
cli = None
progress_count = 0
progress_total = 0
session = {
"settings": {
"project": "Test",
"entrypoint": "main.py",
"application-timeout": "10",
"command-timeout": "5",
"args": ""
},
"env": {},
"tests": [{
"id": str(uuid4()),
"name": "New test",
"steps": []
}]
}
def opened(self):
super(ApiWebSocket, self).opened()
self.load()
def closed(self, code, reason=None):
pass
def received_message(self, message):
msg = json.loads(message.data)
try:
getattr(self, "cmd_{}".format(msg["cmd"]))(msg["options"])
except:
traceback.print_exc()
def send_object(self, obj):
data = json.dumps(obj)
self.send(data, False)
def save(self):
self.session["version_format"] = FILE_API_VERSION
# check if the file changed
if local_filename is not None:
changed = False
try:
with open(local_filename) as fd:
data = json.loads(fd.read())
changed = data != self.session
except:
changed = True
self.send_object(["changed", changed])
with open(SESSION_FN, "w") as fd:
fd.write(json.dumps(self.session))
def load(self):
try:
with open(SESSION_FN) as fd:
session = json.loads(fd.read())
session = upgrade_version(session)
self.session.update(session)
except:
pass
def get_test(self, test_id):
for test in self.session["tests"]:
if test["id"] == test_id:
return test
def get_test_by_name(self, name):
for test in self.session["tests"]:
if test["name"] == name:
return test
@property
def is_running(self):
return self.t_process is not None
# command implementation
def cmd_recover(self, options):
if local_filename:
self.send_object(["is_local", True])
self.send_object(["settings", self.session["settings"]])
self.send_object(["env", dict(self.session["env"].items())])
tests = [{
"name": x["name"],
"id": x["id"]
} for x in self.session["tests"]]
self.send_object(["tests", tests])
if self.t_process is not None:
self.send_object(["status", "running"])
def cmd_save_local(self, options):
try:
assert local_filename is not None
# save json source
data = self.export("json")
with open(local_filename, "w") as fd:
fd.write(data)
# auto export to python
filename = local_filename.replace(".json", ".py")
data = self.export("python")
with open(filename, "w") as fd:
fd.write(data)
self.send_object(["save_local", "ok"])
self.send_object(["changed", False])
except Exception as e:
traceback.print_exc()
self.send_object(["save_local", "error", repr(e)])
def cmd_sync_env(self, options):
while self.session["env"]:
self.session["env"].pop(self.session["env"].keys()[0])
for key, value in options.get("env", {}).items():
self.session["env"][key] = value
self.save()
def cmd_sync_settings(self, options):
self.session["settings"] = options["settings"]
self.save()
def cmd_sync_test(self, options):
uid = options["id"]
for test in self.session["tests"]:
if test["id"] == uid:
test["name"] = options["name"]
test["steps"] = options["steps"]
self.save()
def cmd_add_test(self, options):
self.session["tests"].append({
"id": str(uuid4()),
"name": "New test",
"steps": []
})
self.save()
self.send_object(["tests", self.session["tests"]])
def cmd_clone_test(self, options):
for test in self.session["tests"]:
if test["id"] != options["test_id"]:
continue
clone_test = test.copy()
clone_test["id"] = str(uuid4())
clone_test["name"] += " (1)"
self.session["tests"].append(clone_test)
break
self.save()
self.send_object(["tests", self.session["tests"]])
def cmd_delete_test(self, options):
for test in self.session["tests"][:]:
if test["id"] == options["id"]:
self.session["tests"].remove(test)
if not self.session["tests"]:
self.cmd_add_test(None)
self.save()
self.send_object(["tests", self.session["tests"]])
def cmd_select(self, options):
if not self.cli:
status = "error"
results = "Application not running"
else:
try:
results = self.cli.highlight(options["selector"])
status = "ok"
except Exception as e:
status = "error"
results = u"{}".format(e)
self.send_object(["select", options["selector"], status, results])
def cmd_select_test(self, options):
test = self.get_test(options["id"])
self.send_object(["test", test])
@threaded
def cmd_pick(self, options):
if not self.cli:
return self.send_object(["pick", "error", "App is not started"])
objs = self.cli.pick(True)
return self.send_object(["pick", "success", objs])
@threaded
def cmd_execute(self, options):
self.execute()
def cmd_run_step(self, options):
self.run_step(options["id"], options["index"])
@threaded
def cmd_run_steps(self, options):
test = self.get_test(options["id"])
if test is None:
self.send_object(["alert", "Test not found"])
return
if not self.is_running:
ev_start, ev_stop = self.execute()
ev_start.wait()
if ev_stop.is_set():
return
self.run_test(test)
@threaded
def cmd_run_tests(self, options):
# restart always from scratch
self.send_object(["progress", "started"])
# precalculate the number of steps to run
count = sum([len(x["steps"]) for x in self.session["tests"]])
self.progress_count = 0
self.progress_total = count
try:
ev_start, ev_stop = self.execute()
ev_start.wait()
if ev_stop.is_set():
return
setup = self.get_test_by_name("setUpClass")
if setup:
if not self.run_test(setup):
return
setup = self.get_test_by_name("init")
if setup:
if not self.run_test(setup):
return
for test in self.session["tests"]:
if test["name"] in ("setUpClass", "init"):
continue
if not self.run_test(test):
return
finally:
self.send_object(["progress", "finished"])
def cmd_stop(self, options):
if self.t_process:
self.t_process.terminate()
def cmd_export(self, options):
try:
dtype = options["type"]
mimetype = {
"python": "text/plain",
"json": "application/json"
}[dtype]
ext = {"python": "py", "json": "json"}[dtype]
key = funcname(self.session["settings"]["project"])
filename = "test_ui_{}.{}".format(key, ext)
export = self.export(options["type"])
self.send_object(["export", export, mimetype, filename, dtype])
except Exception as e:
self.send_object(["export", "error", u"{}".format(e)])
def export(self, kind):
if kind == "python":
return Template(TPL_EXPORT_UNITTEST).render(
session=self.session, **self.session)
elif kind == "json":
self.session["version_format"] = FILE_API_VERSION
return json.dumps(
self.session, sort_keys=True, indent=4, separators=(',', ': '))
def execute(self):
ev_start = threading.Event()
ev_stop = threading.Event()
self._execute(ev_start=ev_start, ev_stop=ev_stop)
return ev_start, ev_stop
@threaded
def _execute(self, ev_start, ev_stop):
self.t_process = None
try:
self.start_process()
ev_start.set()
self.t_process.communicate()
self.send_object(["status", "stopped", None])
except Exception as e:
try:
self.t_process.terminate()
except:
pass
try:
self.send_object(["status", "stopped", u"{}".format(e)])
except:
pass
finally:
self.t_process = None
ev_stop.set()
def start_process(self):
url = "http://localhost:9901/jsonrpc"
process_start_timeout = 10
telenium_token = str(uuid4())
self.cli = cli = TeleniumHttpClient(url=url, timeout=10)
# entry no any previous telenium is running
try:
cli.app_quit()
sleep(2)
except:
pass
# prepare the application
entrypoint = self.session["settings"]["entrypoint"]
print(self.session)
args = shlex.split(self.session["settings"].get("args", ""))
cmd = [sys.executable, "-m", "telenium.execute", entrypoint] + args
cwd = dirname(entrypoint)
if not os.path.isabs(cwd):
cwd = os.getcwd()
env = os.environ.copy()
env.update(self.session["env"])
env["TELENIUM_TOKEN"] = telenium_token
# start the application
self.t_process = subprocess.Popen(cmd, env=env, cwd=cwd)
# wait for telenium server to be online
start = time()
while True:
try:
if cli.app_ready():
break
except Exception:
if time() - start > process_start_timeout:
raise Exception("timeout")
sleep(1)
# ensure the telenium we are connected are the same as the one we
# launched here
if cli.get_token() != telenium_token:
raise Exception("Connected to another telenium server")
self.send_object(["status", "running"])
def run_test(self, test):
test_id = test["id"]
try:
self.send_object(["test", test])
self.send_object(["run_test", test_id, "running"])
for index, step in enumerate(test["steps"]):
if not self.run_step(test_id, index):
return
return True
except Exception as e:
self.send_object(["run_test", test_id, "error", str(e)])
else:
self.send_object(["run_test", test_id, "finished"])
def run_step(self, test_id, index):
self.progress_count += 1
self.send_object(
["progress", "update", self.progress_count, self.progress_total])
try:
self.send_object(["run_step", test_id, index, "running"])
success = self._run_step(test_id, index)
if success:
self.send_object(["run_step", test_id, index, "success"])
return True
else:
self.send_object(["run_step", test_id, index, "error"])
except Exception as e:
self.send_object(["run_step", test_id, index, "error", str(e)])
def _run_step(self, test_id, index):
test = self.get_test(test_id)
if not test:
raise Exception("Unknown test")
cmd, selector, arg1, arg2 = test["steps"][index]
timeout = 5
if cmd == "wait":
return self.cli.wait(selector, timeout=timeout)
elif cmd == "wait_click":
self.cli.wait_click(selector, timeout=timeout)
return True
elif cmd == "wait_drag":
self.cli.wait_drag(
selector, target=arg1, duration=arg2, timeout=timeout)
return True
elif cmd == "assertExists":
return self.cli.wait(selector, timeout=timeout) is True
elif cmd == "assertNotExists":
return self.assertNotExists(self.cli, selector, timeout=timeout)
elif cmd == "assertAttributeValue":
attr_name = getarg(arg1)
attr_value = self.cli.getattr(selector, attr_name)
return bool(eval(arg1, {attr_name: attr_value}))
elif cmd == "setAttribute":
return self.cli.setattr(selector, arg1, eval(arg2))
elif cmd == "sendKeycode":
self.cli.send_keycode(selector)
return True
elif cmd == "sleep":
sleep(float(selector))
return True
elif cmd == "executeCode":
return self.cli.execute(selector)
def assertNotExists(self, cli, selector, timeout=-1):
start = time()
while True:
matches = cli.select(selector)
if not matches:
return True
if timeout == -1:
raise AssertionError("selector matched elements")
if timeout > 0 and time() - start > timeout:
raise Exception("Timeout")
sleep(0.1)
class Root(object):
@cherrypy.expose
def index(self):
raise cherrypy.HTTPRedirect("/static/index.html")
@cherrypy.expose
def ws(self):
pass
class WebSocketServer(object):
def __init__(self, host="0.0.0.0", port=8080, open_webbrowser=True):
super(WebSocketServer, self).__init__()
self.host = host
self.port = port
self.daemon = True
self.open_webbrowser = open_webbrowser
def run(self):
cherrypy.config.update({
"global": {
"environment": "production"
},
"server.socket_port": self.port,
"server.socket_host": self.host,
})
cherrypy.tree.mount(
Root(),
"/",
config={
"/": {
"tools.sessions.on": True
},
"/ws": {
"tools.websocket.on": True,
"tools.websocket.handler_cls": ApiWebSocket
},
"/static": {
"tools.staticdir.on":
True,
"tools.staticdir.dir":
join(realpath(dirname(__file__)), "static"),
"tools.staticdir.index":
"index.html"
}
})
cherrypy.engine.start()
url = "http://{}:{}/".format(self.host, self.port)
print("Telenium {} ready at {}".format(telenium.__version__, url))
if self.open_webbrowser:
webbrowser.open(url)
cherrypy.engine.block()
def stop(self):
cherrypy.engine.exit()
cherrypy.server.stop()
def preload_session(filename):
global local_filename
local_filename = filename
if not local_filename.endswith(".json"):
print("You can load only telenium-json files.")
sys.exit(1)
if not os.path.exists(filename):
print("Create new file at {}".format(local_filename))
if os.path.exists(SESSION_FN):
os.unlink(SESSION_FN)
else:
with open(filename) as fd:
session = json.loads(fd.read())
session = upgrade_version(session)
with open(SESSION_FN, "w") as fd:
fd.write(json.dumps(session))
def upgrade_version(session):
# automatically upgrade to latest version
version_format = session.get("version_format")
if version_format is None or version_format == FILE_API_VERSION:
return session
session["version_format"] += 1
version_format = session["version_format"]
print("Upgrade to version {}".format(version_format))
if version_format == 2:
# arg added in steps, so steps must have 3 arguments not 2.
for test in session["tests"]:
for step in test["steps"]:
if len(step) == 2:
step.append(None)
elif version_format == 3:
# arg added in steps, so steps must have 4 arguments not 3.
for test in session["tests"]:
for step in test["steps"]:
if len(step) == 3:
step.append(None)
return session
WebSocketPlugin(cherrypy.engine).subscribe()
cherrypy.tools.websocket = WebSocketTool()
def run():
parser = argparse.ArgumentParser(description="Telenium IDE")
parser.add_argument(
"filename",
type=str,
default=None,
nargs="?",
help="Telenium JSON file")
parser.add_argument(
"--new", action="store_true", help="Start a new session")
parser.add_argument(
"--port", type=int, default=8080, help="Telenium IDE port")
parser.add_argument(
"--notab",
action="store_true",
help="Prevent opening the IDE in the browser")
args = parser.parse_args()
if args.new:
if os.path.exists(SESSION_FN):
os.unlink(SESSION_FN)
if args.filename:
preload_session(args.filename)
server = WebSocketServer(port=args.port, open_webbrowser=not args.notab)
server.run()
server.stop()
if __name__ == "__main__":
run()