diff --git a/Default (Linux).sublime-keymap b/Default (Linux).sublime-keymap index fbcc251..6abdaf1 100644 --- a/Default (Linux).sublime-keymap +++ b/Default (Linux).sublime-keymap @@ -4,5 +4,7 @@ { "keys": ["ctrl+alt+a"], "command": "scan_index" }, { "keys": ["ctrl+alt+s"], "command": "scan" }, { "keys": ["ctrl+alt+i"], "command": "scan_and_index_open_tab" }, - { "keys": ["ctrl+alt+r"], "command": "setting_importer" } + { "keys": ["ctrl+alt+r"], "command": "setting_importer" }, + { "keys": ["f8"], "command": "start_current_robot_test" }, + { "keys": ["shift+f8"], "command": "start_current_robot_test", "args": {"repeat": true}} ] \ No newline at end of file diff --git a/Default (OSX).sublime-keymap b/Default (OSX).sublime-keymap index fbcc251..6abdaf1 100644 --- a/Default (OSX).sublime-keymap +++ b/Default (OSX).sublime-keymap @@ -4,5 +4,7 @@ { "keys": ["ctrl+alt+a"], "command": "scan_index" }, { "keys": ["ctrl+alt+s"], "command": "scan" }, { "keys": ["ctrl+alt+i"], "command": "scan_and_index_open_tab" }, - { "keys": ["ctrl+alt+r"], "command": "setting_importer" } + { "keys": ["ctrl+alt+r"], "command": "setting_importer" }, + { "keys": ["f8"], "command": "start_current_robot_test" }, + { "keys": ["shift+f8"], "command": "start_current_robot_test", "args": {"repeat": true}} ] \ No newline at end of file diff --git a/Default (Windows).sublime-keymap b/Default (Windows).sublime-keymap index f17f850..6abdaf1 100644 --- a/Default (Windows).sublime-keymap +++ b/Default (Windows).sublime-keymap @@ -4,6 +4,7 @@ { "keys": ["ctrl+alt+a"], "command": "scan_index" }, { "keys": ["ctrl+alt+s"], "command": "scan" }, { "keys": ["ctrl+alt+i"], "command": "scan_and_index_open_tab" }, - { "keys": ["ctrl+alt+r"], "command": "setting_importer" } - + { "keys": ["ctrl+alt+r"], "command": "setting_importer" }, + { "keys": ["f8"], "command": "start_current_robot_test" }, + { "keys": ["shift+f8"], "command": "start_current_robot_test", "args": {"repeat": true}} ] \ No newline at end of file diff --git a/README.md b/README.md index 8f68a8a..fe5bf0d 100644 --- a/README.md +++ b/README.md @@ -172,6 +172,27 @@ Such prefixes are testcase specific and must be ignored when looking up a keywor Configuration for typical Gherkin stories: "robot_framework_keyword_prefixes" : ["Given","When","Then","And","But"] +## Special settings for the `start_current_robot_test` command + +### robot_framework_output_path +Where to save output, report, log and debug files produced while running Robot tests. + +By default those files will be saved in `robot_framework_workspace`. + +### robot_framework_keep_console +If set to true, the console will stay open after running a test. + +By default the console is closed immediately after the test has finished. + +Works only on Windows. + +### robot_framework_consolewidth +Defines the width of the console output when running a test. + +### robot_framework_save_before_running_test +If set to `true` all views will be saved, before starting the current test. + + # Syntax definitions By default this plugin will be used with files which extension is `.robot` and plugin will use four spaces as cell separator. The @@ -228,6 +249,9 @@ menu is only displayed if cursor is in settings table and line contains `Librari The usage of the `Ctrl + Alt + a/s/i` commands is explained in the [Internal database for keywords and variables](https://github.com/andriyko/sublime-robot-framework-assistant/wiki/Internal-database-for-keywords-and-variables) wiki page +* Pressing `F8` will run the current test (where the [first] cursor is) +* Pressing `Shift+F8` will repeat the last test which was run by `F8` + # Snippets [Snippets](http://docs.sublimetext.info/en/latest/extensibility/snippets.html?highlight=snippets) are a Sublime Text feature to provide commonly used text templates diff --git a/commands/__init__.py b/commands/__init__.py index bfe5bd9..f1a7ab1 100644 --- a/commands/__init__.py +++ b/commands/__init__.py @@ -11,6 +11,7 @@ from .setting_import_helper import InsertImport from .setting_import_helper import SettingImporter from .show_documentation import ShowKeywordDocumentation +from .start_current_robot_test import StartCurrentRobotTestCommand __all__ = [ 'IndexOpenTabCommand', @@ -25,5 +26,6 @@ 'ScanIndexCommand', 'ScanOpenTabCommand', 'SettingImporter', - 'ShowKeywordDocumentation' + 'ShowKeywordDocumentation', + 'StartCurrentRobotTestCommand' ] diff --git a/commands/start_current_robot_test.py b/commands/start_current_robot_test.py new file mode 100644 index 0000000..ff946ce --- /dev/null +++ b/commands/start_current_robot_test.py @@ -0,0 +1,148 @@ +import json +import re +import os +import platform +import sublime, sublime_plugin +import subprocess + +class StartCurrentRobotTestCommand(sublime_plugin.TextCommand): + """ Run the current test case with Robot. + + The current test case is found by checking the position of the first selection region. + The algorithm will first check that the position is in the Testcase-table and will then + look backwards line by line for the next line which matches the definition of a Testcase-name. + + Uses settings from Robot Framework Assistant (Robot.sublime-settings): + "robot_framework_workspace": "path_were_to_run_Robot" + "robot_framework_output_path": "path_where_to_put_the_files_generated_by_Robot" + "robot_framework_keep_console": [true|false] (only Windows, default: false) + "robot_framework_consolewidth": + "robot_framework_save_before_running_test": [true|false] (default false) + + Example how to bind the function to a key: + { "keys": ["f8"], + "command": "start_current_robot_test" + }, + { "keys": ["shift+f8"], + "command": "start_current_robot_test", + "args": {"repeat": true} + } + """ + + table_regex = re.compile(r'^\s{0,1}\*+\s{0,1}(.+?)\s{0,1}\*') + keyword_regex = re.compile(r'^(\|\s)?(?P[^\s\|]+[^\|]*?)(\s*|\s*\|)?$') + prev_test_file = 'previousTest.json' + + def run(self, edit, repeat=False): + """ Runs current test with Robot, or repeats last test. + + Arguments: + repeat -- if true, the last test will be repeated, instead of running current test + """ + if repeat: + self.repeat_previous_started_test() + else: + self.run_current_test() + + def run_current_test(self): + line = self.get_current_line() + table_name = self.get_table_name(line) + if self.is_table_testcase(table_name): + testcase_name = self.get_keyword_name(line) + suite_name = self.get_suite_name() + self.start_robot(testcase_name, suite_name) + previous_started_test = {'testcase': testcase_name, 'suite': suite_name} + with open(self.prev_test_file, 'w') as jsonfile: + json.dump(previous_started_test, jsonfile) + else: + self.print_and_send('no testcase') + + def repeat_previous_started_test(self): + self.print_and_send('repeating') + with open(self.prev_test_file, 'r') as jsonfile: + previous_started_test = json.load(jsonfile) + testcase_name = previous_started_test['testcase'] + suite_name = previous_started_test['suite'] + self.start_robot(testcase_name, suite_name) + + def save_all_views(self): + for open_view in self.view.window().views(): + open_view.run_command('save') + + def start_robot(self, testcase, suite): + settings = sublime.load_settings('Robot.sublime-settings') + if settings.get('robot_framework_save_before_running_test'): + self.save_all_views() + self.print_and_send("starting Test '{}' in Suite '{}'".format(testcase, suite)) + work_path = settings.get('robot_framework_workspace') + output_path = settings.get('robot_framework_output_path') + consolewidth = settings.get('robot_framework_consolewidth') + + if platform.system() == 'Windows': + cmd = 'cmd ' + cmd += '/k ' if settings.get('robot_framework_keep_console') else '/c ' + cmd += 'pybot ' + else: + cmd = 'python -m robot.run ' + + if consolewidth: + cmd += '--consolewidth %s ' % consolewidth + + cmd += '--test "%s" ' % testcase + + if suite: + cmd += '--suite "%s" ' % suite + + if output_path: + cmd += '--outputdir %s ' % output_path + + cmd += '--debugfile debug.txt ' + cmd += '.' + + subprocess.Popen(cmd, cwd=work_path) + + def print_and_send(self, message): + print(message) + sublime.status_message(message) + + def get_current_line(self): + first_region = self.view.sel()[0] + return self.view.line(first_region.begin()) + + def row_to_line(self, row): + point = self.view.text_point(row, 0) + return self.view.line(point) + + def get_table_name(self, line): + row, _ = self.view.rowcol(line.begin()) + found = None + while not found and row >= 0: + line_str = self.view.substr(self.row_to_line(row)) + match = self.table_regex.search(line_str) + if match: + found = match.group(1) + row -= 1 + return found + + def is_table_testcase(self, table_name): + return table_name.upper() in ['TEST CASES', 'TEST CASE', 'TESTCASES', 'TESTCASE'] + + def get_keyword_name(self, line): + row, _ = self.view.rowcol(line.begin()) + found = None + while not found and row >= 0: + line_str = self.view.substr(self.row_to_line(row)) + match = self.keyword_regex.search(line_str) + if match: + found = match.group('keyword') + row -= 1 + return found + + def get_suite_name(self): + file_name = self.view.file_name() + suite_name = None + if file_name.endswith('.robot') or file_name.endswith('.txt'): + p_stop = file_name.rfind('.') + p_start = file_name.rfind(os.sep) + 1 + suite_name = file_name[p_start:p_stop] + return suite_name