├── .gitignore ├── COPYRIGHT.txt ├── LICENSE ├── MANIFEST.in ├── README.md ├── atest └── test.robot ├── setup.py └── src └── Debugger ├── DebuggerGui.py ├── __init__.py └── logs ├── log.html ├── output.xml └── report.html /.gitignore: -------------------------------------------------------------------------------- 1 | # Byte-compiled / optimized / DLL files 2 | __pycache__/ 3 | *.py[cod] 4 | *$py.class 5 | 6 | # C extensions 7 | *.so 8 | 9 | # Distribution / packaging 10 | .Python 11 | build/ 12 | develop-eggs/ 13 | dist/ 14 | downloads/ 15 | eggs/ 16 | .eggs/ 17 | lib/ 18 | lib64/ 19 | parts/ 20 | sdist/ 21 | var/ 22 | wheels/ 23 | share/python-wheels/ 24 | *.egg-info/ 25 | .installed.cfg 26 | *.egg 27 | MANIFEST 28 | 29 | # PyInstaller 30 | # Usually these files are written by a python script from a template 31 | # before PyInstaller builds the exe, so as to inject date/other infos into it. 32 | *.manifest 33 | *.spec 34 | 35 | # Installer logs 36 | pip-log.txt 37 | pip-delete-this-directory.txt 38 | 39 | # Unit test / coverage reports 40 | htmlcov/ 41 | .tox/ 42 | .nox/ 43 | .coverage 44 | .coverage.* 45 | .cache 46 | nosetests.xml 47 | coverage.xml 48 | *.cover 49 | .hypothesis/ 50 | .pytest_cache/ 51 | 52 | # Translations 53 | *.mo 54 | *.pot 55 | 56 | # Django stuff: 57 | *.log 58 | local_settings.py 59 | db.sqlite3 60 | 61 | # Flask stuff: 62 | instance/ 63 | .webassets-cache 64 | 65 | # Scrapy stuff: 66 | .scrapy 67 | 68 | # Sphinx documentation 69 | docs/_build/ 70 | 71 | # PyBuilder 72 | target/ 73 | 74 | # Jupyter Notebook 75 | .ipynb_checkpoints 76 | 77 | # IPython 78 | profile_default/ 79 | ipython_config.py 80 | 81 | # pyenv 82 | .python-version 83 | 84 | # celery beat schedule file 85 | celerybeat-schedule 86 | 87 | # SageMath parsed files 88 | *.sage.py 89 | 90 | # Environments 91 | .env 92 | .venv 93 | env/ 94 | venv/ 95 | ENV/ 96 | env.bak/ 97 | venv.bak/ 98 | 99 | # Spyder project settings 100 | .spyderproject 101 | .spyproject 102 | 103 | # Rope project settings 104 | .ropeproject 105 | 106 | # mkdocs documentation 107 | /site 108 | 109 | # mypy 110 | .mypy_cache/ 111 | 112 | .idea/ 113 | createLibDoc.bat 114 | 115 | .dmypy.json 116 | dmypy.json 117 | 118 | # Pyre type checker 119 | .pyre/ 120 | 121 | ### Python Patch ### 122 | .venv/ 123 | CreatePiPWheel.bat 124 | CreatePiPWheel.sh 125 | src/CryptoLibrary/keys/ 126 | -------------------------------------------------------------------------------- /COPYRIGHT.txt: -------------------------------------------------------------------------------- 1 | Copyright 2018- René Rohner 2 | 3 | Licensed under the Apache License, Version 2.0 (the "License"); 4 | you may not use this file except in compliance with the License. 5 | You may obtain a copy of the License at 6 | 7 | http://www.apache.org/licenses/LICENSE-2.0 8 | 9 | Unless required by applicable law or agreed to in writing, software 10 | distributed under the License is distributed on an "AS IS" BASIS, 11 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 | See the License for the specific language governing permissions and 13 | limitations under the License. -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | Apache License 2 | Version 2.0, January 2004 3 | http://www.apache.org/licenses/ 4 | 5 | TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION 6 | 7 | 1. Definitions. 8 | 9 | "License" shall mean the terms and conditions for use, reproduction, 10 | and distribution as defined by Sections 1 through 9 of this document. 11 | 12 | "Licensor" shall mean the copyright owner or entity authorized by 13 | the copyright owner that is granting the License. 14 | 15 | "Legal Entity" shall mean the union of the acting entity and all 16 | other entities that control, are controlled by, or are under common 17 | control with that entity. For the purposes of this definition, 18 | "control" means (i) the power, direct or indirect, to cause the 19 | direction or management of such entity, whether by contract or 20 | otherwise, or (ii) ownership of fifty percent (50%) or more of the 21 | outstanding shares, or (iii) beneficial ownership of such entity. 22 | 23 | "You" (or "Your") shall mean an individual or Legal Entity 24 | exercising permissions granted by this License. 25 | 26 | "Source" form shall mean the preferred form for making modifications, 27 | including but not limited to software source code, documentation 28 | source, and configuration files. 29 | 30 | "Object" form shall mean any form resulting from mechanical 31 | transformation or translation of a Source form, including but 32 | not limited to compiled object code, generated documentation, 33 | and conversions to other media types. 34 | 35 | "Work" shall mean the work of authorship, whether in Source or 36 | Object form, made available under the License, as indicated by a 37 | copyright notice that is included in or attached to the work 38 | (an example is provided in the Appendix below). 39 | 40 | "Derivative Works" shall mean any work, whether in Source or Object 41 | form, that is based on (or derived from) the Work and for which the 42 | editorial revisions, annotations, elaborations, or other modifications 43 | represent, as a whole, an original work of authorship. For the purposes 44 | of this License, Derivative Works shall not include works that remain 45 | separable from, or merely link (or bind by name) to the interfaces of, 46 | the Work and Derivative Works thereof. 47 | 48 | "Contribution" shall mean any work of authorship, including 49 | the original version of the Work and any modifications or additions 50 | to that Work or Derivative Works thereof, that is intentionally 51 | submitted to Licensor for inclusion in the Work by the copyright owner 52 | or by an individual or Legal Entity authorized to submit on behalf of 53 | the copyright owner. For the purposes of this definition, "submitted" 54 | means any form of electronic, verbal, or written communication sent 55 | to the Licensor or its representatives, including but not limited to 56 | communication on electronic mailing lists, source code control systems, 57 | and issue tracking systems that are managed by, or on behalf of, the 58 | Licensor for the purpose of discussing and improving the Work, but 59 | excluding communication that is conspicuously marked or otherwise 60 | designated in writing by the copyright owner as "Not a Contribution." 61 | 62 | "Contributor" shall mean Licensor and any individual or Legal Entity 63 | on behalf of whom a Contribution has been received by Licensor and 64 | subsequently incorporated within the Work. 65 | 66 | 2. Grant of Copyright License. Subject to the terms and conditions of 67 | this License, each Contributor hereby grants to You a perpetual, 68 | worldwide, non-exclusive, no-charge, royalty-free, irrevocable 69 | copyright license to reproduce, prepare Derivative Works of, 70 | publicly display, publicly perform, sublicense, and distribute the 71 | Work and such Derivative Works in Source or Object form. 72 | 73 | 3. Grant of Patent License. Subject to the terms and conditions of 74 | this License, each Contributor hereby grants to You a perpetual, 75 | worldwide, non-exclusive, no-charge, royalty-free, irrevocable 76 | (except as stated in this section) patent license to make, have made, 77 | use, offer to sell, sell, import, and otherwise transfer the Work, 78 | where such license applies only to those patent claims licensable 79 | by such Contributor that are necessarily infringed by their 80 | Contribution(s) alone or by combination of their Contribution(s) 81 | with the Work to which such Contribution(s) was submitted. If You 82 | institute patent litigation against any entity (including a 83 | cross-claim or counterclaim in a lawsuit) alleging that the Work 84 | or a Contribution incorporated within the Work constitutes direct 85 | or contributory patent infringement, then any patent licenses 86 | granted to You under this License for that Work shall terminate 87 | as of the date such litigation is filed. 88 | 89 | 4. Redistribution. You may reproduce and distribute copies of the 90 | Work or Derivative Works thereof in any medium, with or without 91 | modifications, and in Source or Object form, provided that You 92 | meet the following conditions: 93 | 94 | (a) You must give any other recipients of the Work or 95 | Derivative Works a copy of this License; and 96 | 97 | (b) You must cause any modified files to carry prominent notices 98 | stating that You changed the files; and 99 | 100 | (c) You must retain, in the Source form of any Derivative Works 101 | that You distribute, all copyright, patent, trademark, and 102 | attribution notices from the Source form of the Work, 103 | excluding those notices that do not pertain to any part of 104 | the Derivative Works; and 105 | 106 | (d) If the Work includes a "NOTICE" text file as part of its 107 | distribution, then any Derivative Works that You distribute must 108 | include a readable copy of the attribution notices contained 109 | within such NOTICE file, excluding those notices that do not 110 | pertain to any part of the Derivative Works, in at least one 111 | of the following places: within a NOTICE text file distributed 112 | as part of the Derivative Works; within the Source form or 113 | documentation, if provided along with the Derivative Works; or, 114 | within a display generated by the Derivative Works, if and 115 | wherever such third-party notices normally appear. The contents 116 | of the NOTICE file are for informational purposes only and 117 | do not modify the License. You may add Your own attribution 118 | notices within Derivative Works that You distribute, alongside 119 | or as an addendum to the NOTICE text from the Work, provided 120 | that such additional attribution notices cannot be construed 121 | as modifying the License. 122 | 123 | You may add Your own copyright statement to Your modifications and 124 | may provide additional or different license terms and conditions 125 | for use, reproduction, or distribution of Your modifications, or 126 | for any such Derivative Works as a whole, provided Your use, 127 | reproduction, and distribution of the Work otherwise complies with 128 | the conditions stated in this License. 129 | 130 | 5. Submission of Contributions. Unless You explicitly state otherwise, 131 | any Contribution intentionally submitted for inclusion in the Work 132 | by You to the Licensor shall be under the terms and conditions of 133 | this License, without any additional terms or conditions. 134 | Notwithstanding the above, nothing herein shall supersede or modify 135 | the terms of any separate license agreement you may have executed 136 | with Licensor regarding such Contributions. 137 | 138 | 6. Trademarks. This License does not grant permission to use the trade 139 | names, trademarks, service marks, or product names of the Licensor, 140 | except as required for reasonable and customary use in describing the 141 | origin of the Work and reproducing the content of the NOTICE file. 142 | 143 | 7. Disclaimer of Warranty. Unless required by applicable law or 144 | agreed to in writing, Licensor provides the Work (and each 145 | Contributor provides its Contributions) on an "AS IS" BASIS, 146 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or 147 | implied, including, without limitation, any warranties or conditions 148 | of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A 149 | PARTICULAR PURPOSE. You are solely responsible for determining the 150 | appropriateness of using or redistributing the Work and assume any 151 | risks associated with Your exercise of permissions under this License. 152 | 153 | 8. Limitation of Liability. In no event and under no legal theory, 154 | whether in tort (including negligence), contract, or otherwise, 155 | unless required by applicable law (such as deliberate and grossly 156 | negligent acts) or agreed to in writing, shall any Contributor be 157 | liable to You for damages, including any direct, indirect, special, 158 | incidental, or consequential damages of any character arising as a 159 | result of this License or out of the use or inability to use the 160 | Work (including but not limited to damages for loss of goodwill, 161 | work stoppage, computer failure or malfunction, or any and all 162 | other commercial damages or losses), even if such Contributor 163 | has been advised of the possibility of such damages. 164 | 165 | 9. Accepting Warranty or Additional Liability. While redistributing 166 | the Work or Derivative Works thereof, You may choose to offer, 167 | and charge a fee for, acceptance of support, warranty, indemnity, 168 | or other liability obligations and/or rights consistent with this 169 | License. However, in accepting such obligations, You may act only 170 | on Your own behalf and on Your sole responsibility, not on behalf 171 | of any other Contributor, and only if You agree to indemnify, 172 | defend, and hold each Contributor harmless for any liability 173 | incurred by, or claims asserted against, such Contributor by reason 174 | of your accepting any such warranty or additional liability. 175 | 176 | END OF TERMS AND CONDITIONS 177 | 178 | APPENDIX: How to apply the Apache License to your work. 179 | 180 | To apply the Apache License to your work, attach the following 181 | boilerplate notice, with the fields enclosed by brackets "[]" 182 | replaced with your own identifying information. (Don't include 183 | the brackets!) The text should be enclosed in the appropriate 184 | comment syntax for the file format. We also recommend that a 185 | file or class name and description of purpose be included on the 186 | same "printed page" as the copyright notice for easier 187 | identification within third-party archives. 188 | 189 | Copyright [yyyy] [name of copyright owner] 190 | 191 | Licensed under the Apache License, Version 2.0 (the "License"); 192 | you may not use this file except in compliance with the License. 193 | You may obtain a copy of the License at 194 | 195 | http://www.apache.org/licenses/LICENSE-2.0 196 | 197 | Unless required by applicable law or agreed to in writing, software 198 | distributed under the License is distributed on an "AS IS" BASIS, 199 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 200 | See the License for the specific language governing permissions and 201 | limitations under the License. 202 | -------------------------------------------------------------------------------- /MANIFEST.in: -------------------------------------------------------------------------------- 1 | include *.txt 2 | include LICENSE 3 | recursive-include src *.html 4 | recursive-include src *.xml 5 | 6 | # added by check-manifest 7 | recursive-include atest *.robot 8 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # robotframework-debugger 2 | Debugger that can stop execution and shows a Gui to try out Robot Framework commands 3 | 4 | ## Installation 5 | 6 | ``pip install robotframework-debugger`` 7 | 8 | ## Usage 9 | 10 | use it as listener 11 | 12 | ``robot --listener Debugger myrobotsuite.robot`` 13 | 14 | ## How it works: 15 | 16 | Debugger pauses the execution on a failing keyword or on keywords named `Debug` or `Break`. 17 | It opens a TKinter based GUI and let you see the error, try out other keywords in exact that situation. 18 | It also gives access to Robot Frameworks variables and logs a history of passed keyword calles. 19 | 20 | ### Have Fun -------------------------------------------------------------------------------- /atest/test.robot: -------------------------------------------------------------------------------- 1 | *** Settings *** 2 | 3 | 4 | *** Test Cases *** 5 | test 123 6 | Log To Console Hello 7 | Run Keyword And Ignore Error A keyword That Fails 8 | Log this is Skipped 9 | Log This Too 10 | [Teardown] Another Keyword that fails 11 | 12 | 13 | *** Keywords *** 14 | A Keyword That Fails 15 | Fail expected 16 | 17 | Another Keyword that fails 18 | Log To Console This is Executed 19 | Fail again 20 | Log To Console This is also Executed 21 | -------------------------------------------------------------------------------- /setup.py: -------------------------------------------------------------------------------- 1 | import re 2 | 3 | from setuptools import setup 4 | from setuptools import find_packages 5 | from os.path import abspath, dirname, join 6 | 7 | CURDIR = dirname(abspath(__file__)) 8 | with open("README.md", "r", encoding='utf-8') as fh: 9 | long_description = fh.read() 10 | 11 | with open(join(CURDIR, 'src', 'Debugger', '__init__.py'), encoding='utf-8') as f: 12 | VERSION = re.search("\n__version__ = '(.*)'", f.read()).group(1) 13 | 14 | setup( 15 | name="robotframework-debugger", 16 | version=VERSION, 17 | author="René Rohner(Snooz82)", 18 | author_email="snooz@posteo.de", 19 | description="A Robot Framework Listener to try keywords and pause execution on failure", 20 | long_description=long_description, 21 | long_description_content_type='text/markdown', 22 | url="https://github.com/Snooz82/robotframework-debugger", 23 | package_dir={'': 'src'}, 24 | packages=find_packages('src'), 25 | classifiers=[ 26 | "Programming Language :: Python :: 3", 27 | "Programming Language :: Python :: 3.6", 28 | "Programming Language :: Python :: 3.7", 29 | "License :: OSI Approved :: Apache Software License", 30 | "Operating System :: OS Independent", 31 | "Topic :: Software Development :: Testing", 32 | "Topic :: Software Development :: Testing :: Acceptance", 33 | "Framework :: Robot Framework", 34 | ], 35 | install_requires=['robotframework >= 3.2.1'], 36 | python_requires='>=3.6', 37 | ) 38 | -------------------------------------------------------------------------------- /src/Debugger/DebuggerGui.py: -------------------------------------------------------------------------------- 1 | from tkinter import ttk 2 | from copy import deepcopy 3 | from tkinter import * 4 | 5 | from robot.errors import DataError 6 | from robot.libraries.BuiltIn import BuiltIn 7 | 8 | 9 | class DebuggerGui: 10 | def __init__( 11 | self, top=None, libraries=None, failed_kw=None, keyword_messages=None, history=None 12 | ): 13 | """This class configures and populates the toplevel window. 14 | top is the toplevel containing window.""" 15 | 16 | _all_libraries_alias = '-- ALL IMPORTS --' 17 | 18 | self.top = top 19 | self.libraries = self.get_libraries_from_list(_all_libraries_alias, libraries) 20 | if isinstance(self.libraries, dict): 21 | self.library_names = list(self.libraries.keys()) 22 | else: 23 | self.library_names = '--' 24 | if isinstance(failed_kw, dict): 25 | self.failed_Command = f'{failed_kw["kwname"]} ' f'{" ".join(failed_kw["args"])}' 26 | else: 27 | self.failed_Command = '' 28 | self.keyword_messages = keyword_messages 29 | self.history = history 30 | self.keyword_names = list() 31 | self.command_value = StringVar() 32 | self.label_value = StringVar() 33 | self.combobox_library_value = StringVar() 34 | self.variable_name_value = StringVar() 35 | self.variable_value_value = StringVar() 36 | self.option_filter_keyword = BooleanVar() 37 | self.option_add_default_param = BooleanVar() 38 | self.option_insert_history_below = BooleanVar() 39 | self.show_builtin_vars = BooleanVar() 40 | 41 | self.menu_bar = None 42 | self.config_menu_bar() 43 | 44 | self.file_menu = None 45 | self.option_menu = None 46 | 47 | self.MainFrame = None 48 | self.config_main_frame() 49 | 50 | self.EntryCommand = None 51 | self.ButtonExecute = None 52 | self.LabelExecutionResult = None 53 | self.config_command_entry_field() 54 | self.config_execute_button() 55 | self.config_execution_result_label() 56 | 57 | self.TNotebook = None 58 | self.TabKeywords = None 59 | 60 | self.ComboboxLibrary = None 61 | self.ListboxKeywordsFrame = None 62 | self.ListboxKeywords = None 63 | self.ListboxKeywordsScrollBar = None 64 | self.Doc_Frame = None 65 | self.TabHistory = None 66 | self.ListboxHistory = None 67 | self.TabVariables = None 68 | self.FrameVariablesOptions = None 69 | self.CheckbtnBuiltInVariables = None 70 | self.ListboxVariables = None 71 | self.ListboxVariablesScrollBar = None 72 | self.VariableFrame = None 73 | self.EntryVariableName = None 74 | self.EntryVariableValue = None 75 | self.ButtonSetVariable = None 76 | self.config_notebook() 77 | 78 | self.top.title("Robot Framework Debugger") 79 | self.top.wm_geometry("1000x600") 80 | self.top.minsize(400, 150) 81 | self.top.columnconfigure(0, weight=1) 82 | self.top.rowconfigure(0, weight=1) 83 | 84 | @property 85 | def built_in_variables(self): 86 | _built_in_variables = [ 87 | '${CURDIR}', 88 | '${TEMPDIR}', 89 | '${EXECDIR}', 90 | '${/}', 91 | '${:}', 92 | '${TEST_NAME}', 93 | '@{TEST_TAGS}', 94 | '${TEST_DOCUMENTATION}', 95 | '${TEST_STATUS}', 96 | '${TEST_MESSAGE}', 97 | '${PREV_TEST_NAME}', 98 | '${PREV_TEST_STATUS}', 99 | '${PREV_TEST_MESSAGE}', 100 | '${SUITE_NAME}', 101 | '${SUITE_SOURCE}', 102 | '${SUITE_DOCUMENTATION}', 103 | '&{SUITE_METADATA}', 104 | '${SUITE_STATUS}', 105 | '${SUITE_MESSAGE}', 106 | '${KEYWORD_STATUS}', 107 | '${KEYWORD_MESSAGE}', 108 | '${LOG_LEVEL}', 109 | '${OUTPUT_FILE}', 110 | '${LOG_FILE}', 111 | '${REPORT_FILE}', 112 | '${DEBUG_FILE}', 113 | '${OUTPUT_DIR}', 114 | '${\\n}', 115 | '${SPACE}', 116 | '${False}', 117 | '${True}', 118 | '${null}', 119 | '${None}', 120 | ] 121 | return _built_in_variables 122 | 123 | @staticmethod 124 | def get_libraries_from_list(all_libraries_alias, libraries): 125 | if isinstance(libraries, dict): 126 | all_libs = dict() 127 | all_libs[all_libraries_alias] = dict() 128 | all_libs[all_libraries_alias]['name'] = all_libraries_alias 129 | all_libs[all_libraries_alias]['version'] = '' 130 | all_libs[all_libraries_alias]['keywords'] = list() 131 | for library in libraries: 132 | for keyword in libraries[library]['keywords']: 133 | unique_keyword = deepcopy(keyword) 134 | unique_keyword['name'] = f'{libraries[library]["name"]}.{keyword["name"]}' 135 | all_libs[all_libraries_alias]['keywords'].append(unique_keyword) 136 | return {**all_libs, **libraries} 137 | else: 138 | return list() 139 | 140 | def config_menu_bar(self): 141 | self.menu_bar = Menu(self.top) 142 | self.top.configure(menu=self.menu_bar) 143 | self.file_menu = Menu(self.top, tearoff=0) 144 | self.menu_bar.add_cascade(menu=self.file_menu, compound="left", label="File") 145 | self.file_menu.add_command(accelerator="CRTL + S", label="Save History") 146 | self.option_menu = Menu(self.top, tearoff=0) 147 | self.menu_bar.add_cascade(menu=self.option_menu, compound="left", label="Options") 148 | self.option_menu.add_checkbutton( 149 | variable=self.option_filter_keyword, 150 | onvalue=True, 151 | offvalue=False, 152 | label="Filter Keyword List", 153 | ) 154 | self.option_menu.add_checkbutton( 155 | variable=self.option_add_default_param, 156 | onvalue=True, 157 | offvalue=False, 158 | label="Add Default Parameter", 159 | ) 160 | self.option_menu.add_checkbutton( 161 | variable=self.option_insert_history_below, 162 | onvalue=True, 163 | offvalue=False, 164 | label="Insert History Below", 165 | ) 166 | self.option_filter_keyword.set(True) 167 | self.option_insert_history_below.set(True) 168 | 169 | def config_main_frame(self): 170 | self.MainFrame = Frame(self.top) 171 | self.MainFrame.grid(column=0, row=0, sticky=N + S + E + W) 172 | self.MainFrame.columnconfigure(1, weight=1) 173 | self.MainFrame.rowconfigure(8, weight=1) 174 | 175 | def config_command_entry_field(self): 176 | self.EntryCommand = ttk.Entry(self.MainFrame) 177 | self.EntryCommand.grid(row=0, column=0, columnspan=7, sticky=N + W + E) 178 | validate_command = ( 179 | self.EntryCommand.register(self.validate_command_entry), 180 | '%d', 181 | '%i', 182 | '%P', 183 | '%s', 184 | '%S', 185 | '%v', 186 | '%V', 187 | '%W', 188 | ) 189 | self.EntryCommand.configure( 190 | textvariable=self.command_value, 191 | font="TkFixedFont", 192 | validate="key", 193 | validatecommand=validate_command, 194 | ) 195 | self.EntryCommand.bind('', self.execute_command) 196 | self.EntryCommand.bind('', self.set_focus_to_keyword_list) 197 | self.EntryCommand.bind('', self.clear_keyword_filter) 198 | self.EntryCommand.bind('', self._select_next_arg) 199 | self.command_value.set(self.failed_Command) 200 | 201 | def config_execute_button(self): 202 | self.ButtonExecute = ttk.Button(self.MainFrame) 203 | self.ButtonExecute.grid(row=0, column=7, columnspan=1, sticky=W) 204 | self.ButtonExecute.configure(text='''Execute''', command=self.execute_command) 205 | 206 | def config_execution_result_label(self): 207 | self.LabelExecutionResult = ttk.Label(self.MainFrame) 208 | self.LabelExecutionResult.grid(row=1, column=1, sticky=W) 209 | self.LabelExecutionResult.configure( 210 | text='''FAIL''', font="TkFixedFont", textvariable=self.label_value 211 | ) 212 | self.label_value.set(self.keyword_messages) 213 | 214 | def config_notebook(self): 215 | self.TNotebook = ttk.Notebook(self.MainFrame) 216 | self.TNotebook.grid(row=2, column=0, columnspan=8, rowspan=10, sticky=N + W + S + E) 217 | self.TNotebook.configure(takefocus="") 218 | self.config_keywords_tab() 219 | self.TNotebook.add(self.TabKeywords, padding=3) 220 | self.TNotebook.tab(0, text="Keywords", compound="left", underline="-1") 221 | self.config_history_tab() 222 | self.TNotebook.add(self.TabHistory, padding=3) 223 | self.TNotebook.tab(1, text="History", compound="left", underline="-1") 224 | self.config_variables_tab() 225 | self.TNotebook.add(self.TabVariables, padding=3) 226 | self.TNotebook.tab(2, text="Variables", compound="left", underline="-1") 227 | 228 | def config_keywords_tab(self): 229 | self.TabKeywords = ttk.Frame(self.TNotebook) 230 | self.TabKeywords.columnconfigure(0, weight=1, minsize=200) 231 | self.TabKeywords.rowconfigure(1, weight=1) 232 | self.config_library_combobox() 233 | self.config_keyword_listbox() 234 | self.config_documentation_frame() 235 | 236 | def config_library_combobox(self): 237 | self.ComboboxLibrary = ttk.Combobox(self.TabKeywords) 238 | self.ComboboxLibrary.grid(row=0, column=0, sticky=N + E + S + W) 239 | self.ComboboxLibrary.configure( 240 | textvariable=self.combobox_library_value, takefocus="", state="readonly" 241 | ) 242 | self.ComboboxLibrary.bind("<>", self.select_library_command) 243 | self.ComboboxLibrary['values'] = self.library_names 244 | self.combobox_library_value.set(self.library_names[0]) 245 | 246 | def config_keyword_listbox(self): 247 | self.ListboxKeywordsFrame = Frame(self.TabKeywords) 248 | self.ListboxKeywordsFrame.grid(row=1, column=0, sticky=N + E + S + W) 249 | self.ListboxKeywords = Listbox(self.ListboxKeywordsFrame) 250 | self.ListboxKeywordsScrollBar = Scrollbar(self.ListboxKeywordsFrame) 251 | self.ListboxKeywords.pack(side=LEFT, fill='both', expand=1, anchor='sw') 252 | self.ListboxKeywordsScrollBar.pack(side=LEFT, fill='y', anchor='se') 253 | self.ListboxKeywords.config( 254 | font="TkFixedFont", yscrollcommand=self.ListboxKeywordsScrollBar.set 255 | ) 256 | self.ListboxKeywordsScrollBar.config(command=self.ListboxKeywords.yview) 257 | self.ListboxKeywords.bind('', self.select_keyword_command) 258 | self.ListboxKeywords.bind('<>', self.click_keyword_command) 259 | self.ListboxKeywords.bind('', self.click_keyword_command) 260 | self.ListboxKeywords.bind('', self.select_keyword_command) 261 | self.ListboxKeywords.bind('', self.select_keyword_command) 262 | self.ListboxKeywords.bind('', self.clear_keyword_filter) 263 | self.select_library_command() 264 | 265 | def config_documentation_frame(self): 266 | self.Doc_Frame = Text(self.TabKeywords) 267 | self.Doc_Frame.grid_propagate(0) 268 | self.Doc_Frame.grid(row=0, column=1, rowspan=2, sticky=N + E + S) 269 | self.Doc_Frame.insert(END, "--") 270 | 271 | def config_history_tab(self): 272 | self.TabHistory = ttk.Frame(self.TNotebook) 273 | self.TabHistory.columnconfigure(0, weight=1) 274 | self.TabHistory.rowconfigure(0, weight=1) 275 | self.config_history_listbox() 276 | 277 | def config_history_listbox(self): 278 | self.ListboxHistory = Listbox(self.TabHistory) 279 | self.ListboxHistory.grid(column=0, row=0, sticky=N + S + E + W) 280 | self.ListboxHistory.configure(font="TkFixedFont", selectmode=EXTENDED) 281 | self.ListboxHistory.bind('', self.select_history_command) 282 | if self.history: 283 | for commands in self.history: 284 | self._add_to_history_listbox(commands) 285 | 286 | def config_variables_tab(self): 287 | self.TabVariables = ttk.Frame(self.TNotebook) 288 | self.TabVariables.columnconfigure(0, weight=1) 289 | self.TabVariables.columnconfigure(1, weight=4) 290 | self.TabVariables.rowconfigure(2, weight=1) 291 | self.config_set_variables_button() 292 | self.config_variables_name_field() 293 | self.config_variables_value_field() 294 | self.config_variables_lsitbox_options() 295 | self.config_variables_listbox() 296 | 297 | def config_variables_name_field(self): 298 | self.EntryVariableName = ttk.Entry(self.TabVariables) 299 | self.EntryVariableName.grid(row=0, column=0, sticky=W + E) 300 | self.EntryVariableName.configure( 301 | textvariable=self.variable_name_value, font="TkFixedFont", validate="key" 302 | ) 303 | self.EntryVariableName.bind('', self.set_variable) 304 | 305 | def config_variables_value_field(self): 306 | self.EntryVariableValue = ttk.Entry(self.TabVariables) 307 | self.EntryVariableValue.grid(row=0, column=1, sticky=W + E) 308 | self.EntryVariableValue.configure( 309 | textvariable=self.variable_value_value, font="TkFixedFont", validate="key" 310 | ) 311 | self.EntryVariableValue.bind('', self.set_variable) 312 | self.EntryVariableValue.bind('', self._select_next_arg) 313 | 314 | def config_set_variables_button(self): 315 | self.ButtonSetVariable = ttk.Button(self.TabVariables) 316 | self.ButtonSetVariable.grid(row=0, column=2, sticky=E) 317 | self.ButtonSetVariable.configure(text='''Set Variable''', command=self.set_variable) 318 | 319 | def config_variables_lsitbox_options(self): 320 | self.FrameVariablesOptions = ttk.Frame(self.TabVariables) 321 | self.FrameVariablesOptions.grid(row=1, column=0, columnspan=6, sticky=W + E + S + N) 322 | self.CheckbtnBuiltInVariables = ttk.Checkbutton( 323 | self.FrameVariablesOptions, 324 | text="Show Built-In Variables", 325 | variable=self.show_builtin_vars, 326 | command=self.update_variables_list, 327 | ) 328 | self.CheckbtnBuiltInVariables.grid(row=0, column=0) 329 | 330 | def config_variables_listbox(self): 331 | self.VariableFrame = ttk.Frame(self.TabVariables) 332 | self.VariableFrame.grid(row=2, column=0, columnspan=6, rowspan=6, sticky=W + E + S + N) 333 | self.ListboxVariables = Listbox(self.VariableFrame) 334 | self.ListboxVariablesScrollBar = Scrollbar(self.VariableFrame) 335 | self.ListboxVariables.pack(side=LEFT, fill='both', expand=1, anchor='sw') 336 | self.ListboxVariablesScrollBar.pack(side=LEFT, fill='y', anchor='se') 337 | self.ListboxVariables.config( 338 | font="TkFixedFont", yscrollcommand=self.ListboxVariablesScrollBar.set 339 | ) 340 | self.ListboxVariablesScrollBar.config(command=self.ListboxVariables.yview) 341 | self.ListboxVariables.bind('', self.update_variables_list) 342 | self.ListboxVariables.bind('<>', self.select_variable) 343 | self.update_variables_list() 344 | 345 | def execute_command(self, event=None): 346 | commands = self._get_command() 347 | try: 348 | self.LabelExecutionResult.configure(text=f'Sent: {" ".join(commands)}') 349 | return_value = BuiltIn().run_keyword(*commands) 350 | if isinstance(return_value, str): 351 | return_value = repr(return_value)[1:-1] 352 | self.label_value.set(f'${{RETURN_VALUE}} => {return_value}') 353 | BuiltIn().set_test_variable('${RETURN_VALUE}', return_value) 354 | self.update_variables_list() 355 | self._add_to_history_listbox(commands) 356 | except Exception as e: 357 | self.label_value.set(f'FAIL: {str(e)}') 358 | 359 | def validate_command_entry(self, d, i, P, s, S, v, V, W): 360 | if hasattr(self, 'ListboxKeywords') and self.option_filter_keyword.get(): 361 | try: 362 | keyword_entry = str(P).split(' ', 1) 363 | keyword_entry = keyword_entry[0].strip() 364 | self.ListboxKeywords.delete(0, END) 365 | for keyword in self.keyword_names: 366 | if keyword_entry.lower() in keyword.lower(): 367 | self.ListboxKeywords.insert(END, keyword) 368 | except Exception as e: 369 | print(e) 370 | else: 371 | self.ListboxKeywords.delete(0, END) 372 | for keyword in self.keyword_names: 373 | self.ListboxKeywords.insert(END, keyword) 374 | return True 375 | 376 | def set_focus_to_keyword_list(self, event=None): 377 | self.ListboxKeywords.focus_set() 378 | self.ListboxKeywords.selection_set(0) 379 | 380 | def clear_keyword_filter(self, event=None): 381 | self.EntryCommand.delete(0, END) 382 | self.EntryCommand.focus_set() 383 | self.EntryCommand.icursor(0) 384 | 385 | def _get_command(self): 386 | command = self.EntryCommand.get() 387 | commands = command.split(' ') 388 | return [c.strip() for c in commands if c != ''] 389 | 390 | def select_library_command(self, event=None): 391 | selected_lib = self.combobox_library_value.get() 392 | self.ListboxKeywords.delete(0, END) 393 | self.keyword_names = list() 394 | if self.libraries: 395 | for keyword in dict(self.libraries[selected_lib])['keywords']: 396 | self.keyword_names.append(keyword['name']) 397 | self.ListboxKeywords.insert(END, keyword['name']) 398 | 399 | def select_keyword_command(self, event=None): 400 | keyword_dict = self.get_keyword_from_library( 401 | self.combobox_library_value.get(), self.ListboxKeywords.get(ACTIVE) 402 | ) 403 | if keyword_dict: 404 | args = keyword_dict["args"] 405 | keyword = keyword_dict["name"] 406 | if ( 407 | self._is_modifier_used(event.state, 'Control') 408 | == self.option_add_default_param.get() 409 | ): 410 | args = [x for x in args if '=' not in x] 411 | self.command_value.set(f'{keyword} {" ".join(args)}') 412 | self.EntryCommand.focus_set() 413 | if len(args) > 0: 414 | arg1_start = len(keyword) + 4 415 | arg1_end = arg1_start + len(args[0]) 416 | self.EntryCommand.selection_range(arg1_start, arg1_end) 417 | self.EntryCommand.icursor(arg1_start) 418 | else: 419 | self.EntryCommand.icursor(len(keyword)) 420 | self.EntryCommand.update() 421 | 422 | def click_keyword_command(self, event=None): 423 | if isinstance(event.x_root, str) or event.x_root < 0: 424 | try: 425 | name = self.ListboxKeywords.get(self.ListboxKeywords.curselection()[0]) 426 | keyword = self.get_keyword_from_library(self.combobox_library_value.get(), name) 427 | br = '\n' 428 | self.Doc_Frame.replace( 429 | 1.0, 430 | END, 431 | f"""Keyword: 432 | {keyword['name']} 433 | 434 | Arguments: 435 | {br.join(keyword['args'])} 436 | 437 | Documentation: 438 | {keyword['doc']}""", 439 | ) 440 | except Exception as e: 441 | print(e) 442 | 443 | def get_keyword_from_library(self, library, name): 444 | selected_lib = self.libraries[library] 445 | for keyword in dict(selected_lib)['keywords']: 446 | if keyword['name'] == name: 447 | return keyword 448 | 449 | def select_history_command(self, event=None): 450 | command = self.ListboxHistory.get(ACTIVE) 451 | self.command_value.set(command.strip()) 452 | self.EntryCommand.update() 453 | 454 | def update_variables_list(self, event=None): 455 | self.ListboxVariables.delete(0, END) 456 | variables = BuiltIn().get_variables() 457 | longest_var_name = '' 458 | for variable in variables: 459 | if not self.show_builtin_vars.get() and variable in self.built_in_variables: 460 | continue 461 | if len(variable) > len(longest_var_name): 462 | longest_var_name = variable 463 | for variable in variables: 464 | if not self.show_builtin_vars.get() and variable in self.built_in_variables: 465 | continue 466 | i = len(longest_var_name) - len(variable) 467 | self.ListboxVariables.insert(END, f'{variable}= {i * " "}{str(variables[variable])}') 468 | 469 | def select_variable(self, event=None): 470 | if event.x_root < 0: 471 | widget = event.widget 472 | selected_var = widget.get(widget.curselection()).split('=', 1) 473 | self.variable_name_value.set(selected_var[0]) 474 | if len(selected_var) == 2: 475 | printable = repr(selected_var[1].strip()) 476 | self.variable_value_value.set(printable[1:-1]) 477 | 478 | def set_variable(self, event=None): 479 | name = self.variable_name_value.get() 480 | if not (name[0] in '@$&' and name[1] == '{' and name[-1] == '}'): 481 | name = f'${{{name}}}' 482 | self.variable_name_value.set(name) 483 | value = self.variable_value_value.get().strip() 484 | value = self._try_eval_var(value) 485 | try: 486 | name = self._try_dict_var(name, value) 487 | self._try_list_var(name, value) 488 | self._try_str_var(name, value) 489 | var = BuiltIn().get_variable_value(name) 490 | self.label_value.set(f'{name} => {var}') 491 | except DataError as e: 492 | self.label_value.set(e) 493 | self.update_variables_list() 494 | 495 | @staticmethod 496 | def _try_dict_var(name, value): 497 | if isinstance(value, dict) and name[0] in '$&': 498 | name = f'${name[1:]}' 499 | BuiltIn().set_test_variable(name, value) 500 | return name 501 | 502 | @staticmethod 503 | def _try_list_var(name, value): 504 | if isinstance(value, list): 505 | if name[0] in '@&': 506 | BuiltIn().set_test_variable(name, *value) 507 | elif name[0] == '$': 508 | BuiltIn().set_test_variable(name, value) 509 | 510 | def _try_str_var(self, name, value): 511 | if isinstance(value, str): 512 | if ' ' in value or name[0] == '@': 513 | value = [v.strip() for v in value.split(' ') if v.strip() != ''] 514 | self._try_list_var(name, value) 515 | elif name[0] in '$&': 516 | BuiltIn().set_test_variable(name, value) 517 | 518 | @staticmethod 519 | def _try_eval_var(value): 520 | try: 521 | value = eval(value) 522 | except Exception: 523 | pass 524 | return value 525 | 526 | @staticmethod 527 | def _is_modifier_used(state, modifier): 528 | if isinstance(state, int): 529 | mods = ( 530 | 'Shift', 531 | 'Lock', 532 | 'Control', 533 | 'Mod1', 534 | 'Mod2', 535 | 'Mod3', 536 | 'Mod4', 537 | 'Mod5', 538 | 'Button1', 539 | 'Button2', 540 | 'Button3', 541 | 'Button4', 542 | 'Button5', 543 | ) 544 | s = [] 545 | for i, n in enumerate(mods): 546 | if state & (1 << i): 547 | s.append(n) 548 | state = state & ~((1 << len(mods)) - 1) 549 | if state or not s: 550 | s.append(hex(state)) 551 | if modifier in s: 552 | return True 553 | return False 554 | 555 | def _select_next_arg(self, event=None): 556 | if self._is_modifier_used(event.state, 'Control'): 557 | return 558 | entry = event.widget 559 | if self._is_modifier_used(event.state, 'Shift'): 560 | return self._move_backward(entry) 561 | else: 562 | return self._move_forward(entry) 563 | 564 | def _move_backward(self, entry): 565 | text = entry.get() 566 | index = entry.index(INSERT) 567 | if not ( 568 | text[index : index + 2] == ' ' 569 | or text[index - 1 : index + 1] == ' ' 570 | or text[index - 2 : index] == ' ' 571 | ): 572 | index = text[:index].rfind(' ') + len(' ') 573 | arg_end = index - (len(text[:index]) - len(text[:index].rstrip())) 574 | space_index = text[:arg_end].rfind(' ') 575 | if space_index == -1: 576 | arg_start = 0 577 | else: 578 | next_space = len(text[:arg_end]) - (space_index + len(' ')) 579 | arg_start = arg_end - next_space 580 | if ( 581 | entry.selection_present() 582 | and entry.index(SEL_FIRST) == arg_start 583 | and entry.index(SEL_LAST) == arg_end 584 | ): 585 | entry.icursor(arg_start - 1) 586 | entry.selection_clear() 587 | self._move_backward(entry) 588 | else: 589 | entry.selection_range(arg_start, arg_end) 590 | entry.icursor(arg_start) 591 | return "break" 592 | 593 | def _move_forward(self, entry): 594 | text = entry.get() 595 | index = entry.index(INSERT) 596 | if not ( 597 | text[index : index + 2] == ' ' 598 | or text[index - 1 : index + 1] == ' ' 599 | or text[index - 2 : index] == ' ' 600 | ): 601 | if text[index:].find(' ') == -1: 602 | entry.delete(0, END) 603 | entry.insert(0, f'{text} ') 604 | entry.icursor(len(entry.get())) 605 | return "break" 606 | index = index + text[index:].find(' ') 607 | arg_start = index + (len(text[index:]) - len(text[index:].lstrip())) 608 | next_space = text[arg_start:].find(' ') 609 | if next_space == -1: 610 | arg_end = len(text) 611 | else: 612 | arg_end = arg_start + next_space 613 | if ( 614 | entry.selection_present() 615 | and entry.index(SEL_FIRST) == arg_start 616 | and entry.index(SEL_LAST) == arg_end 617 | ): 618 | entry.icursor(arg_start + 1) 619 | entry.selection_clear() 620 | self._move_forward(entry) 621 | else: 622 | entry.selection_range(arg_start, arg_end) 623 | entry.icursor(arg_start) 624 | return "break" 625 | 626 | def _add_to_history_listbox(self, commands): 627 | if self.option_insert_history_below.get(): 628 | self.ListboxHistory.insert(END, ' '.join(commands)) 629 | else: 630 | self.ListboxHistory.insert(0, ' '.join(commands)) 631 | 632 | 633 | if __name__ == '__main__': 634 | root = Tk() 635 | Toplevel(root) 636 | root.mainloop() 637 | -------------------------------------------------------------------------------- /src/Debugger/__init__.py: -------------------------------------------------------------------------------- 1 | # Copyright 2019- René Rohner 2 | # 3 | # Licensed under the Apache License, Version 2.0 (the "License"); 4 | # you may not use this file except in compliance with the License. 5 | # You may obtain a copy of the License at 6 | # 7 | # http://www.apache.org/licenses/LICENSE-2.0 8 | # 9 | # Unless required by applicable law or agreed to in writing, software 10 | # distributed under the License is distributed on an "AS IS" BASIS, 11 | # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 | # See the License for the specific language governing permissions and 13 | # limitations under the License. 14 | 15 | import tempfile 16 | import robot 17 | 18 | from tkinter import * 19 | 20 | from distutils.version import StrictVersion 21 | from Debugger.DebuggerGui import DebuggerGui 22 | from robot.libdocpkg import LibraryDocumentation 23 | 24 | __version__ = '0.2.1' 25 | 26 | _SUITE_SETUP = 1 27 | _TEST_CASE = 3 28 | _SUITE_TEARDOWN = 5 29 | is_RF_4 = StrictVersion(robot.__version__) >= StrictVersion('4.0.0') 30 | 31 | muting_keywords = [ 32 | "Run Keyword And Ignore Error", 33 | "Run Keyword And Expect Error", 34 | "Run Keyword And Return Status", 35 | "Run Keyword And Warn On Failure", 36 | "Wait Until Keyword Succeeds", 37 | ] 38 | 39 | 40 | class Debugger: 41 | 42 | ROBOT_LISTENER_API_VERSION = 2 43 | 44 | def __init__(self, break_on_fail=True): 45 | 46 | self.ROBOT_LIBRARY_LISTENER = self 47 | 48 | if isinstance(break_on_fail, str): 49 | self.break_on_fail = break_on_fail.lower() != 'false' 50 | else: 51 | self.break_on_fail = bool(break_on_fail) 52 | 53 | self.new_error = True 54 | self.mutings = [] 55 | self.tempdir = tempfile.mkdtemp() 56 | self.libraries = dict() 57 | self.log_messages = [] 58 | self.messages = [] 59 | self.indent = 0 60 | self.test_phase = None 61 | self.test_history = [] 62 | self.setup_history = [] 63 | self.teardown_history = [] 64 | 65 | def debug(self, keyword=None): 66 | main = Tk() 67 | keyword_history = [['### Suite Setup ###']] 68 | keyword_history += self.setup_history 69 | if self.test_history: 70 | keyword_history.append(['### Test Case ###']) 71 | keyword_history += self.test_history 72 | if self.teardown_history: 73 | keyword_history.append(['### Suite Teardown ###']) 74 | keyword_history += self.teardown_history 75 | DebuggerGui(main, self.libraries, keyword, '\n'.join(self.log_messages), keyword_history) 76 | main.mainloop() 77 | 78 | def start_suite(self, name, attrs): 79 | self.setup_history = [] 80 | self.indent = 0 81 | self.test_phase = _SUITE_SETUP 82 | 83 | def start_test(self, name, attrs): 84 | self.test_history = [] 85 | self.indent = 0 86 | self.test_phase = _TEST_CASE 87 | 88 | def start_keyword(self, name, attrs): 89 | self.log_messages = [] 90 | self.indent = self.indent + 2 91 | command = [' ' * self.indent, attrs["kwname"], *attrs['args']] 92 | if self.test_phase == _SUITE_SETUP: 93 | self.setup_history.append(command) 94 | elif self.test_phase == _TEST_CASE: 95 | self.test_history.append(command) 96 | else: 97 | self.teardown_history.append(command) 98 | if attrs['kwname'] in muting_keywords: 99 | self.mutings.append(attrs['kwname']) 100 | if attrs['kwname'].upper() in ['DEBUG', 'BREAK']: 101 | if len(attrs['args']) == 1: 102 | attrs['kwname'] = attrs['args'][0] 103 | if len(attrs['args']) > 1: 104 | attrs['args'] = attrs['args'][1:] 105 | self.debug(attrs) 106 | self.new_error = True 107 | 108 | def end_keyword(self, name, attrs): 109 | if self.mutings and attrs['kwname'] == self.mutings[-1]: 110 | self.mutings.pop() 111 | self.indent = self.indent - 2 112 | if attrs['status'] == 'FAIL' and self.break_on_fail and self.new_error and not self.mutings: 113 | self.debug(attrs) 114 | self.new_error = False 115 | 116 | def end_test(self, name, attrs): 117 | self.test_phase = _SUITE_TEARDOWN 118 | self.indent = 0 119 | 120 | def end_suite(self, name, attrs): 121 | self.teardown_history = [] 122 | self.indent = 0 123 | 124 | def log_message(self, message): 125 | self.log_messages.append(f'{message["level"]}: {message["message"]}') 126 | 127 | def message(self, message): 128 | self.messages.append(message) 129 | 130 | def library_import(self, name, attrs): 131 | self._analyse_import(name, attrs, True) 132 | 133 | def resource_import(self, name, attrs): 134 | self._analyse_import(name, attrs, False) 135 | 136 | def _analyse_import(self, name, attrs, is_library: bool): 137 | if is_library: 138 | libdoc = LibraryDocumentation(name) 139 | else: 140 | libdoc = LibraryDocumentation(attrs['source']) 141 | library = {'name': libdoc.name, 'version': libdoc.version, 'keywords': []} 142 | for kw in libdoc.keywords: 143 | if is_RF_4: 144 | keyword = {'name': kw.name, 'args': [str(arg) for arg in kw.args], 'doc': kw.doc} 145 | else: 146 | keyword = {'name': kw.name, 'args': kw.args, 'doc': kw.doc} 147 | library['keywords'].append(keyword) 148 | self.libraries[library['name']] = library 149 | -------------------------------------------------------------------------------- /src/Debugger/logs/output.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | Makes a variable available everywhere within the scope of the current suite. 7 | 8 | ${auth} 9 | Test123 10 | 11 | ${auth} = Test123 12 | 13 | 14 | 15 | Logs the given message to the console. 16 | 17 | ${auth} 18 | 19 | 20 | 21 | 22 | 23 | 24 | 25 | Logs the given message to the console. 26 | 27 | ${Teilnehmer}[0] 28 | 29 | 30 | 31 | 32 | Logs the given message to the console. 33 | 34 | ${Teilnehmer} 35 | 36 | 37 | 38 | 39 | Logs the given message with the given level. 40 | 41 | ${User1} 42 | 43 | {'usr': 'rener', 'pwd': '12456', 'freunde': ['Marvin']} 44 | 45 | 46 | 47 | Logs the given message with the given level. 48 | 49 | ${User2} 50 | 51 | {'usr': 'christoph', 'pwd': 'test12', 'freunde': ['René', 'Britta', 'Marvin', 'Thomas', 'Kathrin', 'Christoph']} 52 | 53 | 54 | 55 | Logs the given messages as separate entries using the INFO level. 56 | 57 | @{Teilnehmer} 58 | 59 | René 60 | Britta 61 | Marvin 62 | Thomas 63 | Kathrin 64 | Christoph 65 | 66 | 67 | 68 | Logs the given messages as separate entries using the INFO level. 69 | 70 | @{Benutzer} 71 | 72 | {'usr': 'rener', 'pwd': '12456', 'freunde': ['Marvin']} 73 | {'usr': 'christoph', 'pwd': 'test12', 'freunde': ['René', 'Britta', 'Marvin', 'Thomas', 'Kathrin', 'Christoph']} 74 | 75 | 76 | 77 | Executes all the given keywords in a sequence. 78 | 79 | log 80 | Hello World 81 | AND 82 | Set Test Variable 83 | ${var} 84 | Hello World 85 | AND 86 | Log 87 | ${var} 88 | 89 | 90 | Logs the given message with the given level. 91 | 92 | Hello World 93 | 94 | Hello World 95 | 96 | 97 | 98 | Makes a variable available everywhere within the scope of the current test. 99 | 100 | ${var} 101 | Hello World 102 | 103 | ${var} = Hello World 104 | 105 | 106 | 107 | Logs the given message with the given level. 108 | 109 | ${var} 110 | 111 | Hello World 112 | 113 | 114 | 115 | 116 | 117 | 118 | Log to Console 119 | ${var} 120 | 121 | 122 | Logs the given message to the console. 123 | 124 | ${var} 125 | 126 | 127 | 128 | ${RETURN_VALUE} = None 129 | No keyword with name 'Debug' found. 130 | 131 | 132 | 133 | ananke 134 | Ersteller:René Rohner 135 | 136 | No keyword with name 'Debug' found. 137 | 138 | 139 | 140 | No keyword with name 'Log ${auth}' found. 141 | 142 | 143 | No keyword with name 'Log ${auth}' found. 144 | 145 | 146 | René Rohner (imbus) 147 | 148 | Suite teardown failed: 149 | No keyword with name 'Log ${auth}' found. 150 | 151 | 152 | 153 | Critical Tests 154 | All Tests 155 | 156 | 157 | ananke 158 | Ersteller:René Rohner 159 | 160 | 161 | MyTest 162 | 163 | 164 | 165 | Calling method 'end_keyword' of listener 'Debugger' failed: TclError: invalid command name "html" 166 | Calling method 'end_keyword' of listener 'Debugger' failed: TclError: invalid command name "html" 167 | Calling method 'end_keyword' of listener 'Debugger' failed: TclError: invalid command name "html" 168 | 169 | 170 | --------------------------------------------------------------------------------