diff --git a/DebugLibrary/__init__.py b/DebugLibrary/__init__.py index 9f560e6..205d421 100644 --- a/DebugLibrary/__init__.py +++ b/DebugLibrary/__init__.py @@ -1,8 +1,11 @@ from .keywords import DebugKeywords +from .version import VERSION """A debug library and REPL for RobotFramework.""" class DebugLibrary(DebugKeywords): """Debug Library for RobotFramework.""" + ROBOT_LIBRARY_SCOPE = 'GLOBAL' + ROBOT_LIBRARY_VERSION = VERSION diff --git a/DebugLibrary/cmdcompleter.py b/DebugLibrary/cmdcompleter.py index d7ece67..672f1a5 100644 --- a/DebugLibrary/cmdcompleter.py +++ b/DebugLibrary/cmdcompleter.py @@ -1,6 +1,6 @@ from prompt_toolkit.completion import Completer, Completion -from .robotutils import parse_keyword +from .robotkeyword import parse_keyword class CmdCompleter(Completer): diff --git a/DebugLibrary/debugcmd.py b/DebugLibrary/debugcmd.py index 477cf2a..d56254f 100644 --- a/DebugLibrary/debugcmd.py +++ b/DebugLibrary/debugcmd.py @@ -1,14 +1,15 @@ import os +from robot.api import logger from robot.errors import ExecutionFailed, HandlerExecutionFailed from .cmdcompleter import CmdCompleter from .prompttoolkitcmd import PromptToolkitCmd -from .robotutils import (SELENIUM_WEBDRIVERS, get_builtin_libs, get_keywords, - get_lib_keywords, get_libs, get_libs_dict, - get_robot_instance, match_libs, - reset_robotframework_exception, run_keyword, - start_selenium_commands) +from .robotapp import get_robot_instance, reset_robotframework_exception +from .robotkeyword import get_keywords, get_lib_keywords, run_keyword +from .robotlib import get_builtin_libs, get_libs, get_libs_dict, match_libs +from .robotselenium import SELENIUM_WEBDRIVERS, start_selenium_commands +from .robotvar import assign_variable from .styles import (DEBUG_PROMPT_STYLE, get_debug_prompt_tokens, print_error, print_output) @@ -20,21 +21,23 @@ def run_robot_command(robot_instance, command): if not command: return + result = '' try: result = run_keyword(robot_instance, command) - if result: - head, message = result - print_output(head, message) except ExecutionFailed as exc: print_error('! keyword:', command) - print_error('!', exc.message) + print_error('! execution failed:', str(exc)) except HandlerExecutionFailed as exc: print_error('! keyword:', command) - print_error('!', exc.full_message) + print_error('! handler execution failed:', exc.full_message) except Exception as exc: print_error('! keyword:', command) print_error('! FAILED:', repr(exc)) + if result: + head, message = result + print_output(head, message) + class DebugCmd(PromptToolkitCmd): """Interactive debug shell for robotframework.""" @@ -100,8 +103,7 @@ def get_completer(self): 'Keyword[{0}.]: {1}'.format(keyword['lib'], keyword['doc']), )) - cmd_completer = CmdCompleter(commands, self) - return cmd_completer + return CmdCompleter(commands, self) def do_selenium(self, arg): """Start a selenium webdriver and open url in browser you expect. @@ -121,7 +123,8 @@ def complete_selenium(self, text, line, begin_idx, end_idx): """Complete selenium command.""" if len(line.split()) == 3: command, url, driver_name = line.lower().split() - return [d for d in SELENIUM_WEBDRIVERS if d.startswith(driver_name)] + return [driver for driver in SELENIUM_WEBDRIVERS + if driver.startswith(driver_name)] elif len(line.split()) == 2 and line.endswith(' '): return SELENIUM_WEBDRIVERS return [] @@ -143,9 +146,9 @@ def do_libs(self, args): for lib in get_libs(): print_output(' {}'.format(lib.name), lib.version) if lib.doc: - print(' {}'.format(lib.doc.split('\n')[0])) + logger.console(' {}'.format(lib.doc.split('\n')[0])) if '-s' in args: - print(' {}'.format(lib.source)) + logger.console(' {}'.format(lib.source)) print_output('<', 'Builtin libraries:') for name in sorted(get_builtin_libs()): print_output(' ' + name, '') @@ -199,8 +202,9 @@ def do_docs(self, kw_name): for lib in get_libs(): for keyword in get_lib_keywords(lib, long_format=True): if keyword['name'].lower() == kw_name.lower(): - print(keyword['doc']) + logger.console(keyword['doc']) return - print("could not find documentation for keyword {}".format(kw_name)) + + print_error('< not find keyword', kw_name) do_d = do_docs diff --git a/DebugLibrary/keywords.py b/DebugLibrary/keywords.py index 3ea8211..9ec6936 100644 --- a/DebugLibrary/keywords.py +++ b/DebugLibrary/keywords.py @@ -2,16 +2,12 @@ from .debugcmd import DebugCmd from .styles import print_output -from .version import VERSION from .webdriver import get_remote_url, get_session_id, get_webdriver_remote class DebugKeywords(object): """Debug Keywords for RobotFramework.""" - ROBOT_LIBRARY_SCOPE = 'GLOBAL' - ROBOT_LIBRARY_VERSION = VERSION - def debug(self): """Open a interactive shell, run any RobotFramework keywords. diff --git a/DebugLibrary/utils.py b/DebugLibrary/memoize.py similarity index 100% rename from DebugLibrary/utils.py rename to DebugLibrary/memoize.py diff --git a/DebugLibrary/prompttoolkitcmd.py b/DebugLibrary/prompttoolkitcmd.py index 03dd7bb..6c93b0f 100644 --- a/DebugLibrary/prompttoolkitcmd.py +++ b/DebugLibrary/prompttoolkitcmd.py @@ -10,8 +10,7 @@ class BaseCmd(cmd.Cmd): """Basic REPL tool.""" def emptyline(self): - """By default Cmd runs last command if an empty line is entered. - Disable it.""" + """Do not repeat last command if press enter only.""" def do_exit(self, arg): """Exit the interpreter. You can also use the Ctrl-D shortcut.""" diff --git a/DebugLibrary/robotapp.py b/DebugLibrary/robotapp.py new file mode 100644 index 0000000..a8e169c --- /dev/null +++ b/DebugLibrary/robotapp.py @@ -0,0 +1,24 @@ + +from robot.api import logger +from robot.libraries.BuiltIn import BuiltIn +from robot.running.signalhandler import STOP_SIGNAL_MONITOR + + +def get_robot_instance(): + """Get robotframework builtin instance as context.""" + return BuiltIn() + + +def reset_robotframework_exception(): + """Resume RF after press ctrl+c during keyword running.""" + if STOP_SIGNAL_MONITOR._signal_count: + STOP_SIGNAL_MONITOR._signal_count = 0 + STOP_SIGNAL_MONITOR._running_keyword = True + logger.info('Reset last exception of DebugLibrary') + + +def assign_variable(robot_instance, variable_name, args): + """Assign a robotframework variable.""" + variable_value = robot_instance.run_keyword(*args) + robot_instance._variables.__setitem__(variable_name, variable_value) + return variable_value diff --git a/DebugLibrary/robotkeyword.py b/DebugLibrary/robotkeyword.py new file mode 100644 index 0000000..0969d54 --- /dev/null +++ b/DebugLibrary/robotkeyword.py @@ -0,0 +1,76 @@ +import re + +from robot.libdocpkg.model import LibraryDoc + +from .memoize import memoize +from .robotlib import ImportedLibraryDocBuilder, get_libs +from .robotvar import assign_variable + +try: + from robot.variables.search import is_variable +except ImportError: + from robot.variables import is_var as is_variable # robotframework < 3.2 + +KEYWORD_SEP = re.compile(' +|\t') + + +def parse_keyword(command): + """Split a robotframework keyword string.""" + return KEYWORD_SEP.split(command) + + +@memoize +def get_lib_keywords(library, long_format=False): + """Get keywords of imported library.""" + lib = ImportedLibraryDocBuilder().build(library) + keywords = [] + for keyword in lib.keywords: + if long_format: + doc = keyword.doc + else: + doc = keyword.doc.split('\n')[0] + keywords.append({ + 'name': keyword.name, + 'lib': library.name, + 'doc': doc, + }) + return keywords + + +def get_keywords(): + """Get all keywords of libraries.""" + for lib in get_libs(): + yield from get_lib_keywords(lib) + + +def run_keyword(robot_instance, keyword): + """Run a keyword in robotframewrk environment.""" + if not keyword: + return + + keyword_args = parse_keyword(keyword) + keyword = keyword_args[0] + args = keyword_args[1:] + + is_comment = keyword.strip().startswith('#') + if is_comment: + return + + variable_name = keyword.rstrip('= ') + if is_variable(variable_name): + variable_only = not args + if variable_only: + display_value = ['Log to console', keyword] + robot_instance.run_keyword(*display_value) + else: + variable_value = assign_variable( + robot_instance, + variable_name, + args, + ) + echo = '{0} = {1!r}'.format(variable_name, variable_value) + return ('#', echo) + else: + output = robot_instance.run_keyword(keyword, *args) + if output: + return ('<', repr(output)) diff --git a/DebugLibrary/robotlib.py b/DebugLibrary/robotlib.py new file mode 100644 index 0000000..239e427 --- /dev/null +++ b/DebugLibrary/robotlib.py @@ -0,0 +1,41 @@ +import re + +from robot.libdocpkg.model import LibraryDoc +from robot.libdocpkg.robotbuilder import KeywordDocBuilder, LibraryDocBuilder +from robot.libraries import STDLIBS +from robot.running.namespace import IMPORTER + + +def get_builtin_libs(): + """Get robotframework builtin library names.""" + return list(STDLIBS) + + +def get_libs(): + """Get imported robotframework library names.""" + return sorted(IMPORTER._library_cache._items, key=lambda _: _.name) + + +def get_libs_dict(): + """Get imported robotframework libraries as a name -> lib dict""" + return {lib.name: lib for lib in IMPORTER._library_cache._items} + + +def match_libs(name=''): + """Find libraries by prefix of library name, default all""" + libs = [_.name for _ in get_libs()] + matched = [_ for _ in libs if _.lower().startswith(name.lower())] + return matched + + +class ImportedLibraryDocBuilder(LibraryDocBuilder): + + def build(self, lib): + libdoc = LibraryDoc( + name=lib.name, + doc=self._get_doc(lib), + doc_format=lib.doc_format, + ) + libdoc.inits = self._get_initializers(lib) + libdoc.keywords = KeywordDocBuilder().build_keywords(lib) + return libdoc diff --git a/DebugLibrary/robotselenium.py b/DebugLibrary/robotselenium.py new file mode 100644 index 0000000..d35ed62 --- /dev/null +++ b/DebugLibrary/robotselenium.py @@ -0,0 +1,27 @@ + +from .robotkeyword import parse_keyword + +SELENIUM_WEBDRIVERS = ['firefox', 'chrome', 'ie', + 'opera', 'safari', 'phantomjs', 'remote'] + + +def start_selenium_commands(arg): + """Start a selenium webdriver and open url in browser you expect. + + arg: [ or google] [ or firefox] + """ + yield 'import library SeleniumLibrary' + + # Set defaults, overriden if args set + url = 'http://www.google.com/' + browser = 'firefox' + if arg: + args = parse_keyword(arg) + if len(args) == 2: + url, browser = args + else: + url = arg + if '://' not in url: + url = 'http://' + url + + yield 'open browser %s %s' % (url, browser) diff --git a/DebugLibrary/robotutils.py b/DebugLibrary/robotutils.py deleted file mode 100644 index 9acfcea..0000000 --- a/DebugLibrary/robotutils.py +++ /dev/null @@ -1,160 +0,0 @@ -import re - -from robot.api import logger -from robot.libdocpkg.model import LibraryDoc -from robot.libdocpkg.robotbuilder import KeywordDocBuilder, LibraryDocBuilder -from robot.libraries import STDLIBS -from robot.libraries.BuiltIn import BuiltIn -from robot.running.namespace import IMPORTER -from robot.running.signalhandler import STOP_SIGNAL_MONITOR - -from .utils import memoize - -try: - from robot.variables.search import is_variable -except ImportError: - from robot.variables import is_var as is_variable # robotframework < 3.2 - -KEYWORD_SEP = re.compile(' +|\t') - -SELENIUM_WEBDRIVERS = ['firefox', 'chrome', 'ie', - 'opera', 'safari', 'phantomjs', 'remote'] - - -def get_robot_instance(): - """Get robotframework builtin instance as context.""" - return BuiltIn() - - -def get_builtin_libs(): - """Get robotframework builtin library names.""" - return list(STDLIBS) - - -def get_libs(): - """Get imported robotframework library names.""" - return sorted(IMPORTER._library_cache._items, key=lambda _: _.name) - - -def get_libs_dict(): - """Get imported robotframework libraries as a name -> lib dict""" - return {lib.name: lib for lib in IMPORTER._library_cache._items} - - -def match_libs(name=''): - """Find libraries by prefix of library name, default all""" - libs = [_.name for _ in get_libs()] - matched = [_ for _ in libs if _.lower().startswith(name.lower())] - return matched - - -class ImportedLibraryDocBuilder(LibraryDocBuilder): - - def build(self, lib): - libdoc = LibraryDoc( - name=lib.name, - doc=self._get_doc(lib), - doc_format=lib.doc_format, - ) - libdoc.inits = self._get_initializers(lib) - libdoc.keywords = KeywordDocBuilder().build_keywords(lib) - return libdoc - - -@memoize -def get_lib_keywords(library, long_format=False): - """Get keywords of imported library.""" - lib = ImportedLibraryDocBuilder().build(library) - keywords = [] - for keyword in lib.keywords: - if long_format: - doc = keyword.doc - else: - doc = keyword.doc.split('\n')[0] - keywords.append({ - 'name': keyword.name, - 'lib': library.name, - 'doc': doc, - }) - return keywords - - -def get_keywords(): - """Get all keywords of libraries.""" - for lib in get_libs(): - yield from get_lib_keywords(lib) - - -def parse_keyword(command): - """Split a robotframework keyword string.""" - return KEYWORD_SEP.split(command) - - -def reset_robotframework_exception(): - """Resume RF after press ctrl+c during keyword running.""" - if STOP_SIGNAL_MONITOR._signal_count: - STOP_SIGNAL_MONITOR._signal_count = 0 - STOP_SIGNAL_MONITOR._running_keyword = True - logger.info('Reset last exception of DebugLibrary') - - -def assign_variable(robot_instance, variable_name, args): - """Assign a robotframework variable.""" - variable_value = robot_instance.run_keyword(*args) - robot_instance._variables.__setitem__(variable_name, variable_value) - return variable_value - - -def run_keyword(robot_instance, keyword): - """Run a keyword in robotframewrk environment.""" - if not keyword: - return - - keyword_args = parse_keyword(keyword) - keyword = keyword_args[0] - args = keyword_args[1:] - - is_comment = keyword.strip().startswith('#') - if is_comment: - return - - variable_name = keyword.rstrip('= ') - if is_variable(variable_name): - variable_only = not args - if variable_only: - display_value = ['Log to console', keyword] - robot_instance.run_keyword(*display_value) - else: - variable_value = assign_variable( - robot_instance, - variable_name, - args, - ) - echo = '{0} = {1!r}'.format(variable_name, variable_value) - return ('#', echo) - else: - output = robot_instance.run_keyword(keyword, *args) - if output: - return ('<', repr(output)) - - -def start_selenium_commands(arg): - """Start a selenium webdriver and open url in browser you expect. - - arg: [ or google] [ or firefox] - """ - yield 'import library SeleniumLibrary' - - # Set defaults, overriden if args set - url = 'http://www.google.com/' - browser = 'firefox' - if arg: - args = parse_keyword(arg) - if len(args) == 2: - url, browser = args - else: - url = arg - if '://' not in url: - url = 'http://' + url - - yield 'open browser %s %s' % (url, browser) diff --git a/DebugLibrary/robotvar.py b/DebugLibrary/robotvar.py new file mode 100644 index 0000000..9465b84 --- /dev/null +++ b/DebugLibrary/robotvar.py @@ -0,0 +1,5 @@ +def assign_variable(robot_instance, variable_name, args): + """Assign a robotframework variable.""" + variable_value = robot_instance.run_keyword(*args) + robot_instance._variables.__setitem__(variable_name, variable_value) + return variable_value diff --git a/DebugLibrary/shell.py b/DebugLibrary/shell.py index 4419cf6..fc380c5 100644 --- a/DebugLibrary/shell.py +++ b/DebugLibrary/shell.py @@ -16,22 +16,21 @@ def shell(): """A standalone robotframework shell.""" + default_no_logs = '-l None -x None -o None -L None -r None' + with tempfile.NamedTemporaryFile(prefix='robot-debug-', suffix='.robot', delete=False) as test_file: - try: - test_file.write(TEST_SUITE) - test_file.flush() + test_file.write(TEST_SUITE) + test_file.flush() - default_no_logs = '-l None -x None -o None -L None -r None' - if len(sys.argv) > 1: - args = sys.argv[1:] + [test_file.name] - else: - args = default_no_logs.split() + [test_file.name] + if len(sys.argv) > 1: + args = sys.argv[1:] + [test_file.name] + else: + args = default_no_logs.split() + [test_file.name] + try: rc = run_cli(args) - - sys.exit(rc) finally: test_file.close() # pybot will raise PermissionError on Windows NT or later @@ -40,6 +39,8 @@ def shell(): if os.path.exists(test_file.name): os.unlink(test_file.name) + sys.exit(rc) + if __name__ == "__main__": # Usage: python -m DebugLibrary.shell diff --git a/DebugLibrary/tests/test_debuglibrary.py b/DebugLibrary/tests/test_debuglibrary.py index 7dc5c4a..4678bb5 100755 --- a/DebugLibrary/tests/test_debuglibrary.py +++ b/DebugLibrary/tests/test_debuglibrary.py @@ -11,8 +11,7 @@ def functional_testing(): child = pexpect.spawn('/usr/bin/env python -m DebugLibrary.shell') child.expect('Enter interactive shell', timeout=5) - def check_prompt(keys, pattern): - child.write(keys) + def check_result(pattern): index = child.expect([pattern, pexpect.EOF, pexpect.TIMEOUT], timeout=TIMEOUT_SECONDS) try: @@ -21,17 +20,14 @@ def check_prompt(keys, pattern): print('Screen buffer: ', child._buffer.getvalue()) raise + def check_prompt(keys, pattern): + child.write(keys) + check_result(pattern) child.write('\003') # ctrl-c: reset inputs def check_command(command, pattern): child.sendline(command) - index = child.expect([pattern, pexpect.EOF, pexpect.TIMEOUT], - timeout=TIMEOUT_SECONDS) - try: - assert index == 0 - except AssertionError: - print('Screen buffer: ', child._buffer.getvalue()) - raise + check_result(pattern) check_prompt('key\t', 'keywords') check_prompt('key\t', 'Keyword Should Exist') @@ -46,6 +42,11 @@ def check_command(command, pattern): check_command('help keywords', 'Print keywords of libraries') check_command('k builtin', 'Sleep') check_command('d sleep', 'Pauses the test executed for the given time') + check_command('@{{list}} = Create List hello world', + "@{{list}} = ['helo', 'world']") + + check_command('fail', 'AssertionError') + check_command('nothing', "No keyword with name 'nothing' found.") return 'OK' diff --git a/DebugLibrary/webdriver.py b/DebugLibrary/webdriver.py index 28d9fde..2ad1eda 100644 --- a/DebugLibrary/webdriver.py +++ b/DebugLibrary/webdriver.py @@ -1,20 +1,20 @@ from robot.api import logger -from .robotutils import get_robot_instance +from .robotapp import get_robot_instance def get_remote_url(): """Get selenium URL for connecting to remote WebDriver.""" - s = get_robot_instance().get_library_instance('Selenium2Library') - url = s._current_browser().command_executor._url + se = get_robot_instance().get_library_instance('Selenium2Library') + url = se._current_browser().command_executor._url return url def get_session_id(): """Get selenium browser session id.""" - s = get_robot_instance().get_library_instance('Selenium2Library') - job_id = s._current_browser().session_id + se = get_robot_instance().get_library_instance('Selenium2Library') + job_id = se._current_browser().session_id return job_id @@ -24,7 +24,7 @@ def get_webdriver_remote(): remote_url = get_remote_url() session_id = get_session_id() - s = 'from selenium import webdriver;' \ + code = 'from selenium import webdriver;' \ 'd=webdriver.Remote(command_executor="%s",' \ 'desired_capabilities={});' \ 'd.session_id="%s"' % ( @@ -36,7 +36,7 @@ def get_webdriver_remote(): DEBUG FROM CONSOLE # geckodriver user please check https://stackoverflow.com/a/37968826/150841 %s -''' % (s)) - logger.info(s) +''' % (code)) + logger.info(code) - return s + return code