├── .hgignore ├── BUILD.rst ├── COPYRIGHT.txt ├── LICENSE.txt ├── MANIFEST.in ├── README.rst ├── atest ├── __init__.txt ├── atest_settings.py ├── invalid_file_upload.txt ├── lib │ └── NameParser.py ├── libraries_listed.txt ├── library_details.txt ├── resources │ ├── library_information.txt │ ├── rfdoc.txt │ └── search.txt ├── results │ └── .hgkeep ├── run_atests.py ├── run_ci_tests.sh ├── search_functionality.txt ├── search_interface.txt ├── testdata │ ├── databases │ │ ├── empty.db │ │ └── libraries.db │ ├── sources │ │ └── example_lib.py │ └── xmls │ │ ├── BuiltIn.xml │ │ ├── ExampleLibrary_version_1.xml │ │ ├── ExampleLibrary_version_2.xml │ │ ├── SeleniumLibrary.xml │ │ └── invalid │ │ ├── empty_keyword.xml │ │ ├── empty_keyword_name.xml │ │ ├── empty_keywords.xml │ │ ├── empty_library_name.xml │ │ ├── invalid_content.xml │ │ ├── missing_keyword_doc.xml │ │ ├── missing_keyword_name.xml │ │ ├── missing_keywords.xml │ │ ├── missing_library_doc.xml │ │ ├── missing_library_name.xml │ │ └── text_file.txt ├── update_documentation.txt ├── upload_documentation.txt └── uploader.txt ├── setup.cfg ├── setup.py └── src └── rfdoc ├── __init__.py ├── manage.py ├── rfdoc_wsgi.py ├── rfdocapp ├── __init__.py ├── admin.py ├── models.py ├── static │ ├── default.css │ ├── jquery-1.11.0.min.js │ ├── print.css │ └── rfdoc.js ├── templates │ ├── 404.html │ ├── 500.html │ ├── base.html │ ├── index.html │ ├── library.html │ ├── search.html │ ├── search_form.html │ └── upload.html ├── templatetags │ ├── __init__.py │ └── rfdoc_filters.py ├── tests.py ├── utils │ ├── __init__.py │ └── robot_htmlutils.py └── views │ ├── __init__.py │ ├── index.py │ ├── library.py │ ├── search.py │ └── upload.py ├── rfdocsettings_defaults.py ├── settings.py ├── upload.py ├── urls.py └── version.py /.hgignore: -------------------------------------------------------------------------------- 1 | syntax: glob 2 | 3 | *~ 4 | *.pyc 5 | *.class 6 | .project 7 | .pydevproject 8 | .idea 9 | build 10 | dist 11 | *.egg-info 12 | rfdoc.db 13 | log.html 14 | report.html 15 | output.xml 16 | 17 | -------------------------------------------------------------------------------- /BUILD.rst: -------------------------------------------------------------------------------- 1 | RFDoc 2 | ===== 3 | 4 | Releasing a new version 5 | ----------------------- 6 | 7 | 1. Update the VERSION identifier 8 | 9 | Edit 'src/rfdoc/version.py' in the source repo and commit. 10 | 11 | 2. Tag the source repo and push it 12 | 13 | hg tag N.N && hg push 14 | 15 | 3. Create a source .tar.gz distribution 16 | 17 | python setup.py sdist --formats=gztar 18 | 19 | Verify that the content looks correct: 20 | 21 | tar -ztvf dist/robotframework-rfdoc-N.N.tar.gz 22 | 23 | 4. Upload the source distribution to PyPi 24 | 25 | python setup.py sdist upload 26 | 27 | 5. Update the 'News' section at https://code.google.com/p/rfdoc to mention 28 | about the release with short description of what was done. 29 | 30 | Done via https://code.google.com/p/rfdoc/admin. 31 | -------------------------------------------------------------------------------- /COPYRIGHT.txt: -------------------------------------------------------------------------------- 1 | Copyright 2009-2013 Nokia Siemens Networks Oyj 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 | -------------------------------------------------------------------------------- /LICENSE.txt: -------------------------------------------------------------------------------- 1 | 2 | Apache License 3 | Version 2.0, January 2004 4 | http://www.apache.org/licenses/ 5 | 6 | TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION 7 | 8 | 1. Definitions. 9 | 10 | "License" shall mean the terms and conditions for use, reproduction, 11 | and distribution as defined by Sections 1 through 9 of this document. 12 | 13 | "Licensor" shall mean the copyright owner or entity authorized by 14 | the copyright owner that is granting the License. 15 | 16 | "Legal Entity" shall mean the union of the acting entity and all 17 | other entities that control, are controlled by, or are under common 18 | control with that entity. For the purposes of this definition, 19 | "control" means (i) the power, direct or indirect, to cause the 20 | direction or management of such entity, whether by contract or 21 | otherwise, or (ii) ownership of fifty percent (50%) or more of the 22 | outstanding shares, or (iii) beneficial ownership of such entity. 23 | 24 | "You" (or "Your") shall mean an individual or Legal Entity 25 | exercising permissions granted by this License. 26 | 27 | "Source" form shall mean the preferred form for making modifications, 28 | including but not limited to software source code, documentation 29 | source, and configuration files. 30 | 31 | "Object" form shall mean any form resulting from mechanical 32 | transformation or translation of a Source form, including but 33 | not limited to compiled object code, generated documentation, 34 | and conversions to other media types. 35 | 36 | "Work" shall mean the work of authorship, whether in Source or 37 | Object form, made available under the License, as indicated by a 38 | copyright notice that is included in or attached to the work 39 | (an example is provided in the Appendix below). 40 | 41 | "Derivative Works" shall mean any work, whether in Source or Object 42 | form, that is based on (or derived from) the Work and for which the 43 | editorial revisions, annotations, elaborations, or other modifications 44 | represent, as a whole, an original work of authorship. For the purposes 45 | of this License, Derivative Works shall not include works that remain 46 | separable from, or merely link (or bind by name) to the interfaces of, 47 | the Work and Derivative Works thereof. 48 | 49 | "Contribution" shall mean any work of authorship, including 50 | the original version of the Work and any modifications or additions 51 | to that Work or Derivative Works thereof, that is intentionally 52 | submitted to Licensor for inclusion in the Work by the copyright owner 53 | or by an individual or Legal Entity authorized to submit on behalf of 54 | the copyright owner. For the purposes of this definition, "submitted" 55 | means any form of electronic, verbal, or written communication sent 56 | to the Licensor or its representatives, including but not limited to 57 | communication on electronic mailing lists, source code control systems, 58 | and issue tracking systems that are managed by, or on behalf of, the 59 | Licensor for the purpose of discussing and improving the Work, but 60 | excluding communication that is conspicuously marked or otherwise 61 | designated in writing by the copyright owner as "Not a Contribution." 62 | 63 | "Contributor" shall mean Licensor and any individual or Legal Entity 64 | on behalf of whom a Contribution has been received by Licensor and 65 | subsequently incorporated within the Work. 66 | 67 | 2. Grant of Copyright License. Subject to the terms and conditions of 68 | this License, each Contributor hereby grants to You a perpetual, 69 | worldwide, non-exclusive, no-charge, royalty-free, irrevocable 70 | copyright license to reproduce, prepare Derivative Works of, 71 | publicly display, publicly perform, sublicense, and distribute the 72 | Work and such Derivative Works in Source or Object form. 73 | 74 | 3. Grant of Patent License. Subject to the terms and conditions of 75 | this License, each Contributor hereby grants to You a perpetual, 76 | worldwide, non-exclusive, no-charge, royalty-free, irrevocable 77 | (except as stated in this section) patent license to make, have made, 78 | use, offer to sell, sell, import, and otherwise transfer the Work, 79 | where such license applies only to those patent claims licensable 80 | by such Contributor that are necessarily infringed by their 81 | Contribution(s) alone or by combination of their Contribution(s) 82 | with the Work to which such Contribution(s) was submitted. If You 83 | institute patent litigation against any entity (including a 84 | cross-claim or counterclaim in a lawsuit) alleging that the Work 85 | or a Contribution incorporated within the Work constitutes direct 86 | or contributory patent infringement, then any patent licenses 87 | granted to You under this License for that Work shall terminate 88 | as of the date such litigation is filed. 89 | 90 | 4. Redistribution. You may reproduce and distribute copies of the 91 | Work or Derivative Works thereof in any medium, with or without 92 | modifications, and in Source or Object form, provided that You 93 | meet the following conditions: 94 | 95 | (a) You must give any other recipients of the Work or 96 | Derivative Works a copy of this License; and 97 | 98 | (b) You must cause any modified files to carry prominent notices 99 | stating that You changed the files; and 100 | 101 | (c) You must retain, in the Source form of any Derivative Works 102 | that You distribute, all copyright, patent, trademark, and 103 | attribution notices from the Source form of the Work, 104 | excluding those notices that do not pertain to any part of 105 | the Derivative Works; and 106 | 107 | (d) If the Work includes a "NOTICE" text file as part of its 108 | distribution, then any Derivative Works that You distribute must 109 | include a readable copy of the attribution notices contained 110 | within such NOTICE file, excluding those notices that do not 111 | pertain to any part of the Derivative Works, in at least one 112 | of the following places: within a NOTICE text file distributed 113 | as part of the Derivative Works; within the Source form or 114 | documentation, if provided along with the Derivative Works; or, 115 | within a display generated by the Derivative Works, if and 116 | wherever such third-party notices normally appear. The contents 117 | of the NOTICE file are for informational purposes only and 118 | do not modify the License. You may add Your own attribution 119 | notices within Derivative Works that You distribute, alongside 120 | or as an addendum to the NOTICE text from the Work, provided 121 | that such additional attribution notices cannot be construed 122 | as modifying the License. 123 | 124 | You may add Your own copyright statement to Your modifications and 125 | may provide additional or different license terms and conditions 126 | for use, reproduction, or distribution of Your modifications, or 127 | for any such Derivative Works as a whole, provided Your use, 128 | reproduction, and distribution of the Work otherwise complies with 129 | the conditions stated in this License. 130 | 131 | 5. Submission of Contributions. Unless You explicitly state otherwise, 132 | any Contribution intentionally submitted for inclusion in the Work 133 | by You to the Licensor shall be under the terms and conditions of 134 | this License, without any additional terms or conditions. 135 | Notwithstanding the above, nothing herein shall supersede or modify 136 | the terms of any separate license agreement you may have executed 137 | with Licensor regarding such Contributions. 138 | 139 | 6. Trademarks. This License does not grant permission to use the trade 140 | names, trademarks, service marks, or product names of the Licensor, 141 | except as required for reasonable and customary use in describing the 142 | origin of the Work and reproducing the content of the NOTICE file. 143 | 144 | 7. Disclaimer of Warranty. Unless required by applicable law or 145 | agreed to in writing, Licensor provides the Work (and each 146 | Contributor provides its Contributions) on an "AS IS" BASIS, 147 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or 148 | implied, including, without limitation, any warranties or conditions 149 | of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A 150 | PARTICULAR PURPOSE. You are solely responsible for determining the 151 | appropriateness of using or redistributing the Work and assume any 152 | risks associated with Your exercise of permissions under this License. 153 | 154 | 8. Limitation of Liability. In no event and under no legal theory, 155 | whether in tort (including negligence), contract, or otherwise, 156 | unless required by applicable law (such as deliberate and grossly 157 | negligent acts) or agreed to in writing, shall any Contributor be 158 | liable to You for damages, including any direct, indirect, special, 159 | incidental, or consequential damages of any character arising as a 160 | result of this License or out of the use or inability to use the 161 | Work (including but not limited to damages for loss of goodwill, 162 | work stoppage, computer failure or malfunction, or any and all 163 | other commercial damages or losses), even if such Contributor 164 | has been advised of the possibility of such damages. 165 | 166 | 9. Accepting Warranty or Additional Liability. While redistributing 167 | the Work or Derivative Works thereof, You may choose to offer, 168 | and charge a fee for, acceptance of support, warranty, indemnity, 169 | or other liability obligations and/or rights consistent with this 170 | License. However, in accepting such obligations, You may act only 171 | on Your own behalf and on Your sole responsibility, not on behalf 172 | of any other Contributor, and only if You agree to indemnify, 173 | defend, and hold each Contributor harmless for any liability 174 | incurred by, or claims asserted against, such Contributor by reason 175 | of your accepting any such warranty or additional liability. 176 | 177 | END OF TERMS AND CONDITIONS 178 | 179 | APPENDIX: How to apply the Apache License to your work. 180 | 181 | To apply the Apache License to your work, attach the following 182 | boilerplate notice, with the fields enclosed by brackets "[]" 183 | replaced with your own identifying information. (Don't include 184 | the brackets!) The text should be enclosed in the appropriate 185 | comment syntax for the file format. We also recommend that a 186 | file or class name and description of purpose be included on the 187 | same "printed page" as the copyright notice for easier 188 | identification within third-party archives. 189 | 190 | Copyright [yyyy] [name of copyright owner] 191 | 192 | Licensed under the Apache License, Version 2.0 (the "License"); 193 | you may not use this file except in compliance with the License. 194 | You may obtain a copy of the License at 195 | 196 | http://www.apache.org/licenses/LICENSE-2.0 197 | 198 | Unless required by applicable law or agreed to in writing, software 199 | distributed under the License is distributed on an "AS IS" BASIS, 200 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 201 | See the License for the specific language governing permissions and 202 | limitations under the License. 203 | -------------------------------------------------------------------------------- /MANIFEST.in: -------------------------------------------------------------------------------- 1 | prune atest 2 | include src/rfdoc/*.tmpl 3 | include src/rfdoc/rfdocapp/templates/*.html 4 | include src/rfdoc/rfdocapp/static/*.css 5 | include src/rfdoc/rfdocapp/static/*.js 6 | include COPYRIGHT.txt 7 | include LICENSE.txt 8 | include README.rst 9 | -------------------------------------------------------------------------------- /README.rst: -------------------------------------------------------------------------------- 1 | RFDoc 2 | ===== 3 | 4 | Introduction 5 | ------------ 6 | 7 | RFDoc is a web application for storing and searching `Robot Framework 8 | `_ test library and resource file documentations. 9 | 10 | RFDoc is implemented using `Django web framework `_. 11 | 12 | **Note:** This project is currently not actively maintained. 13 | 14 | License 15 | ------- 16 | 17 | RFDoc is licensed under Apache License 2.0. 18 | 19 | See LICENSE.txt for details. 20 | 21 | Running RFDoc 22 | ------------- 23 | 24 | For getting RFDoc to run locally, see 25 | https://github.com/robotframework/rfdoc/blob/wiki/DevelopmentEnvironment.md 26 | 27 | For setting up a public production server, see 28 | https://github.com/robotframework/rfdoc/blob/wiki/ProductionEnvironment.md 29 | 30 | Directory Layout 31 | ---------------- 32 | 33 | atest/ 34 | Acceptance tests. Naturally using Robot Framework. 35 | 36 | src/ 37 | RFDoc source code. 38 | 39 | tools/ 40 | Utilities to use as part of the CI pipeline or as SCM hooks. 41 | 42 | Running the Acceptance Tests 43 | ---------------------------- 44 | 45 | Acceptance tests are run using ``atest/run_atests.py``. 46 | 47 | Run the script without any arguments for help. 48 | -------------------------------------------------------------------------------- /atest/__init__.txt: -------------------------------------------------------------------------------- 1 | *** Settings *** 2 | Suite Setup Clear Database And Open Browser 3 | Suite Teardown Close All Browsers 4 | Resource resources/rfdoc.txt 5 | 6 | *** Variables *** 7 | ${DEMO DELAY} 0 seconds 8 | 9 | *** Keywords *** 10 | Clear Database And Open Browser 11 | Given no libraries exist in RFDoc 12 | Open Browser ${BASE URL} ${BROWSER} 13 | Set selenium speed ${DEMO DELAY} 14 | 15 | -------------------------------------------------------------------------------- /atest/atest_settings.py: -------------------------------------------------------------------------------- 1 | # Django settings for RFDoc project acceptance tests. 2 | 3 | import sys 4 | from os.path import dirname, join 5 | 6 | sys.path.append(join(dirname(__file__), '..', 'src')) 7 | from rfdoc.settings import * 8 | 9 | DEBUG = True 10 | DATABASES['default']['NAME'] = join(dirname(__file__), 'results', 'rfdoc.db') 11 | -------------------------------------------------------------------------------- /atest/invalid_file_upload.txt: -------------------------------------------------------------------------------- 1 | *** Settings *** 2 | Resource resources/rfdoc.txt 3 | Test Setup "Upload" page is open 4 | 5 | *** Variables *** 6 | ${NON EXISTING FILE} The submitted file is empty. 7 | 8 | *** Test Cases *** *** Uploaded File *** *** Error Message *** 9 | Non XML file Uploading fails text_file.txt Given file text_file.txt is not XML: syntax error: line 1, column 0 10 | Wrong XML content Uploading fails invalid_content.xml Given file invalid_content.xml contains invalid XML: Root tag must be keywordspec 11 | Non existing file Uploading fails non_existing_file.xml ${NON EXISTING FILE} 12 | Empty library name Uploading fails empty_library_name.xml Library parsing error. Given file empty_library_name.xml contains invalid XML: Attribute name not found 13 | Missing library name Uploading fails missing_library_name.xml Library parsing error. Given file missing_library_name.xml contains invalid XML: Attribute name not found 14 | Missing library doc Uploading fails missing_library_doc.xml Library parsing error. Given file missing_library_doc.xml contains invalid XML: Child element doc not found 15 | Empty keyword name Uploading fails empty_keyword_name.xml Keyword parsing error. Given file empty_keyword_name.xml contains invalid XML: Attribute name not found 16 | Missing keyword name Uploading fails missing_keyword_name.xml Keyword parsing error. Given file missing_keyword_name.xml contains invalid XML: Attribute name not found 17 | Missing keyword doc Uploading fails missing_keyword_doc.xml Keyword parsing error. Given file missing_keyword_doc.xml contains invalid XML: Child element doc not found 18 | Empty keyword Uploading fails empty_keyword.xml Keyword parsing error. Given file empty_keyword.xml contains invalid XML: Child element doc not found 19 | Empty keywords Uploading fails empty_keywords.xml Given test library file empty_keywords.xml contains no keywords. 20 | Missing keywords Uploading fails missing_keywords.xml Given test library file missing_keywords.xml contains no keywords. 21 | 22 | *** Keywords *** 23 | Uploading fails [Arguments] ${file} ${message} 24 | Upload invalid${/}${file} 25 | error "${message}" is shown 26 | -------------------------------------------------------------------------------- /atest/lib/NameParser.py: -------------------------------------------------------------------------------- 1 | def get_names(namestr): 2 | if namestr and namestr[0] == namestr[-1] == '"': 3 | namestr = namestr[1:-1] 4 | return namestr.replace(' and ', ', ').split('", "') 5 | 6 | def get_library_name(library_name): 7 | return library_name.split()[0] 8 | 9 | def get_library_version(library_name): 10 | return library_name.split()[-1] -------------------------------------------------------------------------------- /atest/libraries_listed.txt: -------------------------------------------------------------------------------- 1 | *** Settings *** 2 | Resource resources/library_information.txt 3 | 4 | *** Test Cases *** 5 | Main Page Contains Info That No Libraries Exist In RFDoc 6 | [Setup] Given no libraries exist in RFDoc 7 | "main" page is open 8 | Then notification "No libraries in the system." is shown 9 | 10 | Libraries In RFDoc Are Listed On Main Page 11 | [Setup] Given some libraries exist in RFDoc 12 | "main" page is open 13 | Then "BuiltIn", "ExampleLibrary" and "SeleniumLibrary" are listed on the main page 14 | 15 | Library Versions Are Listed On Main Page 16 | [Setup] Given some libraries exist in RFDoc 17 | "main" page is open 18 | Then number of versions is shown 19 | 20 | Libraries Can Be Grouped By Version On Main Page 21 | [Setup] Given some libraries exist in RFDoc 22 | "main" page is open 23 | Click link Sort by version 24 | Then versions "2.1", "1", "2.2", "3" are listed on the main page 25 | 26 | User Can List Libraries Again By Name After Grouping Them By Version 27 | [Setup] Given some libraries exist in RFDoc 28 | version sorted "main" page is open 29 | Click link Sort by library name 30 | Then "BuiltIn", "ExampleLibrary" and "SeleniumLibrary" are listed on the main page 31 | 32 | Version Grouping Should Show All Libraries Of Same Version 33 | [Setup] Given some libraries exist in RFDoc 34 | version sorted "main" page is open 35 | Then "ExampleLibrary", "BuiltIn" are listed in the same version column -------------------------------------------------------------------------------- /atest/library_details.txt: -------------------------------------------------------------------------------- 1 | *** Settings *** 2 | Resource resources/library_information.txt 3 | Suite Setup Given BuiltIn and ExampleLibrary exist in RFDoc 4 | 5 | *** Test Cases *** 6 | Library Details Are Visible 7 | [Setup] When user opens library with version information 8 | Then library name, version and documentation are shown 9 | 10 | Library Initialization Is Visible 11 | [Setup] When user opens library with initialization information 12 | Then library initialization is shown 13 | 14 | Library Initialization Is Not Visible 15 | [Setup] When user opens library without initialization information 16 | Then library initialization is not shown 17 | 18 | Keyword Details Are Visible 19 | [Setup] When user opens library details 20 | Then keywords are listed with name, argument and documentation information 21 | 22 | Total Number Of Keywords Is Visible 23 | [Setup] When user opens library details 24 | Then total number of keywords in library is shown 25 | 26 | Shortcut Links Exist 27 | [Setup] When user opens library details 28 | Then list of keywords linking to the keyword details is shown 29 | 30 | Keyword Documentation Can Contain Keyword-to-keyword Links 31 | [Setup] When user opens library details 32 | Then link exist from keyword documentation to another keyword 33 | 34 | Other versions are visible 35 | [Setup] When user opens library with multiple versions 36 | Then other versions are shown 37 | -------------------------------------------------------------------------------- /atest/resources/library_information.txt: -------------------------------------------------------------------------------- 1 | *** Settings *** 2 | Resource rfdoc.txt 3 | 4 | *** Keywords *** 5 | "${libraries}" Are Listed On The Main Page 6 | @{libraries} = Get names ${libraries} 7 | Main page should contain libraries @{libraries} 8 | 9 | Main Page Should Contain Libraries 10 | [Arguments] @{libraries} 11 | ${regexp} = Set Variable

Libraries

\\s+Sort by version\\s+ 15 | ${source} = Get source 16 | Should match regexp ${source} ${regexp} 17 | 18 | Versions "${versions}" Are Listed On The Main Page 19 | @{versions} = Get Names ${versions} 20 | ${regexp} = Set Variable (?s)
\\s+ 21 | :FOR ${version} IN @{versions} 22 | \ ${regexp} = Set Variable ${regexp}
\\s+

${version}

.*
\\s+ 23 | ${regexp} = Set Variable ${regexp}
24 | ${source} = Get source 25 | Should match regexp ${source} ${regexp} 26 | 27 | "${libraries}" Are Listed In The Same Version Column 28 | @{libraries} = Get Names ${libraries} 29 | ${regexp} = Set Variable
\\s+

.+

\\s+
    \\s+ 30 | :FOR ${library} IN @{libraries} 31 | \ ${regexp} = Set Variable ${regexp}
  • ${library}
  • \\s+ 32 | ${regexp} = Set Variable ${regexp}
\\s+
33 | ${source} = Get source 34 | Should match regexp ${source} ${regexp} 35 | 36 | User Opens Library Details 37 | Navigate to library "BuiltIn" 38 | 39 | User Opens Library With Version Information 40 | User opens library details 41 | 42 | User Opens Library With Multiple Versions 43 | Navigate to library "ExampleLibrary" 44 | 45 | Library Name, Version And Documentation Are Shown 46 | Library name should be BuiltIn 47 | Page should contain Version: 2.1 48 | Page should contain An always available standard library with often needed keywords. 49 | 50 | Library Name And Documentation Are Shown 51 | Library name should be ExampleLibrary 52 | Page should contain First Library Doc 53 | 54 | Library Name Should Be 55 | [Arguments] ${name} 56 | Element should contain xpath=//h2 ${name} 57 | 58 | No Version Information Is Shown 59 | Page should not contain Version: 60 | 61 | User Opens Library With Initialization Information 62 | Navigate to library "ExampleLibrary" 63 | 64 | Library Initialization Is Shown 65 | Element should contain importing Importing 66 | Page should contain Library can be imported with optional arguments. 67 | Page should contain timeout=3.0, is_regexp=False 68 | 69 | User Opens Library Without Initialization Information 70 | User opens library details 71 | 72 | Library Initialization Is Not Shown 73 | Page should not contain element importing 74 | 75 | Keywords Are Listed With Name, Argument And Documentation Information 76 | Library should contain keywords @{BUILTIN KEYWORDS} 77 | 78 | Total Number Of Keywords In Library Is Shown 79 | Page should contain Altogether 72 keywords. 80 | 81 | List Of Keywords Linking To The Keyword Details Is Shown 82 | Should contain link to keyword with short documentation Sleep Pauses the test executed for the given time. 83 | Should contain link to keyword with short documentation Fail Fails the test immediately with the given (optional) message. 84 | 85 | Should Contain Link To Keyword With Short Documentation 86 | [Arguments] ${name} ${short doc} 87 | ${source} = Get source 88 | Should Contain ${source} ${name} 89 | Keyword should contain anchor ${source} ${name} 90 | 91 | Keyword Should Contain Anchor 92 | [Arguments] ${source} ${name} 93 | Should contain ${source} ${name} 94 | 95 | Link Exist From Keyword Documentation To Another Keyword 96 | ${source} = Get source 97 | Should contain ${source} Get Length 98 | Keyword should contain anchor ${source} Get Length 99 | 100 | Other Versions Are Shown 101 | ${regexp} = Set Variable v: 1\\s+
\\s+
Other versions:
\\s+
3
\\s+
102 | ${source} = Get source 103 | Should match regexp ${source} ${regexp} 104 | 105 | Number Of Versions Is Shown 106 | ${regexp} = Set Variable ExampleLibrary \\( 2 versions \\) 107 | ${source} = Get source 108 | Should match regexp ${source} ${regexp} -------------------------------------------------------------------------------- /atest/resources/rfdoc.txt: -------------------------------------------------------------------------------- 1 | *** Settings *** 2 | Library Selenium2Library run_on_failure=Nothing 3 | Library OperatingSystem 4 | Library ${CURDIR}/../lib/NameParser.py 5 | 6 | *** Variables *** 7 | ${HOST} localhost 8 | ${RFDOC PORT} 7000 9 | ${BASE URL} http://${HOST}:${RFDOC PORT} 10 | ${BROWSER} firefox 11 | ${TESTDATA PATH} ${CURDIR}${/}..${/}testdata 12 | ${RESULTS PATH} ${CURDIR}${/}..${/}results 13 | @{BUILTIN KEYWORDS} Convert To Integer item Converts the given item to an integer number. No Operation ${EMPTY} Does absolutely nothing. 14 | @{EXAMPLELIBRARY VERSION 1 KEYWORDS} First Keyword first, second=default, *args First KW doc My Second Keyword some Second KW doc 15 | @{EXAMPLELIBRARY VERSION 2 KEYWORDS} First Keyword Updated first, second=default updated, *args Updated KW doc My Second Keyword some Second KW doc Third Keyword third Third KW doc 16 | @{EXAMPLELIBRARY VERSION 5 KEYWORDS} @{EXAMPLELIBRARY VERSION 1 KEYWORDS} 17 | @{NEWNAMELIBRARY VERSION 2 KEYWORDS} @{EXAMPLELIBRARY VERSION 2 KEYWORDS} 18 | 19 | *** Keywords *** 20 | Given no libraries exist in RFDoc 21 | Copy file ${TEST DATA PATH}/databases/empty.db 22 | ... ${RESULTS PATH}/rfdoc.db 23 | 24 | ${Some libraries} exist${s} in RFDoc 25 | Copy file ${TEST DATA PATH}/databases/libraries.db 26 | ... ${RESULTS PATH}/rfdoc.db 27 | 28 | "Main" page is open 29 | Go to ${BASE URL} 30 | 31 | Version sorted "Main" page is open 32 | Go to ${BASE_URL}/?sort=version 33 | 34 | "${name}" page is open 35 | Go to ${BASE URL}/${name.lower()} 36 | 37 | "${name}" page is opened 38 | Location should be ${BASE URL}/${name} 39 | 40 | Error "${message}" is shown 41 | Element should contain xpath=//ul[@class='errorlist'] ${message} 42 | 43 | User selects "${name}" option 44 | Select checkbox ${name} 45 | 46 | Notification "${message}" is shown 47 | Page should contain ${message} 48 | 49 | Notification contains a link to ${library} 50 | Page should contain link /lib/${library} ${library} 51 | 52 | Notification contains a version "${version}" link to "${library}" 53 | Page should contain link /lib/${library}/${version} ${library} 54 | 55 | Navigate to library "${name}" 56 | Go to ${BASE URL}/lib/${name} 57 | Title should be RFDoc | ${name} 58 | 59 | Navigate to versioned library "${name}" "${version}" 60 | Go to ${BASE URL}/lib/${name}/${version} 61 | Title should be RFDoc | ${name} 62 | 63 | User uploads ${library} 64 | ${file} = Set Variable ${library.replace(' ', '_')}.xml 65 | Upload ${file} 66 | 67 | User overrides version with "${version}" and uploads "${library}" 68 | ${file} = Set Variable ${library.replace(' ', '_')}.xml 69 | Input text override_version ${version} 70 | Upload ${file} 71 | 72 | User overrides name with "${name}" and uploads "${library}" 73 | ${file} = Set Variable ${library.replace(' ', '_')}.xml 74 | Input text override_name ${name} 75 | Upload ${file} 76 | 77 | Upload ${file} 78 | Choose File file ${TESTDATA PATH}${/}xmls${/}${file} 79 | Click button Upload 80 | 81 | RFDoc contains versioned library ${library name with version} 82 | ${name} = Get library name ${library name with version} 83 | ${version} = Get library version ${library name with version} 84 | Navigate to versioned library "${name}" "${version}" 85 | @{keywords} = Set variable @{${library name with version} KEYWORDS} 86 | Library should contain keywords @{keywords} 87 | 88 | RFDoc contains library ${library name} 89 | Navigate to library "${library name}" 90 | @{keywords} = Set variable @{${library name} KEYWORDS} 91 | Library should contain keywords @{keywords} 92 | 93 | Library should contain keywords [Arguments] @{keywords} 94 | ${source} = Get source 95 | :FOR ${name} ${args} ${doc} IN @{keywords} 96 | \ Source should contain keyword ${source} ${name} ${args} ${doc} 97 | 98 | Source should contain keyword [Arguments] ${source} ${name} ${args} ${doc} 99 | ${match} ${group1} ${group2} = Should Match Regexp ${source} 100 | ... (?s)${name}\\s*(.*?)\\s*(.+?) 101 | Should be equal ${group1} ${args} 102 | Should be equal ${group2} ${doc} 103 | -------------------------------------------------------------------------------- /atest/resources/search.txt: -------------------------------------------------------------------------------- 1 | *** Settings *** 2 | Resource rfdoc.txt 3 | 4 | *** Keywords *** 5 | User searches with "${search term}" 6 | Input text search_term ${search term} 7 | Click Button search_button 8 | 9 | User searches with "${search term}" without documentation 10 | Unselect Checkbox include_doc 11 | User searches with "${search term}" 12 | 13 | User searches case sensitively with "${search term}" 14 | Unselect Checkbox case_insensitive 15 | User searches with "${search term}" 16 | 17 | User searches case sensitively with "${search term}" without documentation 18 | Unselect Checkbox case_insensitive 19 | User searches with "${search term}" without documentation 20 | 21 | User searches "${search term}" with version "${search version}" 22 | Input text search_version ${search version} 23 | User searches with "${search term}" 24 | 25 | "${keywords}" keywords are displayed 26 | @{keywords} = Get names ${keywords} 27 | :FOR ${name} IN @{keywords} 28 | \ Search Result Should Contain ${name} 29 | 30 | Search Result Should Contain ${name} 31 | Page should contain element //td/a[text()='${name}'] 32 | 33 | Then search results contain keyword "Get Title" with link to "SeleniumLibrary" with version and with documentation 34 | Page Should Contain Element //td/a[@href='/lib/SeleniumLibrary/2.2#GetTitle' and text()='Get Title'] 35 | Page Should Contain Element //td/a[@href='/lib/SeleniumLibrary/2.2' and text()='SeleniumLibrary'] 36 | Page Should Contain Element //td[text()='2.2'] 37 | Page Should Contain Returns title of current page. 38 | -------------------------------------------------------------------------------- /atest/results/.hgkeep: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/robotframework/rfdoc/bd7063f8e1f93de6f7c792e1dce601feb7dfb671/atest/results/.hgkeep -------------------------------------------------------------------------------- /atest/run_atests.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python 2 | 3 | """Usage: python run_atests.py mode [options] 4 | 5 | This script executes RFDoc's acceptance tests using Selenium2Library. 6 | 7 | Valid `mode` is one of the following: 8 | regr Run acceptance tests so that RFDoc is started before 9 | the test execution and closed after it. 10 | RFDoc is started on port 7000. 11 | 12 | devel Runs acceptance tests so that RFDoc is not started nor stopped. 13 | The server must be started before executing as following: 14 | python -m rfdoc.manage runserver --pythonpath=atest/ --settings atest_settings 15 | 16 | ci Used at continuous integration servers. 17 | Basically same as 'devel' but disables colored test outputs. 18 | 19 | Valid `options` are the same as accepted by Robot Framework and they are passed 20 | to it directly. 21 | 22 | All outputs are written `atests/results` directory. 23 | 24 | Running the acceptance tests requires the following: 25 | - Python (2.6 or newer) 26 | - Django (1.5 or 1.6) 27 | - Robot Framework (2.6 or newer) 28 | - Selenium2Library 29 | 30 | Examples: 31 | $ ./run_atests.py regr 32 | $ ./run_atests.py devel --variable BROWSER:IE --suite upload 33 | """ 34 | 35 | import os 36 | import signal 37 | import sys 38 | from os.path import dirname, join 39 | from subprocess import call, PIPE, Popen 40 | 41 | 42 | ATEST_PATH = dirname(__file__) 43 | ATEST_RESULTS_PATH = join(ATEST_PATH, 'results') 44 | DJANGO_ADMIN = join(ATEST_PATH, '..', 'src', 'rfdoc', 'manage.py') 45 | SHELL = os.name == 'nt' 46 | 47 | try: 48 | call([DJANGO_ADMIN], stderr=PIPE, stdout=PIPE, shell=SHELL) 49 | except OSError: 50 | sys.stderr.write('error: Could not find %s from PATH\n' % DJANGO_ADMIN) 51 | exit(-1) 52 | 53 | 54 | class DevelopmentRunner(object): 55 | 56 | def run_tests(self, options): 57 | command = ['pybot', '-d', ATEST_RESULTS_PATH] + options + [ATEST_PATH] 58 | process = Popen(command, shell=SHELL) 59 | return process.wait() 60 | 61 | def finalize(self): 62 | pass 63 | 64 | 65 | class RegressionRunner(DevelopmentRunner): 66 | 67 | def __init__(self): 68 | self._rfdoc_pid = self._start_rfdoc() 69 | 70 | def _start_rfdoc(self): 71 | command = [DJANGO_ADMIN, 'runserver', 72 | '--pythonpath', ATEST_PATH, 73 | '--settings', 'atest_settings', 74 | '7000'] 75 | process = Popen(command, stderr=PIPE, shell=SHELL) 76 | return process.pid 77 | 78 | def run_tests(self, options): 79 | DevelopmentRunner.run_tests(self, options) 80 | 81 | def finalize(self): 82 | DevelopmentRunner.finalize(self) 83 | self._stop_rfdoc() 84 | 85 | def _stop_rfdoc(self): 86 | if os.name == 'nt': 87 | self._kill_rfdoc_on_windows() 88 | else: 89 | self._kill_rfdoc_on_posix() 90 | 91 | def _kill_rfdoc_on_windows(self): 92 | Popen('taskkill /t /f /pid %d' % self._rfdoc_pid, stdout=PIPE) 93 | 94 | def _kill_rfdoc_on_posix(self): 95 | os.killpg(os.getpgrp(), signal.SIGKILL) 96 | 97 | 98 | class CiRunner(RegressionRunner): 99 | 100 | def run_tests(self, options): 101 | RegressionRunner.run_tests(self, ['--monitorcolors', 'off'] + options) 102 | 103 | 104 | if __name__ == '__main__': 105 | runners = { 106 | 'devel': DevelopmentRunner, 107 | 'regr': RegressionRunner, 108 | 'ci': CiRunner 109 | } 110 | try: 111 | runner = runners[sys.argv[1].lower()]() 112 | except (IndexError, KeyError): 113 | sys.stdout.write(__doc__) 114 | sys.exit(-1) 115 | except Exception, message: 116 | sys.stderr.write('error: %s\n' % message) 117 | sys.exit(-1) 118 | rc = runner.run_tests(sys.argv[2:]) 119 | runner.finalize() 120 | sys.exit(rc) 121 | -------------------------------------------------------------------------------- /atest/run_ci_tests.sh: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | 3 | ATEST_DIR=`cd $(dirname "${BASH_SOURCE}") && pwd` 4 | 5 | xvfb-run $ATEST_DIR/run_atests.py ci 6 | -------------------------------------------------------------------------------- /atest/search_functionality.txt: -------------------------------------------------------------------------------- 1 | *** Settings *** 2 | Resource resources/search.txt 3 | Suite Setup Given SeleniumLibrary ExampleLibrary 1 and ExampleLibrary 2 exist in RFDoc 4 | 5 | *** Test Cases *** 6 | User Can Search Using Keyword Name And Documentation 7 | "search" page is open 8 | When user searches with "window" 9 | Then "Close Window", "Execute Javascript", "Select Window" and "Wait For Condition" keywords are displayed 10 | and notification "4 matching keywords found." is shown 11 | 12 | User Can Search Using Keyword Name Only 13 | "search" page is open 14 | When user searches with "window" without documentation 15 | Then "Close Window" and "Select Window" keywords are displayed 16 | and notification "2 matching keywords found." is shown 17 | 18 | User Can Search Case Sensitively Using Keyword Name And Documentation 19 | "search" page is open 20 | When user searches case sensitively with "Close All" 21 | Then "Close All Browsers", "Open Browser" and "Switch Browser" keywords are displayed 22 | and notification "3 matching keywords found." is shown 23 | 24 | User Can Search Case Sensitively Using Keyword Name Only 25 | "search" page is open 26 | When user searches case sensitively with "Close All" without documentation 27 | Then "Close All Browsers" keywords are displayed 28 | and notification "1 matching keyword found." is shown 29 | 30 | User Can Search Using Keyword Name And Version Info 31 | "search" page is open 32 | When user searches "First" with version "1" 33 | Then "First Keyword" keywords are displayed 34 | and notification "1 matching keyword found." is shown 35 | 36 | Message Is Displayed When Search Matches No Keywords 37 | "search" page is open 38 | When user searches with "non-matching" 39 | Then notification "No matching keywords found." is shown 40 | 41 | Error Is Displayed When Searching Without Search Term 42 | "search" page is open 43 | When user searches with "" 44 | Then error "Search term is required!" is shown 45 | 46 | -------------------------------------------------------------------------------- /atest/search_interface.txt: -------------------------------------------------------------------------------- 1 | *** Settings *** 2 | Resource resources/search.txt 3 | Suite Setup Given SeleniumLibrary exist in RFDoc 4 | 5 | *** Test Cases *** 6 | It Is Possible To Search From Main Page 7 | "main" page is open 8 | When user searches with "title" 9 | Then "search" page is opened 10 | And "Get Title" and "Title Should Be" keywords are displayed 11 | 12 | It Is Possible To Search From Search Page 13 | "search" page is open 14 | When user searches with "title" 15 | Then "search" page is opened 16 | And "Get Title" and "Title Should Be" keywords are displayed 17 | 18 | Search Results Contain Relevant Information 19 | "search" page is open 20 | And user searches with "title" 21 | Then search results contain keyword "Get Title" with link to "SeleniumLibrary" with version and with documentation 22 | -------------------------------------------------------------------------------- /atest/testdata/databases/empty.db: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/robotframework/rfdoc/bd7063f8e1f93de6f7c792e1dce601feb7dfb671/atest/testdata/databases/empty.db -------------------------------------------------------------------------------- /atest/testdata/databases/libraries.db: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/robotframework/rfdoc/bd7063f8e1f93de6f7c792e1dce601feb7dfb671/atest/testdata/databases/libraries.db -------------------------------------------------------------------------------- /atest/testdata/sources/example_lib.py: -------------------------------------------------------------------------------- 1 | """Library for demo purposes. 2 | 3 | This library is only used in an example and it doesn't do anything useful. 4 | """ 5 | 6 | def my_keyword(self): 7 | """Does nothing.""" 8 | pass 9 | 10 | def your_keyword(self, arg): 11 | """Takes one argument and does nothing with it.""" 12 | pass 13 | -------------------------------------------------------------------------------- /atest/testdata/xmls/BuiltIn.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 2.1 4 | An always available standard library with often needed keywords. 5 | 6 | `BuiltIn` is Robot Framework's standard library that provides a set of generic keywords needed often. It is imported automatically and thus always available. The provided keywords can be used, for example, for verifications (e.g. `Should Be Equal`, `Should Contain`), conversions (e.g. `Convert To Integer`) and for various other purposes (e.g. `Log`, `Sleep`, `Run Keyword If`, `Set Global Variable`). 7 | 8 | Calls the named method of the given object with the provided arguments. 9 | 10 | The possible return value from the method is returned and can be assigned to a variable. Keyword fails both if the object does not have a method with the given name or if executing the method raises an exception. 11 | 12 | Examples: 13 | | Call Method | ${hashtable} | put | myname | myvalue | 14 | | ${isempty} = | Call Method | ${hashtable} | isEmpty | | 15 | | Should Not Be True | ${isempty} | | | | 16 | | ${value} = | Call Method | ${hashtable} | get | myname | 17 | | Should Be Equal | ${value} | myvalue | | | 18 | 19 | object 20 | method_name 21 | *args 22 | 23 | 24 | 25 | Catenates the given items together and returns the resulted string. 26 | 27 | By default, items are catenated with spaces, but if the first item contains the string 'SEPARATOR=<sep>', the separator '<sep>' is used. Items are converted into strings when necessary. 28 | 29 | Examples: 30 | | ${str1} = | Catenate | Hello | world | | 31 | | ${str2} = | Catenate | SEPARATOR=--- | Hello | world | 32 | | ${str3} = | Catenate | SEPARATOR= | Hello | world | 33 | => 34 | - ${str1} = 'Hello world' 35 | - ${str2} = 'Hello---world' 36 | - ${str3} = 'Helloworld' 37 | 38 | *items 39 | 40 | 41 | 42 | Displays the given messages in the log file as keyword arguments. 43 | 44 | This keyword does nothing with the arguments it receives, but as they are visible in the log, this keyword can be used to display simple messages. Given arguments are ignored so thoroughly that they can even contain non-existing variables. If you are interested about variable values, you can use the `Log` or `Log Many` keywords. 45 | 46 | *messages 47 | 48 | 49 | 50 | Converts the given item to Boolean true or false. 51 | 52 | Handles strings 'True' and 'False' (case-insensitive) as expected, otherwise returns item's truth value using Python's 'bool' method. For more information about truth values, see http://docs.python.org/lib/truth.html. 53 | 54 | item 55 | 56 | 57 | 58 | Converts the given item to an integer number. 59 | 60 | item 61 | 62 | 63 | 64 | Converts the given item to a floating point number. 65 | 66 | item 67 | 68 | 69 | 70 | Converts the given item to a Unicode string. 71 | 72 | Uses '__unicode__' or '__str__' method with Python objects and 'toString' with Java objects. 73 | 74 | item 75 | 76 | 77 | 78 | Returns a list containing given items. 79 | 80 | The returned list can be assigned both to ${scalar} and @{list} variables. The earlier can be used e.g. with Java keywords expecting an array as an argument. 81 | 82 | Examples: 83 | | @{list} = | Create List | a | b | c | 84 | | ${scalar} = | Create List | a | b | c | 85 | | ${ints} = | Create List | ${1} | ${2} | ${3} | 86 | 87 | *items 88 | 89 | 90 | 91 | Evaluates the given expression in Python and returns the results. 92 | 93 | `modules` argument can be used to specify a comma separated list of Python modules to be imported and added to the namespace of the evaluated `expression`. 94 | 95 | Examples (expecting ${result} is 3.14): 96 | | ${status} = | Evaluate | 0 < ${result} < 10 | 97 | | ${down} = | Evaluate | int(${result}) | 98 | | ${up} = | Evaluate | math.ceil(${result}) | math | 99 | | ${random} = | Evaluate | random.randint(0, sys.maxint) | random,sys | 100 | => 101 | - ${status} = True 102 | - ${down} = 3 103 | - ${up} = 4.0 104 | - ${random} = <random integer> 105 | 106 | Notice that instead of creating complicated expressions, it is recommended to move the logic into a test library. 107 | 108 | expression 109 | modules=None 110 | 111 | 112 | 113 | Fails the test immediately with the given (optional) message. 114 | 115 | msg=None 116 | 117 | 118 | 119 | Returns and logs how many times `item2` is found from `item1`. 120 | 121 | This keyword works with Python strings and lists and all objects that either have 'count' method or can be converted to Python lists. 122 | 123 | Example: 124 | | ${count} = | Get Count | ${some item} | interesting value | 125 | | Should Be True | 5 < ${count} < 10 | 126 | 127 | item1 128 | item2 129 | 130 | 131 | 132 | Returns and logs the length of the given item. 133 | 134 | The keyword first tries to get the length with the Python function 'len', which calls the item's '__len__' method internally. If that fails, the keyword tries to call the item's 'length' and 'size' methods directly. The final attempt is trying to get the value of the item's 'length' attribute. If all these attempts are unsuccessful, the keyword fails. 135 | 136 | item 137 | 138 | 139 | 140 | Returns the current time in the requested format. 141 | 142 | How time is returned is determined based on the given `format` string as follows. Note that all checks are case-insensitive. 143 | 144 | - If `format` contains the word 'epoch', the time is returned in seconds after the UNIX epoch. The return value is always an integer. 145 | 146 | - If `format` contains any of the words 'year', 'month', 'day', 'hour', 'min', or 'sec', only the selected parts are returned. The order of the returned parts is always the one in the previous sentence and the order of words in `format` is not significant. The parts are returned as zero-padded strings (e.g. May -> '05'). 147 | 148 | - Otherwise (and by default) the time is returned as a timestamp string in the format '2006-02-24 15:08:31'. 149 | 150 | Examples (expecting the current time is 2006-03-29 15:06:21): 151 | | ${time} = | Get Time | | | | 152 | | ${secs} = | Get Time | epoch | | | 153 | | ${year} = | Get Time | return year | | | 154 | | ${yyyy} | ${mm} | ${dd} = | Get Time | year,month,day | 155 | | @{time} = | Get Time | year month day hour min sec | | | 156 | | ${y} | ${s} = | Get Time | seconds and year | | 157 | => 158 | - ${time} = '2006-03-29 15:06:21' 159 | - ${secs} = 1143637581 160 | - ${year} = '2006' 161 | - ${yyyy} = '2006', ${mm} = '03', ${dd} = '29' 162 | - @{time} = ['2006', '03', '29', '15', '06', '21'] 163 | - ${y} = '2006' 164 | - ${s} = '21' 165 | 166 | format=timestamp 167 | 168 | 169 | 170 | *DEPRECATED* Use `Get Lines Matching XXX` keywords from `String` library instead. This keyword will be removed in Robot Framework 2.2. 171 | 172 | text 173 | pattern 174 | pattern_type=literal string 175 | 176 | 177 | 178 | Imports a library with the given name and optional arguments. 179 | 180 | This functionality allows dynamic importing of libraries while tests are running. That may be necessary, if the library itself is dynamic and not yet available when test data is processed. In a normal case, libraries should be imported using the Library setting in the Setting table. 181 | 182 | This keyword supports importing libraries both using library names and physical paths. When path are used, they must be given in absolute format. Starting from 2.0.2 version, forward slashes can be used as path separators in all operating systems. It is possible to use arguments as well as to give a custom name with 'WITH NAME' syntax. For more information about importing libraries, see Robot Framework User Guide. 183 | 184 | Examples: 185 | | Import Library | MyLibrary | 186 | | Import Library | ${CURDIR}/Library.py | some | args | 187 | | Import Library | ${CURDIR}/../libs/Lib.java | arg | WITH NAME | JavaLib | 188 | 189 | name 190 | *args 191 | 192 | 193 | 194 | Imports a variable file with the given path and optional arguments. 195 | 196 | Variables imported with this keyword are set into the test suite scope similarly when importing them in the Setting table using the Variables setting. These variables override possible existing variables with the same names and this functionality can thus be used to import new variables, e.g. for each test in a test suite. 197 | 198 | The given path must be absolute. Starting from 2.0.2 version, forward slashes can be used as path separator regardless the operating system, but on earlier versions ${/} variable must be used instead. 199 | 200 | Examples: 201 | | Import Variables | ${CURDIR}/variables.py | | | 202 | | Import Variables | ${CURDIR}/../vars/env.py | arg1 | arg2 | 203 | 204 | path 205 | *args 206 | 207 | 208 | 209 | Verifies that the length of the given item is correct. 210 | 211 | The length of the item is got using the `Get Length` keyword. The default error message can be overridden with the `msg` argument. 212 | 213 | item 214 | length 215 | msg=None 216 | 217 | 218 | 219 | Logs the given message with the given level. 220 | 221 | Valid levels are TRACE, DEBUG, INFO (default), HTML and WARN. 222 | 223 | HTML level is special because it writes the message into the log file without escaping HTML code from it. For example logging a message like '<img src="image.png">' with that level creates an image, but with other levels you see just that string. Logging HTML messages should be used with care, because invalid messages can corrupt the whole log file. The actual log level used for HTML messages is INFO. 224 | 225 | message 226 | level=INFO 227 | 228 | 229 | 230 | Logs the given messages as separate entries with the INFO level. 231 | 232 | *messages 233 | 234 | 235 | 236 | Logs all variables in the current scope with given log level. 237 | 238 | level=INFO 239 | 240 | 241 | 242 | Does absolutely nothing. 243 | 244 | 245 | 246 | 247 | Returns each argument string escaped for use as a regular expression. 248 | 249 | This keyword can be used to escape strings to be used with `Should Match Regexp` and `Should Not Match Regexp` keywords. 250 | 251 | Escaping is done with Python's re.escape() function. 252 | 253 | Examples: 254 | | ${escaped} = | Regexp Escape | ${original} | 255 | | @{strings} = | Regexp Escape | @{strings} | 256 | 257 | *patterns 258 | 259 | 260 | 261 | Removes given `tags` from the current test or all tests in a suite. 262 | 263 | Tags can be given exactly or using a pattern where '*' matches anything and '?' matches one character. 264 | 265 | This keyword can affect either one test case or all test cases in a test suite similarly as `Set Tags` keyword. 266 | 267 | Example: 268 | | Remove Tags | mytag | something-* | ?ython | 269 | 270 | New in Robot Framework version 2.0.3. 271 | 272 | *tags 273 | 274 | 275 | 276 | Executes the specified keyword multiple times. 277 | 278 | `name` and `args` define the keyword that is executed similarly as with `Run Keyword`, and `times` specifies how many the keyword should be executed. `times` can be given as an integer or as a string that can be converted to an integer. It can also have postfix 'times' or 'x' (case and space insensitive) to make the expression easier to read. 279 | 280 | If `times` is zero or negative, the keyword is not executed at all. This keyword fails immediately if any of the execution rounds fails. 281 | 282 | Examples: 283 | | Repeat Keyword | 5 times | Goto Previous Page | 284 | | Repeat Keyword | ${var} | Some Keyword | arg1 | arg2 | 285 | 286 | times 287 | name 288 | *args 289 | 290 | 291 | 292 | Replaces variables in the given text with their current values. 293 | 294 | If the text contains undefined variables, this keyword fails. 295 | 296 | Example: 297 | 298 | The file 'template.txt' contains 'Hello ${NAME}!' and variable '${NAME}' has the value 'Robot'. 299 | 300 | | ${template} = | Get File | ${CURDIR}/template.txt | 301 | | ${message} = | Replace Variables | ${template} | 302 | | Should Be Equal | ${message} | Hello Robot! | 303 | 304 | If the given `text` contains only a single variable, its value is returned as-is. Otherwise, and always with Robot Framework 2.0.3 and earlier, this keyword returns a string. 305 | 306 | text 307 | 308 | 309 | 310 | Executes the given keyword with the given arguments. 311 | 312 | Because the name of the keyword to execute is given as an argument, it can be a variable and thus set dynamically, e.g. from a return value of another keyword or from the command line. 313 | 314 | name 315 | *args 316 | 317 | 318 | 319 | Runs the keyword and checks that the expected error occurred. 320 | 321 | The expected error must be given in the same format as in Robot Framework reports. It can be a pattern containing characters '?', which matches to any single character and '*', which matches to any number of any characters. `name` and `*args` have same semantics as with `Run Keyword`. 322 | 323 | If the expected error occurs, the error message is returned and it can be further processed/tested, if needed. If there is no error, or the error does not match the expected error, this keyword fails. 324 | 325 | Examples: 326 | | Run Keyword And Expect Error | My error | Some Keyword | arg1 | arg2 | 327 | | ${msg} = | Run Keyword And Expect Error | * | My KW | 328 | | Should Start With | ${msg} | Once upon a time in | 329 | 330 | expected_error 331 | name 332 | *args 333 | 334 | 335 | 336 | Runs the given keyword with the given arguments and ignores possible error. 337 | 338 | This keyword returns two values, so that the first is either 'PASS' or 'FAIL', depending on the status of the executed keyword. The second value is either the return value of the keyword or the received error message. 339 | 340 | The keyword name and arguments work as in `Run Keyword`. See `Run Keyword If` for a usage example. 341 | 342 | name 343 | *args 344 | 345 | 346 | 347 | Runs the given keyword with the given arguments, if `condition` is true. 348 | 349 | The given `condition` is evaluated similarly as with `Should Be True` keyword, and `name` and `*args` have same semantics as with `Run Keyword`. 350 | 351 | Example, a simple if/else construct: 352 | | ${status} | ${value} = | Run Keyword And Ignore Error | My Keyword | 353 | | Run Keyword If | '${status}' == 'PASS' | Some Action | 354 | | Run Keyword Unless | '${status}' == 'PASS' | Another Action | 355 | 356 | In this example, only either 'Some Action' or 'Another Action' is executed, based on the status of 'My Keyword'. 357 | 358 | condition 359 | name 360 | *args 361 | 362 | 363 | 364 | Runs the given keyword with the given arguments, if all critical tests passed. 365 | 366 | This keyword can only be used in suite teardown. Trying to use it in any other place will result in an error. 367 | 368 | Otherwise, this keyword works exactly like `Run Keyword`, see its documentation for more details. 369 | 370 | name 371 | *args 372 | 373 | 374 | 375 | Runs the given keyword with the given arguments, if all tests passed. 376 | 377 | This keyword can only be used in a suite teardown. Trying to use it anywhere else results in an error. 378 | 379 | Otherwise, this keyword works exactly like `Run Keyword`, see its documentation for more details. 380 | 381 | name 382 | *args 383 | 384 | 385 | 386 | Runs the given keyword with the given arguments, if any critical tests failed. 387 | 388 | This keyword can only be used in a suite teardown. Trying to use it anywhere else results in an error. 389 | 390 | Otherwise, this keyword works exactly like `Run Keyword`, see its documentation for more details. 391 | 392 | name 393 | *args 394 | 395 | 396 | 397 | Runs the given keyword with the given arguments, if one or more tests failed. 398 | 399 | This keyword can only be used in a suite teardown. Trying to use it anywhere else results in an error. 400 | 401 | Otherwise, this keyword works exactly like `Run Keyword`, see its documentation for more details. 402 | 403 | name 404 | *args 405 | 406 | 407 | 408 | Runs the given keyword with the given arguments, if the test failed. 409 | 410 | This keyword can only be used in a test teardown. Trying to use it anywhere else results in an error. 411 | 412 | Otherwise, this keyword works exactly like `Run Keyword`, see its documentation for more details. 413 | 414 | name 415 | *args 416 | 417 | 418 | 419 | Runs the given keyword with the given arguments, if the test passed. 420 | 421 | This keyword can only be used in a test teardown. Trying to use it anywhere else results in an error. 422 | 423 | Otherwise, this keyword works exactly like `Run Keyword`, see its documentation for more details. 424 | 425 | name 426 | *args 427 | 428 | 429 | 430 | Runs the given keyword with the given arguments, if `condition` is false. 431 | 432 | See `Run Keyword If` for more information and an example. 433 | 434 | condition 435 | name 436 | *args 437 | 438 | 439 | 440 | Makes a variable available globally in all tests and suites. 441 | 442 | Variables set with this keyword are globally available in all test cases and suites executed after setting them. Setting variables with this keyword thus has the same effect as creating from the command line using the options '--variable' or '--variablefile'. Because this keyword can change variables everywhere, it should be used with care. 443 | 444 | See `Set Suite Variable` for more information and examples. 445 | 446 | name 447 | *values 448 | 449 | 450 | 451 | Sets the resolution order to use when a name matches multiple keywords. 452 | 453 | The library search order is used to resolve conflicts when a keyword name in the test data matches multiple keywords. The first library containing the keyword is selected and that keyword implementation used. If keyword is not found from any library, or the library search order is not set, executing the specified keyword fails. 454 | 455 | When this keyword is used, there is no need to use the long `LibraryName.Keyword Name` notation. For example, instead of having 456 | 457 | | MyLibrary.Keyword | arg | 458 | | MyLibrary.Another Keyword | 459 | | MyLibrary.Keyword | xxx | 460 | 461 | you can have 462 | 463 | | Set Library Search Order | MyLibrary | 464 | | Keyword | arg | 465 | | Another Keyword | 466 | | Keyword | xxx | 467 | 468 | The library search order is valid only in the suite where this keyword is used in. The old order is returned and can be used to reset the search order later. 469 | 470 | *libraries 471 | 472 | 473 | 474 | Sets the log threshold to the specified level and returns the old level. 475 | 476 | Messages below the level will not logged. The default logging level is INFO, but it can be overridden with the command line option '--loglevel'. 477 | 478 | The available levels: TRACE, DEBUG, INFO (default), WARN and NONE (no logging). 479 | 480 | level 481 | 482 | 483 | 484 | Makes a variable available everywhere within the scope of the current suite. 485 | 486 | Variables set with this keyword are available everywhere within the scope of the currently executed test suite. Setting variables with this keyword thus has the same effect as creating them using the Variable table in the test data file or importing them from variable files. Other test suites, including possible child test suites, will not see variables set with this keyword. 487 | 488 | The name of the variable can be given either as a normal variable name (e.g. ${NAME}) or in escaped format (e.g. \${NAME}). Notice that the former works only in Robot Framework 2.1 and newer. 489 | 490 | If a variable already exists within the new scope, its value will be overwritten. Otherwise a new variable is created. If a variable already exists within the current scope, the value can be left empty and the variable within the new scope gets the value within the current scope. 491 | 492 | Examples: 493 | | Set Suite Variable | ${GREET} | Hello, world! | 494 | | ${ID} = | Get ID | 495 | | Set Suite Variable | ${ID} | 496 | 497 | See also `Set Global Variable` and `Set Test Variable`. 498 | 499 | name 500 | *values 501 | 502 | 503 | 504 | Adds given `tags` for the current test or all tests in a suite. 505 | 506 | When this keyword is used inside a test case, that test gets the specified tags and other tests are not affected. 507 | 508 | If this keyword is used in a suite setup, all test cases in that suite, recursively, gets the given tags. It is a failure to use this keyword in a suite teardown. 509 | 510 | See `Remove Tags` for another keyword to modify tags at test execution time. 511 | 512 | New in Robot Framework version 2.0.3. 513 | 514 | *tags 515 | 516 | 517 | 518 | Makes a variable available everywhere within the scope of the current test. 519 | 520 | Variables set with this keyword are available everywhere within the scope of the currently executed test case. For example, if you set a variable in a user keyword, it is available both in the test case level and also in all other user keywords used in the current test. Other test cases will not see variables set with this keyword. 521 | 522 | See `Set Suite Variable` for more information and examples. 523 | 524 | name 525 | *values 526 | 527 | 528 | 529 | Returns the given argument which can then be assigned to a variable. 530 | 531 | This keyword is mainly used for setting scalar variables. Additionally it can be used for converting a scalar variable containing a list to a list variable or to multiple scalar variables. 532 | 533 | Using this keyword with more (or less) than one value has been deprecated in Robot Framework 2.1 and that usage will be removed in 2.2 version. 534 | 535 | Examples: 536 | | ${hi} = | Set Variable | Hello, world! | 537 | | ${hi2} = | Set Variable | I said: ${hi} | 538 | | @{list} = | Set Variable | ${list with some items} | 539 | | ${item1} | ${item2} = | Set Variable | ${list with 2 items} | 540 | 541 | Variables created with this keyword are available only in the scope where they are created. See `Set Global Variable`, `Set Test Variable` and `Set Suite Variable` for information on how to set variables so that they are available also in a larger scope. 542 | 543 | *values 544 | 545 | 546 | 547 | Sets variable based on the given condition. 548 | 549 | The basic usage is giving a condition and two values. The given condition is first evaluated the same way as with the `Should Be True` keyword. If the condition is true, then the first value is returned, and otherwise the second value is returned. The second value can also be omitted, in which case it has a default value None. This usage is illustrated in the examples below, where ${rc} is assumed to be zero. 550 | 551 | | ${var1} = | Set Variable If | ${rc} == 0 | zero | nonzero | 552 | | ${var2} = | Set Variable If | ${rc} > 0 | value1 | value2 | 553 | | ${var3} = | Set Variable If | ${rc} > 0 | whatever | | 554 | => 555 | - ${var1} = 'zero' 556 | - ${var2} = 'value2' 557 | - ${var3} = None 558 | 559 | Starting from Robot Framework 2.0.2 it is also possible to have 'Else If' support by replacing the second value with another condition, and having two new values after it. If the first condition is not true, the second is evaluated and one of the values after it is returned based on its truth value. This can be continued by adding more conditions without a limit. 560 | 561 | | ${var} = | Set Variable If | ${rc} == 0 | zero | 562 | | ... | ${rc} > 0 | greater than zero | less then zero | 563 | | | 564 | | ${var} = | Set Variable If | 565 | | ... | ${rc} == 0 | zero | 566 | | ... | ${rc} == 1 | one | 567 | | ... | ${rc} == 2 | two | 568 | | ... | ${rc} > 2 | greater than two | 569 | | ... | ${rc} < 0 | less than zero | 570 | 571 | condition 572 | *values 573 | 574 | 575 | 576 | Verifies that the given item is empty. 577 | 578 | The length of the item is got using the `Get Length` keyword. The default error message can be overridden with the `msg` argument. 579 | 580 | item 581 | msg=None 582 | 583 | 584 | 585 | Fails if the given objects are unequal. 586 | 587 | - If `msg` is not given, the error message is 'first != second'. 588 | - If `msg` is given and `values` is either Boolean False or the string 'False' or 'No Values', the error message is simply `msg`. 589 | - Otherwise the error message is '`msg`: `first` != `second`'. 590 | 591 | first 592 | second 593 | msg=None 594 | values=True 595 | 596 | 597 | 598 | Fails if objects are unequal after converting them to integers. 599 | 600 | See `Should Be Equal` for an explanation on how to override the default error message with `msg` and `values`. 601 | 602 | first 603 | second 604 | msg=None 605 | values=True 606 | 607 | 608 | 609 | Fails if objects are unequal after converting them to real numbers. 610 | 611 | Starting from Robot Framework 2.0.2, the check for equality is done using six decimal places. 612 | 613 | See `Should Be Equal` for an explanation on how to override the default error message with `msg` and `values`. 614 | 615 | first 616 | second 617 | msg=None 618 | values=True 619 | 620 | 621 | 622 | Fails if objects are unequal after converting them to strings. 623 | 624 | See `Should Be Equal` for an explanation on how to override the default error message with `msg` and `values`. 625 | 626 | first 627 | second 628 | msg=None 629 | values=True 630 | 631 | 632 | 633 | Fails if the given condition is not true. 634 | 635 | If `condition` is a string (e.g. '${rc} < 10'), it is evaluated as a Python expression using the built-in 'eval' function and the keyword status is decided based on the result. If a non-string item is given, the status is got directly from its truth value as explained at http://docs.python.org/lib/truth.html. 636 | 637 | The default error message ('<condition> should be true') is not very informative, but it can be overridden with the `msg` argument. 638 | 639 | Examples: 640 | | Should Be True | ${rc} < 10 | 641 | | Should Be True | '${status}' == 'PASS' | # Strings must be quoted | 642 | | Should Be True | ${number} | # Passes if ${number} is not zero | 643 | | Should Be True | ${list} | # Passes if ${list} is not empty | 644 | 645 | condition 646 | msg=None 647 | 648 | 649 | 650 | Fails if `item1` does not contain `item2` one or more times. 651 | 652 | Works with strings, lists, and anything that supports Python's 'in' keyword. See `Should Be Equal` for an explanation on how to override the default error message with `msg` and `values`. 653 | 654 | Examples: 655 | | Should Contain | ${output} | PASS | 656 | | Should Contain | ${some_list} | value | 657 | 658 | item1 659 | item2 660 | msg=None 661 | values=True 662 | 663 | 664 | 665 | Fails if `item1` does not contain `item2` `count` times. 666 | 667 | Works with strings, lists and all objects that `Get Count` works with. The default error message can be overridden with `msg` and the actual count is always logged. 668 | 669 | Examples: 670 | | Should Contain X Times | ${output} | hello | 2 | 671 | | Should Contain X Times | ${some list} | value | 3 | 672 | 673 | item1 674 | item2 675 | count 676 | msg=None 677 | 678 | 679 | 680 | Fails if the string `str1` does not end with the string `str2`. 681 | 682 | See `Should Be Equal` for an explanation on how to override the default error message with `msg` and `values`. 683 | 684 | str1 685 | str2 686 | msg=None 687 | values=True 688 | 689 | 690 | 691 | Fails unless the given `string` matches the given `pattern`. 692 | 693 | Pattern matching is similar as matching files in a shell, and it is always case-sensitive. In the pattern, '*' matches to anything and '?' matches to any single character. 694 | 695 | See `Should Be Equal` for an explanation on how to override the default error message with `msg` and `values`. 696 | 697 | string 698 | pattern 699 | msg=None 700 | values=True 701 | 702 | 703 | 704 | Fails if `string` does not match `pattern` as a regular expression. 705 | 706 | Regular expression check is done using the Python 're' module, which has a pattern syntax derived from Perl, and thus also very similar to the one in Java. See the following documents for more details about regular expressions in general and Python implementation in particular. 707 | 708 | * http://docs.python.org/lib/module-re.html 709 | * http://www.amk.ca/python/howto/regex/ 710 | 711 | Things to note about the regexp syntax in Robot Framework test data: 712 | 713 | 1) Backslash is an escape character in the test data, and possible backslashes in the pattern must thus be escaped with another backslash (e.g. '\\d\\w+'). 714 | 715 | 2) Strings that may contain special characters, but should be handled as literal strings, can be escaped with the `Regexp Escape` keyword. 716 | 717 | 3) The given pattern does not need to match the whole string. For example, the pattern 'ello' matches the string 'Hello world!'. If a full match is needed, the '^' and '$' characters can be used to denote the beginning and end of the string, respectively. For example, '^ello$' only matches the exact string 'ello'. 718 | 719 | 4) Possible flags altering how the expression is parsed (e.g. re.IGNORECASE, re.MULTILINE) can be set by prefixing the pattern with the '(?iLmsux)' group (e.g. '(?im)pattern'). The available flags are 'IGNORECASE': 'i', 'MULTILINE': 'm', 'DOTALL': 's', 'VERBOSE': 'x', 'UNICODE': 'u', and 'LOCALE': 'L'. 720 | 721 | If this keyword passes, it returns the portion of the string that matched the pattern. Additionally, the possible captured groups are returned. 722 | 723 | See the `Should Be Equal` keyword for an explanation on how to override the default error message with the `msg` and `values` arguments. 724 | 725 | Examples: 726 | | Should Match Regexp | ${output} | \\d{6} | # Output contains six numbers | 727 | | Should Match Regexp | ${output} | ^\\d{6}$ | # Six numbers and nothing more | 728 | | ${ret} = | Should Match Regexp | Foo: 42 | (?i)foo: \\d+ | 729 | | ${match} | ${group1} | ${group2} = | 730 | | ... | Should Match Regexp | Bar: 43 | (Foo|Bar): (\\d+) | 731 | => 732 | - ${ret} = 'Foo: 42' 733 | - ${match} = 'Bar: 43' 734 | - ${group1} = 'Bar' 735 | - ${group2} = '43' 736 | 737 | string 738 | pattern 739 | msg=None 740 | values=True 741 | 742 | 743 | 744 | Verifies that the given item is not empty. 745 | 746 | The length of the item is got using the `Get Length` keyword. The default error message can be overridden with the `msg` argument. 747 | 748 | item 749 | msg=None 750 | 751 | 752 | 753 | Fails if the given objects are equal. 754 | 755 | See `Should Be Equal` for an explanation on how to override the default error message with `msg` and `values`. 756 | 757 | first 758 | second 759 | msg=None 760 | values=True 761 | 762 | 763 | 764 | Fails if objects are equal after converting them to integers. 765 | 766 | See `Should Be Equal` for an explanation on how to override the default error message with `msg` and `values`. 767 | 768 | first 769 | second 770 | msg=None 771 | values=True 772 | 773 | 774 | 775 | Fails if objects are equal after converting them to real numbers. 776 | 777 | Starting from Robot Framework 2.0.2, the check for equality is done using six decimal places. 778 | 779 | See `Should Be Equal` for an explanation on how to override the default error message with `msg` and `values`. 780 | 781 | first 782 | second 783 | msg=None 784 | values=True 785 | 786 | 787 | 788 | Fails if objects are equal after converting them to strings. 789 | 790 | See `Should Be Equal` for an explanation on how to override the default error message with `msg` and `values`. 791 | 792 | first 793 | second 794 | msg=None 795 | values=True 796 | 797 | 798 | 799 | Fails if the given condition is true. 800 | 801 | See `Should Be True` for details about how `condition` is evaluated and how `msg` can be used to override the default error message. 802 | 803 | condition 804 | msg=None 805 | 806 | 807 | 808 | Fails if `item1` contains `item2` one or more times. 809 | 810 | Works with strings, lists, and anything that supports Python's 'in' keyword. See `Should Be Equal` for an explanation on how to override the default error message with `msg` and `values`. 811 | 812 | Examples: 813 | | Should Not Contain | ${output} | FAILED | 814 | | Should Not Contain | ${some_list} | value | 815 | 816 | item1 817 | item2 818 | msg=None 819 | values=True 820 | 821 | 822 | 823 | Fails if the string `str1` ends with the string `str2`. 824 | 825 | See `Should Be Equal` for an explanation on how to override the default error message with `msg` and `values`. 826 | 827 | str1 828 | str2 829 | msg=None 830 | values=True 831 | 832 | 833 | 834 | Fails if the given `string` matches the given `pattern`. 835 | 836 | Pattern matching is similar as matching files in a shell, and it is always case-sensitive. In the pattern '*' matches to anything and '?' matches to any single character. 837 | 838 | See `Should Be Equal` for an explanation on how to override the default error message with `msg` and `values`. 839 | 840 | string 841 | pattern 842 | msg=None 843 | values=True 844 | 845 | 846 | 847 | Fails if `string` matches `pattern` as a regular expression. 848 | 849 | See `Should Match Regexp` for more information about arguments. 850 | 851 | string 852 | pattern 853 | msg=None 854 | values=True 855 | 856 | 857 | 858 | Fails if the string `str1` starts with the string `str2`. 859 | 860 | See `Should Be Equal` for an explanation on how to override the default error message with `msg` and `values`. 861 | 862 | str1 863 | str2 864 | msg=None 865 | values=True 866 | 867 | 868 | 869 | Fails if the string `str1` does not start with the string `str2`. 870 | 871 | See `Should Be Equal` for an explanation on how to override the default error message with `msg` and `values`. 872 | 873 | str1 874 | str2 875 | msg=None 876 | values=True 877 | 878 | 879 | 880 | Pauses the test executed for the given time. 881 | 882 | `time` may be either a number or a time string. Time strings are in a format such as '1 day 2 hours 3 minutes 4 seconds 5milliseconds' or '1d 2h 3m 4s 5ms', and they are fully explained in an appendix of Robot Framework User Guide. Optional `reason` can be used to explain why sleeping is necessary. Both the time slept and the reason are logged. 883 | 884 | Examples: 885 | | Sleep | 42 | 886 | | Sleep | 1.5 | 887 | | Sleep | 2 minutes 10 seconds | 888 | | Sleep | 10s | Wait for a reply | 889 | 890 | time 891 | reason=None 892 | 893 | 894 | 895 | *DEPRECATED* Use `Log` keyword with WARN level instead. This keyword will be removed in Robot Framework 2.2. 896 | 897 | message 898 | level=INFO 899 | 900 | 901 | 902 | Fails unless the given variable exists within the current scope. 903 | 904 | The name of the variable can be given either as a normal variable name (e.g. ${NAME}) or in escaped format (e.g. \${NAME}). Notice that the former works only in Robot Framework 2.1 and newer. 905 | 906 | The default error message can be overridden with the `msg` argument. 907 | 908 | name 909 | msg=None 910 | 911 | 912 | 913 | Fails if the given variable exists within the current scope. 914 | 915 | The name of the variable can be given either as a normal variable name (e.g. ${NAME}) or in escaped format (e.g. \${NAME}). Notice that the former works only in Robot Framework 2.1 and newer. 916 | 917 | The default error message can be overridden with the `msg` argument. 918 | 919 | name 920 | msg=None 921 | 922 | 923 | 924 | Waits until the specified keyword succeeds or the given timeout expires. 925 | 926 | `name` and `args` define the keyword that is executed similarly as with `Run Keyword`. If the specified keyword does not succeed within `timeout`, this keyword fails. `retry_interval` is the time to wait before trying to run the keyword again after the previous run has failed. 927 | 928 | Both `timeout` and `retry_interval` must be given in Robot Framework's time format (e.g. '1 minute', '2 min 3 s', '4.5'). 929 | 930 | Example: 931 | | Wait Until Keyword Succeeds | 2 min | 5 sec | My keyword | arg1 | arg2 | 932 | 933 | timeout 934 | retry_interval 935 | name 936 | *args 937 | 938 | 939 | 940 | -------------------------------------------------------------------------------- /atest/testdata/xmls/ExampleLibrary_version_1.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 1 4 | First Library Doc 5 | 6 | Library can be imported with optional arguments. 7 | 8 | timeout=3.0 9 | is_regexp=False 10 | 11 | 12 | 13 | First KW doc 14 | 15 | first 16 | second=default 17 | *args 18 | 19 | 20 | 21 | Second KW doc 22 | 23 | some 24 | 25 | 26 | 27 | -------------------------------------------------------------------------------- /atest/testdata/xmls/ExampleLibrary_version_2.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 2 4 | Updated Library Doc 5 | 6 | Library can be imported with optional arguments. 7 | 8 | timeout=3.0 9 | is_regexp=New Value 10 | 11 | 12 | 13 | Updated KW doc 14 | 15 | first 16 | second=default updated 17 | *args 18 | 19 | 20 | 21 | Second KW doc 22 | 23 | some 24 | 25 | 26 | 27 | Third KW doc 28 | 29 | third 30 | 31 | 32 | 33 | -------------------------------------------------------------------------------- /atest/testdata/xmls/SeleniumLibrary.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 2.2 4 | SeleniumLibrary is a web testing library for Robot Test Automation Framework. It uses the Selenium Remote Control tool internally to control a web browser. See http://selenium-rc.openqa.org/ for more information on Selenium tool. 5 | 6 | SeleniumLibrary runs tests in a real browser instance. It should work in most modern browsers and can be used with both Python and Jython interpreters. 7 | 8 | To test sites over HTTPS, special browser launchers *chrome and *iehta must be used. See `Open Browser` keyword for more details. 9 | 10 | *Before running the tests* 11 | 12 | Prior to running test cases using SeleniumLibrary, the Selenium server must be started. This can be done using keyword `Start Selenium Server` or from the command line by using command: `java -jar /path/to/selenium-server.jar`. The Selenium server is included in the SeleniumLibrary distribution and can be found under `[PythonLibs]/site-packages/SeleniumLibrary/lib`. Additionally, `Open Browser` keyword must be used in order to open browser in the desired location before any other keyword from the library may be used. 13 | 14 | *Locating elements* 15 | 16 | To do operations on elements, elements have to be identified. The most common way of doing this is by searching the values of key attributes of an element type. All keywords that operate on elements document the key attributes for that element type. If the given `locator` argument matches the value of any key attribute, the element is found. 17 | 18 | It is also possible to give an arbitrary XPath or DOM expression as `locator`. In this case, the expression must be prefixed with either 'xpath=' or 'dom=' 19 | 20 | Examples: 21 | | Click Link | my link | # Mathces if either link text or value of attribute 'id', 'name' or 'href' equals 'my link' | 22 | | Select Checkbox | xpath=//table[0]/input[name=my_checkbox] | # Using XPath | 23 | | Click Image | dom=document.images[56] | # Using a DOM expression | 24 | 25 | *Handling page load events* 26 | 27 | Some keywords that may cause a page to load take an additional argument `dont_wait` that is used to determine whether a new page is expected to load or not. By default, a page load is expected to happen whenever a link or image is clicked, or a form submitted. If a page load does not happen (if the link only executes some Javascript, for example), a non-empty value must be given to `dont_wait` argument. 28 | 29 | There are also some keywords that may cause a page to load but by default we expect them not to. In these case, the keywords have an optional `wait` argument, and providing a non-empty value for it will cause the keyword to wait for a page load. 30 | 31 | Examples: 32 | | Click Link | link text | | | # A page is expected to load. | 33 | | Click Link | another link | don't wait | | # A page is not expected to load. | 34 | | Select Radio Button | group1 | value1 | | # A page is not expected to load. | 35 | | Select Radio Button | group2 | value2 | and wait | # A page is expected to load. | 36 | 37 | SeleniumLibrary can be imported with optional arguments. 38 | 39 | `timeout` is the default timeout used to wait for page load actions. It can be later set with `Set Timeout` 40 | 41 | `host` and `port` are used to connect to Selenium server. Browsers opened with this SeleniumLibrary instance will be attached to that server. Note that the Selenium server must be running before `Open Browser`-keyword can be used. Selenium server can be started with keyword `Start Selenium Server`. 42 | 43 | timeout=5.0 44 | server_host=localhost 45 | server_port=4444 46 | 47 | 48 | 49 | Verifies an alert is present and dismisses it. 50 | 51 | If `text` is a non-empty string, then it is also verified that the message of the alert equals to `text`. 52 | 53 | Will fail if no alert is present. Note that when running tests with selenium, the alerts will not be visible in the browser. Nevertheless, following keywords will fail unless the alert is dismissed by this keyword or by `Get Alert Message`. 54 | 55 | text= 56 | 57 | 58 | 59 | Captures a screenshot and embeds it in Robot Framework log file. 60 | 61 | Given path must be relative to Robot Framework output directory, otherwise the embedded image is not shown in the log file. If path is not given, file with name similar to 'selenium-image-x.png' is created directly under the output directory. 62 | 63 | path=None 64 | 65 | 66 | 67 | Verifies checkbox identified by `locator` is selected/checked. 68 | 69 | Key attributes for checkboxes are `id` and `name`. See `introduction` for details about locating elements. 70 | 71 | locator 72 | 73 | 74 | 75 | Verifies checkbox identified by `locator` is not selected/checked. 76 | 77 | Key attributes for checkboxes are `id` and `name`. See `introduction` for details about locating elements. 78 | 79 | locator 80 | 81 | 82 | 83 | Cancel will be selected the next time `Confirm Action` is used. 84 | 85 | 86 | 87 | 88 | Inputs the `file_path` into file input field found by `identifier` 89 | 90 | `file_path` must be absolute and point to an existing file. 91 | 92 | This keyword only works with *chrome browser launcher. See `Open Browser` for details. 93 | 94 | identifier 95 | file_path 96 | 97 | 98 | 99 | Clicks a button identified by `locator`. 100 | 101 | Key attributes for buttons are `id`, `name` and `value`. See `introduction` for details about locating elements and about meaning of `dont_wait` argument. 102 | 103 | locator 104 | dont_wait= 105 | 106 | 107 | 108 | Click element identified by `locator`. 109 | 110 | Key attributes for arbitrary elements are `id` and `name`. See `introduction` for details about locating elements and about meaning of `dont_wait` argument. 111 | 112 | locator 113 | dont_wait= 114 | 115 | 116 | 117 | Clicks an image found by `locator`. 118 | 119 | Key attributes for images are `id`, `src` and `alt`. See `introduction` for details about locating elements and about meaning of `dont_wait` argument. 120 | 121 | locator 122 | dont_wait= 123 | 124 | 125 | 126 | Clicks a link identified by locator. 127 | 128 | Key attributes for links are `id`, `name`, `href` and link text. See `introduction` for details about locating elements and about meaning of `dont_wait` argument. 129 | 130 | locator 131 | dont_wait= 132 | 133 | 134 | 135 | Closes all open browsers and empties the connection cache. 136 | 137 | After this keyword new indexes get from Open Browser keyword are reset to 1. 138 | 139 | This keyword should be used in test or suite teardown to make sure all browsers are closed. 140 | 141 | 142 | 143 | 144 | Closes the current browser. 145 | 146 | 147 | 148 | 149 | Closes currently opened pop-up window. 150 | 151 | 152 | 153 | 154 | Dismisses currently shown confirmation dialog. 155 | 156 | By default, this keyword chooses 'Ok' option from the dialog. If 'cancel' needs to be chosen, keyword `Choose Cancel On Next Confirmation` must be called before the action that causes the confirmation dialog to be shown. 157 | 158 | Examples: 159 | 160 | | Click Button | Send | # Shows a confirmation dialog | 161 | | Choose Confirm | | # Chooses Ok | 162 | | | | | 163 | | Choose Cancel On Next Confirmation | | | 164 | | Click Button | Send | # Shows a confirmation dialog | 165 | | Choose Confirm | | # Chooses Cancel | 166 | 167 | 168 | 169 | 170 | Verifies that current page contains `text`. 171 | 172 | text 173 | 174 | 175 | 176 | Verifies that current page contains `text`. 177 | 178 | text 179 | 180 | 181 | 182 | Deletes all cookies by calling `Delete Cookie` repeatedly. 183 | 184 | 185 | 186 | 187 | Deletes cookie matching `name` and `options`. 188 | 189 | If the cookie is not found, nothing happens. 190 | 191 | `options` is the options for the cookie as a string. Currently supported options include 'path', 'domain' and 'recurse.' Format for `options` is "path=/path/, domain=.foo.com, recurse=true". The order of options is irrelevant. Note that specifying a domain that isn't a subset of the current domain will usually fail. Setting `recurse=true` will cause this keyword to search all sub-domains of current domain with all paths that are subset of current path. This can take a long time. 192 | 193 | name 194 | options= 195 | 196 | 197 | 198 | Verifies element identified by `locator` contains text `expected`. 199 | 200 | `message` can be used to override the default error message. 201 | 202 | Key attributes for arbitrary elements are `id` and `name`. See `introduction` for details about locating elements. 203 | 204 | locator 205 | excepted 206 | message= 207 | 208 | 209 | 210 | Executes the given javascript code. 211 | 212 | `*code` may contain multiple statements and the return value of last statement is returned by this keyword. 213 | 214 | `*code` may be divided in multiple cells in the test data. In that case, the parts are catenated as is with no white space added. 215 | 216 | Note that, by default, the code will be executed in the context of the Selenium object itself, so 'this' will refer to the Selenium object. Use 'window' to refer to the window of your application, e.g. window.document.getElementById('foo') 217 | 218 | Example: 219 | | Execute Javascript | window.my_js_function('arg1', 'arg2') | 220 | 221 | *code 222 | 223 | 224 | 225 | Sets focus to element identified by `locator`. 226 | 227 | This is useful for instance to direct native keystrokes to particular element using `Press Key Native`. 228 | 229 | locator 230 | 231 | 232 | 233 | Verifies frame identified by `locator` contains `text`. 234 | 235 | Key attributes for frames are `id` and `name.` See `introduction` for details about locating elements. 236 | 237 | locator 238 | text 239 | 240 | 241 | 242 | Verifies frame identified by `locator` contains `text`. 243 | 244 | Key attributes for frames are `id` and `name.` See `introduction` for details about locating elements. 245 | 246 | locator 247 | text 248 | 249 | 250 | 251 | Returns the text of current javascript alert. 252 | 253 | This keyword will fail if no alert is present. Note that when running tests with selenium, the alerts will not be visible in the browser. Nevertheless, following keywords will fail unless the alert is dismissed by this keyword or by `Alert Should Be Present`. 254 | 255 | 256 | 257 | 258 | Returns value of cookie found with `name`. 259 | 260 | If no cookie is found with `name`, this keyword fails. 261 | 262 | name 263 | 264 | 265 | 266 | Returns all cookies of the current page. 267 | 268 | 269 | 270 | 271 | Returns the current location. 272 | 273 | 274 | 275 | 276 | Returns the entire html source of the current page or frame. 277 | 278 | 279 | 280 | 281 | Returns the text of element identified by `locator`. 282 | 283 | See `introduction` for details about locating elements. 284 | 285 | locator 286 | 287 | 288 | 289 | Returns title of current page. 290 | 291 | 292 | 293 | 294 | Returns the value attribute of element identified by `locator`. 295 | 296 | See `introduction` for details about locating elements. 297 | 298 | locator 299 | 300 | 301 | 302 | Navigates the active browser instance to the provided URL. 303 | 304 | url 305 | 306 | 307 | 308 | Types the given password into text field identified by `locator`. 309 | 310 | Difference between this keyword and `Input Text` is that this keyword does not log the given password. See `introduction` for details about locating elements. 311 | 312 | locator 313 | text 314 | 315 | 316 | 317 | Types the given `text` into text field identified by `locator`. 318 | 319 | See `introduction` for details about locating elements. 320 | 321 | locator 322 | text 323 | 324 | 325 | 326 | Verifies the selection of list identified by `locator` is exactly `*values`. 327 | 328 | If you want to test that no option is selected, simply give no `values`. Key attributes for list are `id` and `name`. See `introduction` for details about locating elements. 329 | 330 | locator 331 | *values 332 | 333 | 334 | 335 | Verifies list idenetified by `locator` has no selections. 336 | 337 | Key attributes for list are `id` and `name`. See `introduction` for more details on key attributes and locating elements. 338 | 339 | locator 340 | 341 | 342 | 343 | Verifies that current URL is exactly `url`. 344 | 345 | url 346 | 347 | 348 | 349 | Verifies that current URL contains `expected`. 350 | 351 | expected 352 | 353 | 354 | 355 | Logs and returns the entire html source of the current page or frame 356 | 357 | 358 | 359 | 360 | Simulates a mouse down event on an image. 361 | 362 | Key attributes for images are `id`, `src` and `alt`. See `introduction` for details about locating elements. 363 | 364 | locator 365 | 366 | 367 | 368 | Simulates a mouse down event on a link. 369 | 370 | Key attributes for links are `id`, `name`, `href` and link text. See `introduction` for details about locating elements. 371 | 372 | locator 373 | 374 | 375 | 376 | Opens a new browser instance to given url. 377 | 378 | Possible already opened connections are cached. 379 | 380 | Returns the index of this browser instance which can be used later to switch back to it. Index starts from 1 and is reset back to it when `Close All Browsers` keyword is used. See `Switch Browser` for example. 381 | 382 | Optional alias is a alias for the browser instance and it can be used for switching between browsers similarly as the index. See `Switch Browser` for more details about that. 383 | 384 | Possible values for `browser` are all the values supported by Selenium. Additionally, following table lists case and space insensitive aliases that can be used: 385 | 386 | | *Alias* | *Browser* | 387 | | firefox | Firefox | 388 | | ff | Firefox | 389 | | ie | Internet Explorer | 390 | | internetexplorer | Internet Explorer | 391 | 392 | There are also experimental browser launchers *chrome and *iehta, for Firefox and Internet Explorer, respectively. These browser launchers are automatically used when the given base url startswith 'https'. These launchers should also be used when text has to be inserted in a file upload dialog with keyword `Choose File`. 393 | 394 | url 395 | browser=firefox 396 | alias=None 397 | 398 | 399 | 400 | Verifies that current page contains `text`. 401 | 402 | text 403 | 404 | 405 | 406 | Verifies button identified by `locator` is found from current page. 407 | 408 | `message` can be used to override default error message. 409 | 410 | Key attributes for buttons are `id`, `name` and `value`. See `introduction` for details about locating elements. 411 | 412 | locator 413 | message= 414 | 415 | 416 | 417 | Verifies checkbox identified by `locator` is found from current page. 418 | 419 | `message` can be used to override default error message. 420 | 421 | Key attributes for checkboxes are `id` and `name`. See `introduction` for details about locating elements. 422 | 423 | locator 424 | message= 425 | 426 | 427 | 428 | Verifies element identified by `locator` is found from current page. 429 | 430 | `message` can be used to override default error message. 431 | 432 | Key attributes for arbitrary elements are `id` and `name`. See `introduction` for details about locating elements. 433 | 434 | locator 435 | message= 436 | 437 | 438 | 439 | Verifies image identified by `locator` is found from current page. 440 | 441 | `message` can be used to override default error message. 442 | 443 | Key attributes for images are `id`, `src` and `alt`. See `introduction` for details about locating elements. 444 | 445 | locator 446 | message= 447 | 448 | 449 | 450 | Verifies link identified by `locator` is found from current page. 451 | 452 | `message` can be used to override default error message. 453 | 454 | Key attributes for links are `id`, `name`, `href` and link text. See `introduction` for details about locating elemself._info("Verifying current location is '%s'." % url)ents. 455 | 456 | locator 457 | message= 458 | 459 | 460 | 461 | Verifies list identified by `locator` is found from current page. 462 | 463 | `message` can be used to override default error message. 464 | 465 | Key attributes for lists are `id` and `name`. See `introduction` for details about locating elements. 466 | 467 | locator 468 | message= 469 | 470 | 471 | 472 | Verifies radio button identified by `locator` is found from current page. 473 | 474 | `message` can be used to override default error message. 475 | 476 | Key attributes for radio buttons are `id`, `name` and `value`. See `introduction` for details about locating elements. 477 | 478 | locator 479 | message= 480 | 481 | 482 | 483 | Verifies text field identified by `locator` is found from current page. 484 | 485 | `message` can be used to override default error message. 486 | 487 | Key attributes for text fields are `id` and `name`. See `introduction` for details about locating elements. 488 | 489 | locator 490 | message= 491 | 492 | 493 | 494 | Verifies the current page does not contain `text`. 495 | 496 | text 497 | 498 | 499 | 500 | Verifies button identified by `locator` is not found from current page. 501 | 502 | `message` can be used to override default error message. 503 | 504 | Key attributes for buttons are `id`, `name` and `value`. See `introduction` for details about locating elements. 505 | 506 | locator 507 | message= 508 | 509 | 510 | 511 | Verifies checkbox identified by `locator` is not found from current page. 512 | 513 | `message` can be used to override default error message. 514 | 515 | Key attributes for checkboxes are `id` and `name`. See `introduction` for details about locating elements. 516 | 517 | locator 518 | message= 519 | 520 | 521 | 522 | Verifies element identified by `locator` is not found from current page. 523 | 524 | `message` can be used to override default error message. 525 | 526 | Key attributes for arbitrary elements are `id` and `name`. See `introduction` for details about locating elements. 527 | 528 | locator 529 | message= 530 | 531 | 532 | 533 | Verifies image identified by `locator` is not found from current page. 534 | 535 | `message` can be used to override default error message. 536 | 537 | Key attributes for images are `id`, `src` and `alt`. See `introduction` for details about locating elements. 538 | 539 | locator 540 | message= 541 | 542 | 543 | 544 | Verifies link identified by `locator` is not found from current page. 545 | 546 | `message` can be used to override default error message. 547 | 548 | Key attributes for links are `id`, `name`, `href` and link text. See `introduction` for details about locating elements. 549 | 550 | locator 551 | message= 552 | 553 | 554 | 555 | Verifies list identified by `locator` is not found from current page. 556 | 557 | `message` can be used to override default error message. 558 | 559 | Key attributes for lists are `id` and `name`. See `introduction` for details about locating elements. 560 | 561 | locator 562 | message= 563 | 564 | 565 | 566 | Verifies radio button identified by `locator` is not found from current page. 567 | 568 | `message` can be used to override default error message. 569 | 570 | Key attributes for radio buttons are `id`, `name` and `value`. See `introduction` for details about locating elements. 571 | 572 | locator 573 | message= 574 | 575 | 576 | 577 | Verifies text field identified by `locator` is not found from current page. 578 | 579 | `message` can be used to override default error message. 580 | 581 | Key attributes for text fields are `id` and `name`. See `introduction` for details about locating elements. 582 | 583 | locator 584 | message= 585 | 586 | 587 | 588 | Simulates user pressing key on element identified by `locator`. 589 | 590 | `key` is either a single character, or numerical ASCII code of the key lead by '\'. 591 | 592 | See `introduction` for details about `wait` argument. 593 | 594 | Examples: 595 | | Press Key | text_field | q | 596 | | Press Key | login_button | \13 | # ASCII code for enter key | 597 | 598 | locator 599 | key 600 | wait= 601 | 602 | 603 | 604 | Simulates user pressing key by sending an operating system keystroke. 605 | 606 | `keycode` corresponds to `java.awt.event.KeyEvent` constants, which can be found from http://java.sun.com/javase/6/docs/api/constant-values.html#java.awt.event.KeyEvent.CHAR_UNDEFINED 607 | 608 | The key press does not target a particular element. An element can be chosen by first using `Focus` -keyword. 609 | 610 | See `introduction` for details about `wait` argument. 611 | 612 | Examples: 613 | | Press Key Native | 517 | # Exclamation mark | 614 | | Focus | login_button | 615 | | Press Key Native | 10 | # Enter key | 616 | 617 | keycode 618 | wait= 619 | 620 | 621 | 622 | Verifies radio button group identified by `group_name` has its selection set to `value`. 623 | 624 | group_name 625 | value 626 | 627 | 628 | 629 | Verifies radio button group identified by `group_name` has no selection. 630 | 631 | group_name 632 | 633 | 634 | 635 | Selects all values from multi-select list identified by `id`. 636 | 637 | Key attributes for list are `id` and `name`. See `introduction` for details about locating elements and about `wait` argument. 638 | 639 | locator 640 | wait= 641 | 642 | 643 | 644 | Selects checkbox identified by `locator`. 645 | 646 | Does nothing if checkbox is already selected. Key attributes for checkboxes are `id` and `name`. See `introduction` for details about locating elements. 647 | 648 | locator 649 | 650 | 651 | 652 | Sets frame identified by `locator` as current frame. 653 | 654 | Key attributes for frames are `id` and `name.` See `introduction` for details about locating elements. 655 | 656 | locator 657 | 658 | 659 | 660 | Selects `*values` from list identified by `locator` 661 | 662 | If more than one value is given for a single-selection list, the last value will be selected. If the target list is a multi-selection list, and `*values` is an empty list, all values of the list will be selected. 663 | 664 | Key attributes for list are `id` and `name`. See `introduction` for details about locating elements. 665 | 666 | locator 667 | *values 668 | 669 | 670 | 671 | Sets selection of radio button group identified by `group_name` to `value`. 672 | 673 | See `introduction` for details about `wait` argument. 674 | 675 | group_name 676 | value 677 | wait= 678 | 679 | 680 | 681 | Selects the window found with `windowID` as the context of actions. 682 | 683 | If the window is found, all subsequent commands use that window, until this keyword is used again. If the window is not found, this keyword fails. 684 | 685 | `windowID` may be either the title of the window or the name of the window in the javascript code that creates it. Name is second argument passed to javascript function window.open(). In case of multiple windows with same identifier are found, the first one is selected. 686 | 687 | To select main window, the argument can be left empty, or name 'main' can be used. 688 | 689 | Example: 690 | | Click Link | popup_link | don't wait | # opens new window | 691 | | Select Window | popupName | 692 | | Title Should Be | Popup Title | 693 | | Select Window | | | # Chooses the main window again | 694 | 695 | windowID=None 696 | 697 | 698 | 699 | Sets the delay that is waited after each Selenium command. 700 | 701 | This is useful mainly in slowing down the test execution to be able to view the execution. `seconds` may be given in Robot Framework time format. Returns the previous speed value. 702 | 703 | Example: 704 | | Set Selenium Speed | 2 seconds | 705 | 706 | seconds 707 | 708 | 709 | 710 | Sets the timeout used by various keywords. 711 | 712 | The keywords that expect a page load to happen will fail if the page does not load within the time specified with `seconds`. `seconds` may be given in Robot Framework time format and the default value is 5 seconds. Returns the previous timeout value. 713 | 714 | seconds 715 | 716 | 717 | 718 | Shuts down the selenium server (and all browsers). 719 | 720 | 721 | 722 | 723 | *DEPRECATED* Use `Shut Down Selenium Server` instead. 724 | 725 | 726 | 727 | 728 | Simulates `event` on component identified by `locator`. 729 | 730 | This keyword is useful if component has OnEvent handler that needs to be explicitly invoked. 731 | 732 | See `introduction` for details about locating elements. 733 | 734 | locator 735 | event 736 | 737 | 738 | 739 | Starts the selenium server provided with Selenium Library. 740 | 741 | Server will use the default host and port, ie. `localhost:4444`. Additionally, all Selenium server output will be written in a file called `selenium_server_log.txt` under Robot Framework output directory. 742 | 743 | This keyword does not work with Jython 2.2 or older. 744 | 745 | *params 746 | 747 | 748 | 749 | Submits a form identified by `locator`. 750 | 751 | If `locator` is empty, first form in the page will be submitted. Key attributes for forms are `id` and `name`. See `introduction` for details about locating elements and about meaning of `dont_wait` argument. 752 | 753 | locator= 754 | dont_wait= 755 | 756 | 757 | 758 | Switches between active browsers using index or alias. 759 | 760 | Index is got from `Open Browser` and alias can be given to it. 761 | 762 | Examples: 763 | | Open Browser | http://google.com | ff | 764 | | Location Should Be | http://google.com | | 765 | | Open Browser | http://yahoo.com | ie | 2nd conn | 766 | | Location Should Be | http://yahoo.com | | 767 | | Switch Browser | 1 | # index | 768 | | Page Should Contain | I'm feeling lucky | | 769 | | Switch Browser | 2nd conn | # alias | 770 | | Page Should Contain | More Yahoo! | | 771 | | Close All Browsers | | | 772 | 773 | Above example expects that there was no other open browsers when opening the first one because it used index '1' when switching to it later. If you aren't sure about that you can store the index into a variable as below. 774 | 775 | | ${id} = | Open Browser | http://google.com | *firefox | 776 | | # Do something ... | 777 | | Switch Browser | ${id} | | | 778 | 779 | index_or_alias 780 | 781 | 782 | 783 | Verifies text field identified by `locator` contains text `expected`. 784 | 785 | `message` can be used to override default error message. 786 | 787 | Key attributes for text fields are `id` and `name`. See `introduction` for details about locating elements. 788 | 789 | locator 790 | expected 791 | message= 792 | 793 | 794 | 795 | Verifies the value in text field identified by `locator` is exactly `expected`. 796 | 797 | `message` can be used to override default error message. 798 | 799 | Key attributes for text fields are `id` and `name`. See `introduction` for details about locating elements. 800 | 801 | locator 802 | expected 803 | message= 804 | 805 | 806 | 807 | Verifies that current page title equals `title`. 808 | 809 | title 810 | 811 | 812 | 813 | Removes selection of checkbox identified by `locator`. 814 | 815 | Does nothing if the checkbox is not checked. Key attributes for checkboxes are `id` and `name`. See `introduction` for details about locating elements. 816 | 817 | locator 818 | 819 | 820 | 821 | Sets the top frame as the current frame. 822 | 823 | 824 | 825 | 826 | Unselects given values from list identified by locator. 827 | 828 | As a special case, giving empty list as `*selection` will remove all selections. 829 | 830 | Key attributes for list are `id` and `name`. See `introduction` for details about locating elements. 831 | 832 | locator 833 | *values 834 | 835 | 836 | 837 | Waits either for given condition to be true or until timeout expires. 838 | 839 | `condition` can be arbitrary javascript expression. It can be multiple lines, but only the statement in the last line is used for evaluation. 840 | 841 | `timeout` must given using Robot Framework time syntax, see http://robotframework.googlecode.com/svn/trunk/doc/userguide/RobotFrameworkUserGuide.html#time-format. 842 | 843 | See `Execute Javascript` for information about accessing the actual contents of the window through javascript. 844 | 845 | condition 846 | timeout=5 seconds 847 | 848 | 849 | 850 | Waits until `text` appears on current page or `timeout` expires. 851 | 852 | `timeout` must given using Robot Framework time syntax, see http://robotframework.googlecode.com/svn/trunk/doc/userguide/RobotFrameworkUserGuide.html#time-format. 853 | 854 | Robot Framework built-in keyword `Wait Until Succeeds` can be used to get this kind of functionality for any Selenium keyword. 855 | 856 | text 857 | timeout 858 | 859 | 860 | 861 | -------------------------------------------------------------------------------- /atest/testdata/xmls/invalid/empty_keyword.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | Documentation 4 | 2.1 5 | 6 | Init Doc 7 | 8 | init_arg 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | -------------------------------------------------------------------------------- /atest/testdata/xmls/invalid/empty_keyword_name.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | Documentation 4 | 2.1 5 | 6 | Init Doc 7 | 8 | init_arg 9 | 10 | 11 | 12 | 13 | KW doc 14 | 15 | kw arg 16 | 17 | 18 | 19 | 20 | -------------------------------------------------------------------------------- /atest/testdata/xmls/invalid/empty_keywords.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | Documentation 4 | 2.1 5 | 6 | Init Doc 7 | 8 | init_arg 9 | 10 | 11 | 12 | 13 | 14 | -------------------------------------------------------------------------------- /atest/testdata/xmls/invalid/empty_library_name.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | Documentation 4 | 2.1 5 | 6 | Init Doc 7 | 8 | init_arg 9 | 10 | 11 | 12 | 13 | KW doc 14 | 15 | kw arg 16 | 17 | 18 | 19 | 20 | -------------------------------------------------------------------------------- /atest/testdata/xmls/invalid/invalid_content.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | Critical Tests 11 | All Tests 12 | 13 | 14 | 15 | 16 | Rfdoc 17 | Rfdoc.Upload 18 | Rfdoc.Upload.Invalid File Upload 19 | 20 | 21 | 22 | 23 | 24 | -------------------------------------------------------------------------------- /atest/testdata/xmls/invalid/missing_keyword_doc.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | Documentation 4 | 2.1 5 | 6 | Init Doc 7 | 8 | init_arg 9 | 10 | 11 | 12 | 13 | 14 | kw arg 15 | 16 | 17 | 18 | 19 | -------------------------------------------------------------------------------- /atest/testdata/xmls/invalid/missing_keyword_name.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | Documentation 4 | 2.1 5 | 6 | Init Doc 7 | 8 | init_arg 9 | 10 | 11 | 12 | 13 | KW doc 14 | 15 | kw arg 16 | 17 | 18 | 19 | 20 | -------------------------------------------------------------------------------- /atest/testdata/xmls/invalid/missing_keywords.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | Documentation 4 | 2.1 5 | 6 | Init Doc 7 | 8 | init_arg 9 | 10 | 11 | 12 | -------------------------------------------------------------------------------- /atest/testdata/xmls/invalid/missing_library_doc.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 2.1 4 | 5 | Init Doc 6 | 7 | init_arg 8 | 9 | 10 | 11 | 12 | KW doc 13 | 14 | kw arg 15 | 16 | 17 | 18 | 19 | -------------------------------------------------------------------------------- /atest/testdata/xmls/invalid/missing_library_name.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | Documentation 4 | 2.1 5 | 6 | Init Doc 7 | 8 | init_arg 9 | 10 | 11 | 12 | 13 | KW doc 14 | 15 | kw arg 16 | 17 | 18 | 19 | 20 | -------------------------------------------------------------------------------- /atest/testdata/xmls/invalid/text_file.txt: -------------------------------------------------------------------------------- 1 | Some text -------------------------------------------------------------------------------- /atest/update_documentation.txt: -------------------------------------------------------------------------------- 1 | *** Settings *** 2 | Resource resources/rfdoc.txt 3 | Test Setup Given ExampleLibrary version 1 exists in RFDoc 4 | 5 | *** Test Cases *** 6 | Existing Library Is Not Overridden By Default 7 | "upload" page is open 8 | When user uploads ExampleLibrary version 1 9 | Then error "ExampleLibrary version 1 already exists" is shown 10 | and RFDoc contains versioned library ExampleLibrary version 1 11 | 12 | User Can Update Library Documentation 13 | "upload" page is open 14 | When user selects "override" option 15 | and user uploads ExampleLibrary version 1 16 | Then notification "Successfully uploaded library ExampleLibrary" is shown 17 | and RFDoc contains versioned library ExampleLibrary version 1 18 | -------------------------------------------------------------------------------- /atest/upload_documentation.txt: -------------------------------------------------------------------------------- 1 | *** Settings *** 2 | Resource resources/rfdoc.txt 3 | Test Setup Given no libraries exist in RFDoc 4 | 5 | *** Test Cases *** 6 | User Can Upload Library Documentation 7 | "upload" page is open 8 | When user uploads BuiltIn 9 | Then notification "Successfully uploaded library BuiltIn" is shown 10 | and notification contains a version "2.1" link to "BuiltIn" 11 | and RFDoc contains library BuiltIn 12 | 13 | User Can Upload Library Documentation And Override Version 14 | "upload" page is open 15 | When user overrides version with "5" and uploads "ExampleLibrary version 1" 16 | Then notification "Successfully uploaded library ExampleLibrary" is shown 17 | and notification contains a version "5" link to "ExampleLibrary" 18 | and RFDoc contains versioned library ExampleLibrary version 5 19 | 20 | User Can Upload Library Documentation And Override Name 21 | "upload" page is open 22 | When user overrides name with "NewnameLibrary" and uploads "ExampleLibrary version 2" 23 | Then notification "Successfully uploaded library NewnameLibrary" is shown 24 | and notification contains a version "2" link to "NewnameLibrary" 25 | and RFDoc contains versioned library NewnameLibrary version 2 -------------------------------------------------------------------------------- /atest/uploader.txt: -------------------------------------------------------------------------------- 1 | *** Settings *** 2 | Resource resources/rfdoc.txt 3 | 4 | *** Variables *** 5 | ${INVALID URL} localhost:1 6 | ${SCRIPT NAME} upload.py 7 | ${SCRIPT PATH} ${CURDIR}${/}..${/}src${/}rfdoc${/}${SCRIPT NAME} 8 | ${EXAMPLE LIBRARY SOURCE} ${TESTDATA PATH}${/}sources${/}example_lib.py 9 | ${EMPTY KEYWORDS XML} ${TESTDATA PATH}${/}xmls${/}invalid${/}empty_keywords.xml 10 | ${EXAMPLE LIBRARY XML V1} ${TESTDATA PATH}${/}xmls${/}ExampleLibrary_version_1.xml 11 | ${EXAMPLE LIBRARY XML V2} ${TESTDATA PATH}${/}xmls${/}ExampleLibrary_version_2.xml 12 | @{EXAMPLE LIB KEYWORDS} My Keyword self Does nothing. Your Keyword self, arg Takes one argument and does nothing with it. 13 | 14 | *** Test Cases *** 15 | With one source file given 16 | [Setup] Given no libraries exist in RFDoc 17 | ${rc} ${output}= Run With -u ${BASE URL} ${EXAMPLE LIBRARY SOURCE} 18 | Should Print ${EXAMPLE LIBRARY SOURCE} Updated 19 | RFDoc contains library example_lib 20 | Should Exit With 0 21 | 22 | With one library XML given 23 | [Setup] Given no libraries exist in RFDoc 24 | ${rc} ${output}= Run With -u ${BASE URL} ${EXAMPLE LIBRARY XML V1} 25 | Should Print ${EXAMPLE LIBRARY XML V1} Updated 26 | RFDoc contains versioned library ExampleLibrary version 1 27 | Should Exit With 0 28 | 29 | With multiple different files given 30 | [Setup] Given no libraries exist in RFDoc 31 | ${rc} ${output}= Run With -u ${BASE URL} ${EXAMPLE LIBRARY XML V1} ${EXAMPLE LIBRARY SOURCE} 32 | Should Print ${EXAMPLE LIBRARY XML V1} Updated 33 | Should Print ${EXAMPLE LIBRARY SOURCE} Updated 34 | RFDoc contains versioned library ExampleLibrary version 1 35 | RFDoc contains library example_lib 36 | Should Exit With 0 37 | 38 | With a directory given 39 | [Setup] Given no libraries exist in RFDoc 40 | ${rc} ${output}= Run With -u ${BASE URL} ${TESTDATA PATH}${/}sources 41 | Should Print ${EXAMPLE LIBRARY SOURCE} Updated 42 | RFDoc contains library example_lib 43 | Should Exit With 0 44 | 45 | With new version 46 | [Setup] Given some libraries exist in RFDoc 47 | Run Keyword And Expect Error * RFDoc contains versioned library ExampleLibrary version 2 48 | ${rc} ${output}= Run With -u ${BASE URL} ${EXAMPLE LIBRARY XML V2} 49 | Should Print ${EXAMPLE LIBRARY XML V2} Updated 50 | RFDoc contains versioned library ExampleLibrary version 1 51 | RFDoc contains versioned library ExampleLibrary version 2 52 | Should Exit With 0 53 | 54 | With version override 55 | [Setup] Given no libraries exist in RFDoc 56 | Run Keyword And Expect Error * RFDoc contains versioned library ExampleLibrary version 5 57 | ${rc} ${output}= Run With -u ${BASE URL} -v 5 ${EXAMPLE LIBRARY XML V1} 58 | Should Print ${EXAMPLE LIBRARY XML V1} Updated 59 | RFDoc contains versioned library ExampleLibrary version 5 60 | Should Exit With 0 61 | 62 | With name override 63 | [Setup] Given no libraries exist in RFDoc 64 | Run Keyword And Expect Error * RFDoc contains versioned library ExampleLibrary version 5 65 | ${rc} ${output}= Run With -u ${BASE URL} -n NewnameLibrary ${EXAMPLE LIBRARY XML V2} 66 | Should Print ${EXAMPLE LIBRARY XML V2} Updated 67 | RFDoc contains versioned library NewnameLibrary version 2 68 | Should Exit With 0 69 | 70 | With invalid arguments 71 | ${invalid_argument}= Set Variable --invalid 72 | ${rc} ${output}= Run With ${invalid_argument} 73 | Should Print Argument ${invalid_argument} Is Invalid 74 | Should Exit With 2 75 | 76 | Without arguments 77 | ${rc} ${output}= Run With ${EMPTY} 78 | Should Print Help 79 | Should Exit With 1 80 | 81 | With -h 82 | ${rc} ${output}= Run With -h 83 | Should Print Help 84 | Should Exit With 0 85 | 86 | With --help 87 | ${rc} ${output}= Run With --help 88 | Should Print Help 89 | Should Exit With 0 90 | 91 | With -u but without URL 92 | ${rc} ${output}= Run With -u 93 | Should Print Argument -u Requires Value 94 | Should Exit With 2 95 | 96 | With -u but no files given 97 | ${rc} ${output}= Run With -u ${BASE URL} 98 | Should Print Help 99 | Should Exit With 1 100 | 101 | With --url but no files given 102 | ${rc} ${output}= Run With --url=${BASE URL} 103 | Should Print Help 104 | Should Exit With 1 105 | 106 | With library not found 107 | ${rc} ${output}= Run With -u ${BASE URL} NotExistingLibrary 108 | Should Print NotExistingLibrary Not Found 109 | Should Exit With 1 110 | 111 | With an error in parsing one library 112 | [Setup] Given no libraries exist in RFDoc 113 | ${rc} ${output}= Run With -u ${BASE URL} InvalidLibrary ${EXAMPLE LIBRARY XML V1} 114 | Should Print InvalidLibrary Failed To Update 115 | Should Print ${EXAMPLE LIBRARY XML V1} Updated 116 | RFDoc contains versioned library ExampleLibrary version 1 117 | Should Exit With 1 118 | 119 | With invalid host given 120 | ${rc} ${output}= Run With --url=${INVALID URL} ${EXAMPLE LIBRARY XML V1} 121 | Should Print ${INVALID URL} Is Invalid Host 122 | Should Exit With 1 123 | 124 | With a remote error 125 | [Setup] Given no libraries exist in RFDoc 126 | ${rc} ${output}= Run With -u ${BASE URL} ${EMPTY KEYWORDS XML} ${EXAMPLE LIBRARY XML V1} 127 | Should Print empty_keywords.xml Contains No Keywords 128 | Should Print ${EMPTY KEYWORDS XML} In Errorlist 129 | Should Print ${EXAMPLE LIBRARY XML V1} Updated 130 | RFDoc contains versioned library ExampleLibrary version 1 131 | Should Exit With 1 132 | 133 | *** Keywords *** 134 | Run With ${arguments} 135 | ${rc} ${output}= Run And Return Rc And Output ${SCRIPT_PATH} ${arguments} 136 | [Return] ${rc} ${output} 137 | 138 | Should Exit With 139 | [Arguments] ${expected} 140 | Should Be Equal As Integers ${rc} ${expected} 141 | 142 | Should Print Help 143 | Should Contain ${output} This script updates the documentation at Robot Framework RFDoc server. 144 | 145 | Should Print ${test_library} Updated 146 | Should Contain ${output} Updated documentation for '${test_library}' 147 | 148 | Should Print ${test_library} Failed To Update 149 | Should Contain ${output} Skipping '${test_library}' due to an error: 150 | 151 | Should Print ${test_library} Not Found 152 | Should Contain ${output} Skipping '${test_library}' due to an error: Library not found. 153 | 154 | Should Print Argument ${argument} Requires Value 155 | Should Contain ${output} ${SCRIPT_NAME}: error: ${argument} option requires an argument 156 | Should Contain ${output} Try '${SCRIPT_NAME} --help' for more information 157 | 158 | Should Print Argument ${argument} Is Invalid 159 | Should Contain ${output} ${SCRIPT_NAME}: error: no such option: ${argument} 160 | Should Contain ${output} Try '${SCRIPT_NAME} --help' for more information 161 | 162 | Should Print ${host} Is Invalid Host 163 | Should Contain ${output} ${SCRIPT_NAME}: Remote error: connection refused to '${host}', check that the host responds and is reachable. 164 | 165 | Should Print ${test_library} Contains No Keywords 166 | Should Contain ${output} ${SCRIPT_NAME}: Remote error: Given test library file ${test_library} contains no keywords. 167 | 168 | Should Print ${test_library} In Errorlist 169 | Should Contain ${output} ERROR: Uploading 1 file(s) failed: 170 | Should Contain ${output} * ${test_library} -------------------------------------------------------------------------------- /setup.cfg: -------------------------------------------------------------------------------- 1 | [sdist] 2 | force-manifest=1 3 | -------------------------------------------------------------------------------- /setup.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python 2 | 3 | try: 4 | from setuptools import setup 5 | requires = { 6 | 'install_requires': ['django >= 1.5, < 1.7'], 7 | } 8 | except ImportError: 9 | from distutils.core import setup 10 | requires = {} 11 | 12 | from os.path import abspath, dirname, join 13 | 14 | execfile(join(dirname(abspath(__file__)), 'src', 'rfdoc', 'version.py')) 15 | 16 | # Maximum width in Windows installer seems to be 70 characters -------| 17 | DESCRIPTION = """ 18 | RFDoc is a web application for storing and searching Robot Framework 19 | test library and resource file documentations. 20 | 21 | Required packages: 22 | django >= 1.5 23 | """[1:-1] 24 | 25 | CLASSIFIERS = """ 26 | Development Status :: 5 - Production/Stable 27 | License :: OSI Approved :: Apache Software License 28 | Operating System :: OS Independent 29 | Programming Language :: Python 30 | Topic :: Software Development :: Testing 31 | """[1:-1] 32 | 33 | setup( 34 | name = 'robotframework-rfdoc', 35 | version = VERSION, 36 | description = 'Web-based Robot Framework library documentation server', 37 | long_description = DESCRIPTION, 38 | author = 'Robot Framework Developers', 39 | author_email = 'robotframework-devel@googlegroups.com', 40 | url = 'http://code.google.com/p/rfdoc/', 41 | license = 'Apache License 2.0', 42 | keywords = 'robotframework testing testautomation documentation', 43 | platforms = 'any', 44 | classifiers = CLASSIFIERS.splitlines(), 45 | package_dir = {'rfdoc': 'src/rfdoc'}, 46 | packages = ['rfdoc', 'rfdoc.rfdocapp', 'rfdoc.rfdocapp.views', 47 | 'rfdoc.rfdocapp.templatetags', 'rfdoc.rfdocapp.utils'], 48 | package_data = {'rfdoc': ['*.tmpl', 'rfdocapp/templates/*.html', 49 | 'rfdocapp/static/*.css', 50 | 'rfdocapp/static/*.js']}, 51 | **requires 52 | ) 53 | -------------------------------------------------------------------------------- /src/rfdoc/__init__.py: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/robotframework/rfdoc/bd7063f8e1f93de6f7c792e1dce601feb7dfb671/src/rfdoc/__init__.py -------------------------------------------------------------------------------- /src/rfdoc/manage.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python 2 | 3 | import os 4 | import sys 5 | 6 | if __name__ == "__main__": 7 | os.environ.setdefault('DJANGO_SETTINGS_MODULE', 'rfdoc.settings') 8 | from django.core.management import execute_from_command_line 9 | execute_from_command_line(sys.argv) 10 | -------------------------------------------------------------------------------- /src/rfdoc/rfdoc_wsgi.py: -------------------------------------------------------------------------------- 1 | """ 2 | WSGI config for RFDoc. 3 | 4 | For more information on this file, see 5 | https://docs.djangoproject.com/en/1.6/howto/deployment/wsgi/ 6 | """ 7 | 8 | import os 9 | import site 10 | import sys 11 | 12 | # Site-packages reside inside the virtualenv 13 | SITE_PACKAGES_PATH = os.path.join(os.path.expanduser('~'), 'venv', 'lib', 14 | 'python2.7', 'site-packages') 15 | 16 | # Adds site-packages to sys.path 17 | prev_sys_path = list(sys.path) 18 | site.addsitedir(SITE_PACKAGES_PATH) 19 | new_sys_path = [] 20 | for item in list(sys.path): 21 | if item not in prev_sys_path: 22 | new_sys_path.append(item) 23 | sys.path.remove(item) 24 | sys.path[:0] = new_sys_path 25 | 26 | # Defines the name of the settings module 27 | os.environ.setdefault("DJANGO_SETTINGS_MODULE", "rfdoc.settings") 28 | 29 | # Initializes WSGI 30 | from django.core.wsgi import get_wsgi_application 31 | application = get_wsgi_application() 32 | -------------------------------------------------------------------------------- /src/rfdoc/rfdocapp/__init__.py: -------------------------------------------------------------------------------- 1 | # Copyright 2009-2013 Nokia Siemens Networks Oyj 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 | -------------------------------------------------------------------------------- /src/rfdoc/rfdocapp/admin.py: -------------------------------------------------------------------------------- 1 | # Copyright 2009-2013 Nokia Siemens Networks Oyj 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 | from django.contrib import admin 16 | 17 | from rfdoc.rfdocapp.models import Library, Keyword, Init 18 | 19 | 20 | class InitInline(admin.StackedInline): 21 | model = Init 22 | extra = 1 23 | fieldsets = ( 24 | ('Details', {'classes': ('collapse',), 25 | 'fields': ('args', 'doc',)}), 26 | ) 27 | 28 | class KeywordInline(admin.StackedInline): 29 | model = Keyword 30 | extra = 5 31 | fieldsets = ( 32 | (None, {'fields': ('name',)}), 33 | ('Details', {'classes': ('collapse',), 34 | 'fields': ('args', 'doc',)}) 35 | ) 36 | 37 | class LibraryAdmin(admin.ModelAdmin): 38 | fieldsets = ( 39 | (None, {'fields': ('name',)}), 40 | ('Details', {'classes': ('collapse',), 41 | 'fields': ('version', 'doc')}) 42 | ) 43 | inlines = [InitInline, KeywordInline] 44 | 45 | admin.site.register(Library, LibraryAdmin) 46 | -------------------------------------------------------------------------------- /src/rfdoc/rfdocapp/models.py: -------------------------------------------------------------------------------- 1 | # Copyright 2009-2013 Nokia Siemens Networks Oyj 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 | from django.db import models 16 | 17 | 18 | class Library(models.Model): 19 | name = models.CharField(max_length=80) 20 | doc = models.TextField(verbose_name='Documentation') 21 | version = models.CharField(max_length=20, default='') 22 | 23 | def __unicode__(self): 24 | return self.name 25 | 26 | 27 | class Keyword(models.Model): 28 | library = models.ForeignKey(Library) 29 | name = models.CharField(max_length=80) 30 | doc = models.TextField(verbose_name='Documentation') 31 | args = models.CharField(max_length=200, verbose_name='Arguments', 32 | help_text='Use format: arg1, arg2=default') 33 | 34 | def __unicode__(self): 35 | return self.name 36 | 37 | 38 | class Init(models.Model): 39 | library = models.ForeignKey(Library) 40 | name = '' 41 | doc = models.TextField(verbose_name='Documentation') 42 | args = models.CharField(max_length=200, verbose_name='Arguments', 43 | help_text='Use format: arg1, arg2=default') 44 | 45 | def __unicode__(self): 46 | return self.name 47 | 48 | -------------------------------------------------------------------------------- /src/rfdoc/rfdocapp/static/default.css: -------------------------------------------------------------------------------- 1 | /* 2 | * The original source for these styles is the Graphite theme got from 3 | * the excellent Wufoo Form Gallery . 4 | * 5 | * All themes and templates from the Wufoo Form Gallery are provided 6 | * under a Creative Commons Attribution license. 7 | * 8 | * http://creativecommons.org/licenses/by/3.0/ 9 | */ 10 | 11 | /* General structure */ 12 | body { 13 | background: #222222; 14 | font-size: small; 15 | font-family: sans-serif; 16 | padding: 1em; 17 | } 18 | #header, #content, #footer { 19 | margin: 0 auto; 20 | width: 70%; 21 | max-width: 1000px; 22 | padding: 1.7em; 23 | } 24 | 25 | /* Header */ 26 | #header { 27 | padding-top: 0.7em; 28 | padding-bottom: 0.7em; 29 | height: 1.5em; 30 | background: #444444; 31 | color: white; 32 | font-variant: small-caps; 33 | } 34 | h1 { 35 | font-size: 1.5em; 36 | letter-spacing: -0.2em; 37 | margin: 0; 38 | float: left; 39 | } 40 | #navigation { 41 | float: right; 42 | } 43 | h1 a, h1 a:hover, #navigation a { 44 | color: white; 45 | text-decoration: none; 46 | } 47 | #navigation a:hover { 48 | color: #eeeeee; 49 | } 50 | 51 | /* Footer */ 52 | #footer { 53 | padding-top: 0.4em; 54 | padding-bottom: 0.4em; 55 | background: #444444; 56 | color: black; 57 | } 58 | #footer p { 59 | font-size: 0.8em; 60 | text-align: center; 61 | margin: 0; 62 | } 63 | #footer p a { 64 | color: black; 65 | text-decoration: none; 66 | } 67 | #footer p a:hover { 68 | color: #eeeeee; 69 | } 70 | 71 | /* Content */ 72 | #content { 73 | padding-top: 0.7em; 74 | background: white; 75 | color: #555555; 76 | } 77 | h2, h3 { 78 | color: #222222; 79 | font-weight: normal; 80 | font-size: 1.6em; 81 | margin: 0.5em 0em 0.3em 0em; 82 | } 83 | h3 { 84 | font-size: 1.2em; 85 | } 86 | div.hr { 87 | margin: 1.3em 0em; 88 | border-bottom: 1px dotted #cccccc; 89 | } 90 | div.hr hr { 91 | display: none; 92 | } 93 | p.info { 94 | margin: 0; 95 | } 96 | a { 97 | color: black; 98 | } 99 | ul.libraries { 100 | margin-top: 20px; 101 | margin-bottom: 0; 102 | } 103 | ul.libraries a { 104 | text-decoration: none; 105 | } 106 | a:hover { 107 | color: #444444; 108 | text-decoration: underline; 109 | } 110 | div.versioncontainer{ 111 | margin-top:20px; 112 | overflow: auto; 113 | width: 100%; 114 | } 115 | div.versioncontainer div{ 116 | display:inline-table; 117 | width: 200px; 118 | margin-bottom: 20px; 119 | } 120 | div.versioncontainer div h3 { 121 | text-align: center; 122 | margin: 2px; 123 | padding: 2px; 124 | background-color: #eee; 125 | } 126 | 127 | /* Forms */ 128 | 129 | form { 130 | font-size: 0.9em; 131 | } 132 | form div { 133 | margin: 1.3em 0; 134 | } 135 | .success, .errorlist { 136 | font-size: 0.85em; 137 | background: #eeeeee; 138 | padding: 0.6em 1.4em; 139 | margin: 0; 140 | -webkit-border-radius: 5px; 141 | -moz-border-radius: 5px; 142 | } 143 | .errorlist { 144 | list-style-type: none; 145 | color: red; 146 | } 147 | .form-options { 148 | border: 1px dashed #eee; 149 | } 150 | a.help { 151 | font-size: 0.5em; 152 | } 153 | div.help { 154 | display: none; 155 | border: 1px solid #000; 156 | background-color: #eee; 157 | } 158 | div.help h3 { 159 | margin: 10px; 160 | } 161 | div.help table { 162 | margin: 10px; 163 | border-spacing: 10px; 164 | } 165 | div.help th { 166 | text-align: right; 167 | } 168 | 169 | /* Search */ 170 | table#search { 171 | margin: 0.5em 0 1em 0; 172 | } 173 | table#search td { 174 | padding-right: 1em; 175 | } 176 | 177 | table.results td a { 178 | text-decoration: none; 179 | } 180 | table.results td a:hover { 181 | text-decoration: underline; 182 | } 183 | 184 | /* Library pages */ 185 | 186 | #versions { 187 | background-color: #eee; 188 | width: auto; 189 | max-width: 300px; 190 | position: fixed; 191 | bottom: 20px; 192 | right: 20px; 193 | z-index: 400; 194 | } 195 | #versions dt { 196 | font-weight: bold; 197 | } 198 | #versions dl { 199 | display: none; 200 | margin: 10px 10px; 201 | } 202 | #versions dd { 203 | display: inline; 204 | margin: 0 5px; 205 | } 206 | span.versions-current { 207 | cursor: pointer; 208 | display: block; 209 | width: auto; 210 | margin: 0; 211 | padding: 0 20px; 212 | line-height: 1.5em; 213 | text-align: right; 214 | color: #fff; 215 | background-color: #000; 216 | } 217 | span.versions-current:after { 218 | display: inline-block; 219 | position:relative; 220 | top: -2px; 221 | left: 10px; 222 | content: ""; 223 | height:0; 224 | width:0; 225 | padding:0; 226 | margin:-5px; 227 | border:5px solid transparent; 228 | border-top-color:#FFF; 229 | } 230 | p.version { 231 | font-size: 0.85em; 232 | } 233 | p.version b { 234 | font-weight: normal; 235 | color: black; 236 | } 237 | p.libintro { 238 | margin: 1em 0em; 239 | } 240 | div.shortcuts { 241 | margin: 1em 0em; 242 | font-size: 0.85em; 243 | } 244 | div.shortcuts a { 245 | text-decoration: none; 246 | } 247 | div.shortcuts a:hover { 248 | text-decoration: underline; 249 | } 250 | table.keywords { 251 | background: white; 252 | border: 1px solid #444444; 253 | border-collapse: collapse; 254 | empty-cells: show; 255 | font-size: 0.9em; 256 | margin: 1em 0em; 257 | width: 100%; 258 | } 259 | table.keywords th, table.keywords td { 260 | border: 1px solid #444444; 261 | padding: 0.2em 0.3em; 262 | } 263 | table.keywords th { 264 | background: #eeeeee; 265 | font-weight: normal; 266 | color: black; 267 | } 268 | table.keywords td { 269 | vertical-align: top; 270 | } 271 | table.keywords td.kw { 272 | width: 120px; 273 | color: black; 274 | } 275 | table.keywords td.arg { 276 | width: 150px; 277 | font-style: italic; 278 | } 279 | table.doc { 280 | border: 1px solid gray; 281 | background: transparent; 282 | border-collapse: collapse; 283 | empty-cells: show; 284 | font-size: 0.85em; 285 | font-family: sans-serif; 286 | } 287 | table.doc td { 288 | border: 1px solid gray; 289 | padding: 0.1em 0.2em; 290 | height: 1.2em; 291 | } 292 | table.doc b { 293 | font-weight: normal; 294 | color: black; 295 | } 296 | a.name, span.name { 297 | font-style: italic; 298 | color: black; 299 | } 300 | a.name:hover { 301 | color: #444444; 302 | } 303 | -------------------------------------------------------------------------------- /src/rfdoc/rfdocapp/static/print.css: -------------------------------------------------------------------------------- 1 | #header, #footer { 2 | display: none; 3 | } 4 | 5 | body, #content { 6 | background: white; 7 | width: 100%; 8 | max-width: 100%; 9 | padding: 0; 10 | margin: 0; 11 | font-size: 10px; 12 | } 13 | 14 | table.keywords { 15 | width: 99%; 16 | margin-left: 1px; 17 | margin-right: 1px; 18 | } 19 | 20 | a { 21 | text-decoration: none; 22 | } 23 | -------------------------------------------------------------------------------- /src/rfdoc/rfdocapp/static/rfdoc.js: -------------------------------------------------------------------------------- 1 | $(document).ready(function() { 2 | $('.versions-current').click(function(){ 3 | $('#versions dl').toggle(); 4 | }); 5 | 6 | $('a.help').click(function(){ 7 | $('div.help').toggle(); 8 | }); 9 | }); -------------------------------------------------------------------------------- /src/rfdoc/rfdocapp/templates/404.html: -------------------------------------------------------------------------------- 1 | {% extends "base.html" %} 2 | 3 | {% block title %}Page not found{% endblock title %} 4 | {% block content %} 5 |

Page not found

6 |

7 | The page you requested does not exist. 8 | Return to the front page. 9 |

10 | {% endblock content %} 11 | 12 | -------------------------------------------------------------------------------- /src/rfdoc/rfdocapp/templates/500.html: -------------------------------------------------------------------------------- 1 | {% extends "base.html" %} 2 | 3 | {% block title %}Internal error{% endblock title %} 4 | {% block content %} 5 |

Internal error occurred

6 |

7 | Enable debugging in application configuration to get more information. 8 |

9 | {% endblock content %} 10 | 11 | -------------------------------------------------------------------------------- /src/rfdoc/rfdocapp/templates/base.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 18 | {% load staticfiles %} 19 | 20 | 21 | RFDoc | {% block title %}{% endblock title %} 22 | 23 | 24 | 25 | 26 | 27 | 28 | 29 | 30 | 34 | 35 |
{% block content %}{% endblock content %}
36 | 37 | 46 | {% block outside %}{% endblock outside %} 47 | 48 | 49 | -------------------------------------------------------------------------------- /src/rfdoc/rfdocapp/templates/index.html: -------------------------------------------------------------------------------- 1 | {% extends "base.html" %} 2 | 3 | {% block title %}Welcome{% endblock title %} 4 | {% block navi %}[upload]{% endblock navi %} 5 | {% block content %} 6 |

Welcome

7 |

8 | RFDoc is a web application for storing and searching Robot Framework 9 | test library and resource file documentations. 10 |

11 | 12 |

13 | {% include "search_form.html" %} 14 |

15 | 16 |

Libraries

17 | {% if libs %} 18 | Sort by version 19 |
    20 | {% for lib in libs %}
  • {{ lib.name }} {% if lib.versions %}( {{ lib.versions|length }} versions ){% endif %}
  • 21 | {% endfor %}
22 | {% elif versions %} 23 | Sort by library name 24 |
25 | {% for version in versions %} 26 |
27 |

{{ version.version }}

28 |
    29 | {% for lib in version.libs %} 30 |
  • {{ lib }}
  • 31 | {% endfor %} 32 |
33 |
34 | {% endfor %} 35 |
36 |
 
37 | {% else %}

38 | No libraries in the system. Why don't you upload one? 39 |

40 | {%endif %} 41 | {% endblock content %} 42 | -------------------------------------------------------------------------------- /src/rfdoc/rfdocapp/templates/library.html: -------------------------------------------------------------------------------- 1 | {% extends "base.html" %} 2 | {% load rfdoc_filters %} 3 | 4 | {% block title %}{{ lib.name }}{% endblock title %} 5 | {% block content %} 6 |

{{ lib.name }}

7 |

8 | Version: {{ lib.version }} 9 |

10 |

Introduction

11 |

{{ lib.doc|safe }}

12 | {% if lib.inits %} 13 |

Importing

14 | 15 | 16 | 17 | 18 | 19 | {% for init in lib.inits %} 20 | 21 | 22 | 23 | 24 | {% endfor %} 25 |
ArgumentsDocumentation
{{ init.args }}{{ init.doc|safe }}
26 | {% endif %} 27 |

Shortcuts

28 |
{% for kw in lib.keywords %} 29 | {{ kw.name }}{% if not forloop.last %} · {% endif %}{% endfor %} 30 |
31 | 32 |

Keywords

33 | 34 | 35 | 36 | 37 | 38 | 39 | {% for kw in lib.keywords %} 40 | 41 | 42 | 43 | 44 | 45 | {% endfor %} 46 |
KeywordArgumentsDocumentation
{{ kw.name }}{{ kw.args }}{{ kw.doc|safe }}
47 |

48 |

Altogether {{ lib.keywords|length }} keywords.

49 | {% endblock content %} 50 | {% block outside %} 51 | {% if versions %} 52 |
53 | v: {{ lib.version }} 54 |
55 |
Other versions:
56 | {% for version in versions %} 57 |
{{ version }}
58 | {% endfor %} 59 |
60 |
61 | {% endif %} 62 | {% endblock outside %} 63 | 64 | -------------------------------------------------------------------------------- /src/rfdoc/rfdocapp/templates/search.html: -------------------------------------------------------------------------------- 1 | {% extends "base.html" %} 2 | {% load rfdoc_filters %} 3 | 4 | {% block title %}Search{% endblock title %} 5 | {% block content %}{% include "search_form.html" %}{% if search_performed %} 6 |

7 | 8 |

Search results

9 |

10 | {% if not kws %}No{% else %}{{ kws|length }}{% endif %} matching keyword{{ kws|length|pluralize }} found. 11 |

{% if kws %} 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | {% for kw in kws %} 20 | 21 | 22 | 23 | 24 | 25 | 26 | {% endfor %} 27 |
KeywordLibraryVersionDoc
{{ kw.name }}{{ kw.library.name }}{{ kw.library.version }}{{ kw.doc|first_line }}
{% endif %}{% endif %} 28 | {% endblock content %} 29 | -------------------------------------------------------------------------------- /src/rfdoc/rfdocapp/templates/search_form.html: -------------------------------------------------------------------------------- 1 |

Search keywords

2 |
3 | {{ form.search_term.errors }} 4 | 5 | 6 | 7 | 11 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | 23 | 24 | 28 | 29 | 30 | 31 | 32 |
33 |

Search options:

34 | 35 | 36 | 37 | 38 | 39 | 40 | 41 | 42 | 43 | 44 | 45 | 46 | 47 | 48 | 49 | 51 | 52 |
Search termText needs to only match a part of the keywords and/or documentation.
Also DocumentationSearch also keyword documentation.
Case-insensitiveMake the search term case-insensitive.
Version filterOnly match the versions matching the filter term. The term be an exact match but wild cards 50 | ? (one character) and * (any number of characters) are supported.
53 |
54 |
55 | -------------------------------------------------------------------------------- /src/rfdoc/rfdocapp/templates/upload.html: -------------------------------------------------------------------------------- 1 | {% extends "base.html" %} 2 | 3 | {% block title %}Upload{% endblock title %} 4 | {% block content %} 5 |

Upload documentation

6 |

7 | Select an XML file containing library or resource file 8 | documentation. These files can be created using 9 | libdoc.py 10 | tool. 11 |

12 | 13 |

14 | 15 |
{% if lib %} 16 |
17 | Successfully uploaded library {{ lib.name }}. 18 |
{% endif %}{{ form.file.errors }} 19 |
20 | {{ form.file }} 21 |
22 |
23 | 24 | 25 | 26 | 27 | 28 | 29 | 30 | 31 | 32 | 33 | 34 | 35 | 36 |
{{ form.override_name }}
{{ form.override_version }}
{{ form.override }}
37 |
38 |

39 | 40 |

41 |
42 | {% endblock content %} 43 | -------------------------------------------------------------------------------- /src/rfdoc/rfdocapp/templatetags/__init__.py: -------------------------------------------------------------------------------- 1 | # Copyright 2009-2013 Nokia Siemens Networks Oyj 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 | -------------------------------------------------------------------------------- /src/rfdoc/rfdocapp/templatetags/rfdoc_filters.py: -------------------------------------------------------------------------------- 1 | # Copyright 2009-2013 Nokia Siemens Networks Oyj 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 | from django import template 16 | from django.template.defaultfilters import stringfilter 17 | 18 | 19 | register = template.Library() 20 | 21 | @register.filter 22 | @stringfilter 23 | def nospaces(value): 24 | return value.replace(' ', '') 25 | 26 | @register.filter 27 | @stringfilter 28 | def first_line(value): 29 | return value.split('\n')[0] 30 | 31 | -------------------------------------------------------------------------------- /src/rfdoc/rfdocapp/tests.py: -------------------------------------------------------------------------------- 1 | # Copyright 2009-2013 Nokia Siemens Networks Oyj 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 unittest 16 | from StringIO import StringIO 17 | 18 | from rfdoc.rfdocapp.views.upload import LibraryData, InvalidXmlError 19 | 20 | 21 | VALID_SPEC = ''' 22 | 23 | 1.0 24 | This is documentation 25 | 26 | Init Doc 27 | 28 | arg1 29 | arg2=default 30 | 31 | 32 | 33 | Kw doc 34 | 35 | arg 36 | 37 | 38 | 39 | 40 | 41 | 42 | 43 | 44 | 45 | 46 | arg1 47 | arg2=default 48 | *args 49 | 50 | 51 | 52 | ''' 53 | 54 | 55 | class TestLibraryData(unittest.TestCase): 56 | 57 | def test_parsing_library_data(self): 58 | data = LibraryData(self._create_input(VALID_SPEC)) 59 | self.assertEqual(data.name, 'TestLibrary') 60 | self.assertEqual(data.doc, 'This is documentation') 61 | self.assertEqual(data.version, '1.0') 62 | 63 | def test_parsing_keyword_data(self): 64 | data = LibraryData(self._create_input(VALID_SPEC)) 65 | expected = [('KW 1', 'Kw doc', 'arg'), 66 | ('Keyword 2', '', ''), 67 | ('Another Keyword', '', 'arg1, arg2=default, *args')] 68 | self.assertEqual(len(data.kws), 3) 69 | for kw, (name, doc, args) in zip(data.kws, expected): 70 | self.assertEqual(kw.name, name) 71 | self.assertEqual(kw.doc, doc) 72 | self.assertEqual(kw.args, args) 73 | 74 | def test_parsing_init_data(self): 75 | data = LibraryData(self._create_input(VALID_SPEC)) 76 | self.assertEqual(len(data.inits), 1) 77 | self.assertEqual(data.inits[0].doc, 'Init Doc') 78 | self.assertEqual(data.inits[0].args, 'arg1, arg2=default') 79 | self.assertEqual(data.inits[0].name, '') 80 | 81 | def test_parsing_empty_documentations(self): 82 | data = LibraryData(self._create_input(''' 83 | 84 | 85 | 86 | ''')) 87 | self.assertEqual(data.doc, '') 88 | self.assertEqual(data.kws[0].doc, '') 89 | 90 | def test_parsing_spec_with_incomplete_data_fails(self): 91 | self._assert_parsing_fails('') 92 | 93 | def test_parsing_spec_without_keywords_fails(self): 94 | self._assert_parsing_fails('' 95 | '') 96 | 97 | def test_iterating_spec_with_incomplete_keyword_data_fails(self): 98 | self._assert_parsing_fails(''' 99 | 100 | 101 | 102 | 103 | ''') 104 | 105 | def test_parsing_non_xml_fails(self): 106 | self._assert_parsing_fails('This is not xml') 107 | 108 | def test_parsing_invalid_xml_fails(self): 109 | self._assert_parsing_fails('') 110 | 111 | def test_parsing_old_style_library_data(self): 112 | data = LibraryData(self._create_input(''' 113 | 114 | No version here 115 | KW 1 doc 116 | ''')) 117 | self.assertEqual(data.name, 'Test') 118 | self.assertEqual(data.doc, 'No version here') 119 | self.assertEqual(data.version, '') 120 | self.assertEqual(len(data.kws), 1) 121 | self.assertEqual(data.kws[0].name, 'KW 1') 122 | self.assertEqual(data.kws[0].doc, 'KW 1 doc') 123 | self.assertEqual(data.kws[0].args, '') 124 | 125 | def _assert_parsing_fails(self, data): 126 | self.assertRaises(InvalidXmlError, LibraryData, self._create_input(data)) 127 | 128 | def _create_input(self, data): 129 | return StringIO('\n' + data.strip()) 130 | 131 | 132 | if __name__ == '__main__': 133 | unittest.main() 134 | 135 | -------------------------------------------------------------------------------- /src/rfdoc/rfdocapp/utils/__init__.py: -------------------------------------------------------------------------------- 1 | # Copyright 2009-2013 Nokia Siemens Networks Oyj 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 | from robot_htmlutils import html_escape 16 | 17 | 18 | def normalize(string): 19 | return string.lower().replace(' ', '') 20 | 21 | def eq(str1, str2): 22 | return normalize(str1) == normalize(str2) 23 | 24 | def eq_any(string, strings): 25 | string = normalize(string) 26 | for s in strings: 27 | if normalize(s) == string: 28 | return True 29 | return False 30 | -------------------------------------------------------------------------------- /src/rfdoc/rfdocapp/utils/robot_htmlutils.py: -------------------------------------------------------------------------------- 1 | # Copyright 2008-2009 Nokia Siemens Networks Oyj 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 | # This file is copied from Robot Framework SVN repository and cleaned up a bit: 16 | # http://code.google.com/p/robotframework/source/browse/trunk/src/robot/utils/htmlutils.py?spec=svn1999&r=1922 17 | 18 | import os.path 19 | import re 20 | 21 | _hr_re = re.compile('^-{3,} *$') 22 | _bold_re = re.compile(''' 23 | ( # prefix (group 1) 24 | (\A|\ ) # begin of line or space 25 | ["'(]* _? # optionally any char "'( and optional begin of italic 26 | ) # 27 | \* # start of bold 28 | ([^\ ].*?) # no space and then anything (group 3) 29 | \* # end of bold 30 | (?= # start of postfix (non-capturing group) 31 | _? ["').,!?:;]* # optional end of italic and any char "').,!?:; 32 | (\Z|\ ) # end of line or space 33 | ) 34 | ''', re.VERBOSE) 35 | _italic_re = re.compile(''' 36 | ( (\A|\ ) ["'(]* ) # begin of line or space and opt. any char "'( 37 | _ # start of italic 38 | ([^\ _].*?) # no space or underline and then anything 39 | _ # end of italic 40 | (?= ["').,!?:;]* (\Z|\ ) ) # opt. any char "').,!?:; and end of line or space 41 | ''', re.VERBOSE) 42 | _url_re = re.compile(''' 43 | ( (\A|\ ) ["'([]* ) # begin of line or space and opt. any char "'([ 44 | (\w{3,9}://[\S]+?) # url (protocol is any alphanum 3-9 long string) 45 | (?= [])"'.,!?:;]* (\Z|\ ) ) # opt. any char ])"'.,!?:; and end of line or space 46 | ''', re.VERBOSE) 47 | 48 | 49 | def html_escape(text, formatting=False): 50 | for name, value in [('&', '&'), ('<', '<'), ('>', '>')]: 51 | text = text.replace(name, value) 52 | 53 | ret = [] 54 | table = _Table() 55 | hr = None 56 | 57 | for line in text.splitlines(): 58 | if formatting and table.is_table_row(line): 59 | if hr: 60 | ret.append(hr) 61 | hr = None 62 | table.add_row(line) 63 | elif table.is_started(): 64 | if _hr_re.match(line): 65 | hr = '
\n' 66 | line = '' 67 | else: 68 | line = _format_line(line, True) 69 | ret.append(table.end() + line) 70 | elif formatting and _hr_re.match(line): 71 | hr = '
\n' 72 | else: 73 | line = _format_line(line, formatting) 74 | if hr: 75 | line = hr + line 76 | hr = None 77 | ret.append(line) 78 | 79 | if table.is_started(): 80 | ret.append(table.end()) 81 | if hr: 82 | ret.append(hr) 83 | 84 | return '
\n'.join(ret) 85 | 86 | 87 | class _Table: 88 | 89 | _is_line = re.compile('^\s*\| (.* |)\|\s*$') 90 | _line_splitter = re.compile(' \|(?= )') 91 | 92 | def __init__(self): 93 | self._rows = [] 94 | 95 | def is_table_row(self, row): 96 | return self._is_line.match(row) is not None 97 | 98 | def add_row(self, text): 99 | text = text.strip()[1:-1] # remove outer whitespace and pipes 100 | cells = [ cell.strip() for cell in self._line_splitter.split(text) ] 101 | self._rows.append(cells) 102 | 103 | def end(self): 104 | ret = self._format(self._rows) 105 | self._rows = [] 106 | return ret 107 | 108 | def is_started(self): 109 | return len(self._rows) > 0 110 | 111 | def _format(self, rows): 112 | maxlen = max([ len(row) for row in rows ]) 113 | table = [''] 114 | for row in rows: 115 | row += [''] * (maxlen - len(row)) # fix ragged tables 116 | table.append('') 117 | table.extend([ '' % _format_line(cell, True) 118 | for cell in row ]) 119 | table.append('') 120 | table.append('
%s
\n') 121 | return '\n'.join(table) 122 | 123 | 124 | def _format_line(line, formatting=False): 125 | if formatting: 126 | line = _bold_re.sub('\\1\\3', line) 127 | line = _italic_re.sub('\\1\\3', line) 128 | line = _url_re.sub(lambda res: _repl_url(res, formatting), line) 129 | # Replace a tab with eight "hard" spaces, and two "soft" spaces with one 130 | # "hard" and one "soft" space (preserves spaces but allows wrapping) 131 | return line.replace('\t', ' '*8).replace(' ', '  ') 132 | 133 | 134 | def _repl_url(res, formatting): 135 | pre = res.group(1) 136 | url = res.group(3).replace('"', '"') 137 | if formatting and os.path.splitext(url)[1].lower() \ 138 | in ['.jpg', '.jpeg', '.png', '.gif', '.bmp']: 139 | return '%s' % (pre, url, url) 140 | return '%s%s' % (pre, url, url) 141 | -------------------------------------------------------------------------------- /src/rfdoc/rfdocapp/views/__init__.py: -------------------------------------------------------------------------------- 1 | # Copyright 2009-2013 Nokia Siemens Networks Oyj 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 | from index import index 16 | from library import library 17 | from search import search 18 | from upload import upload 19 | -------------------------------------------------------------------------------- /src/rfdoc/rfdocapp/views/index.py: -------------------------------------------------------------------------------- 1 | # Copyright 2009-2013 Nokia Siemens Networks Oyj 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 | from django.shortcuts import render_to_response 16 | 17 | from rfdoc.rfdocapp.models import Library 18 | from search import SearchForm 19 | 20 | 21 | def index(request): 22 | libs = None 23 | versions = None 24 | if request.GET.get('sort') == 'version': 25 | versions = Library.objects.values('version').distinct() 26 | for version in versions: 27 | version['libs'] = [lib.name for lib in Library.objects.filter(version=version['version'])] 28 | else: 29 | libs = Library.objects.values('name').distinct() 30 | for lib in libs: 31 | versioned_libs = Library.objects.filter(name=lib['name']) 32 | if len(versioned_libs) > 1: 33 | lib['versions'] = [library.version for library in versioned_libs] 34 | return render_to_response('index.html', { 35 | 'libs': libs, 36 | 'versions': versions, 37 | 'form': SearchForm() 38 | } 39 | ) 40 | -------------------------------------------------------------------------------- /src/rfdoc/rfdocapp/views/library.py: -------------------------------------------------------------------------------- 1 | # Copyright 2009-2013 Nokia Siemens Networks Oyj 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 re 16 | from django.shortcuts import render_to_response, get_list_or_404 17 | from django.http import Http404 18 | from django.utils.http import unquote 19 | 20 | from rfdoc.rfdocapp import utils 21 | from rfdoc.rfdocapp.models import Library 22 | 23 | 24 | def library(request, libname, version=None): 25 | libname = unquote(libname) 26 | if version: 27 | version = unquote(version) 28 | lib = Library.objects.filter(name=libname, version=version).get() 29 | else: 30 | lib = Library.objects.filter(name=libname).order_by('id')[0:1].get() 31 | if not lib: 32 | raise Http404('No library matches the given query.') 33 | libdoc = LibraryDoc(lib) 34 | versions = [x.version for x in Library.objects.filter(name=libname) if x.version != lib.version] 35 | return render_to_response('library.html', {'lib': libdoc, 'versions': versions}) 36 | 37 | 38 | class _DocHelper: 39 | # This code is adapted from libdoc.py, see 40 | # http://code.google.com/p/robotframework/wiki/LibraryDocumentationTool 41 | 42 | _name_regexp = re.compile("`(.+?)`") 43 | 44 | def _get_htmldoc(self): 45 | doc = utils.html_escape(self._doc, formatting=True) 46 | return self._name_regexp.sub(self._link_keywords, doc) 47 | 48 | def _link_keywords(self, res): 49 | name = res.group(1) 50 | keywords = self.keywords if isinstance(self, LibraryDoc) else self._library.keywords 51 | for kw in keywords: 52 | if utils.eq(name, kw.name): 53 | return '%s' %\ 54 | (kw.name.replace(' ', ''), name) 55 | if utils.eq_any(name, ['introduction', 'library introduction']): 56 | return '%s' % name 57 | if utils.eq_any(name, ['importing', 'library importing']): 58 | return '%s' % name 59 | return '%s' % name 60 | 61 | doc = property(_get_htmldoc) 62 | 63 | 64 | class LibraryDoc(_DocHelper): 65 | 66 | def __init__(self, libdata): 67 | self.name = libdata.name 68 | self._doc = libdata.doc 69 | self.version = libdata.version 70 | self.inits = [ KeywordDoc(initdata, self) 71 | for initdata in libdata.init_set.all() ] 72 | self.keywords = [ KeywordDoc(kwdata, self) 73 | for kwdata in libdata.keyword_set.all() ] 74 | 75 | 76 | class KeywordDoc(_DocHelper): 77 | 78 | def __init__(self, kwdata, library): 79 | self.name = kwdata.name 80 | self.args = kwdata.args 81 | self._doc = kwdata.doc 82 | self.shortdoc = self._doc.split('\n')[0] 83 | self._library = library 84 | 85 | -------------------------------------------------------------------------------- /src/rfdoc/rfdocapp/views/search.py: -------------------------------------------------------------------------------- 1 | # Copyright 2009-2013 Nokia Siemens Networks Oyj 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 re 16 | from django import forms 17 | from django.db.models import Q 18 | from django.shortcuts import render_to_response 19 | 20 | from rfdocapp.models import Keyword 21 | 22 | 23 | def search(request): 24 | search_performed = False 25 | kws = [] 26 | if request.method == 'POST': 27 | form = SearchForm(request.POST) 28 | if form.is_valid(): 29 | term = form.cleaned_data['search_term'] 30 | version = form.cleaned_data['search_version'] 31 | query = Q(name__icontains = term) 32 | if form.cleaned_data['include_doc']: 33 | query = query | Q(doc__icontains = term) 34 | # SQLite LIKEs are case-insensitive by default. 35 | # Thus using just name__contains wouldn't work expectedly. 36 | # To circumvent this, an additional query using regular expressions 37 | # is applied for the case-sensitive searches. 38 | if not form.cleaned_data['case_insensitive']: 39 | query = Q(name__regex = r'.*%s.*' % re.escape(term)) 40 | if form.cleaned_data['include_doc']: 41 | query = query | Q(doc__regex = r'.*%s.*' % re.escape(term)) 42 | kws = Keyword.objects.filter(query) 43 | if version: 44 | version = re.escape(version).replace('\?','.').replace('\*','.*') 45 | kws = kws.filter(library__version__regex=r'^%s$' % version) 46 | search_performed = True 47 | else: 48 | form = SearchForm() 49 | return render_to_response('search.html', { 50 | 'form': form, 51 | 'kws': kws, 52 | 'search_performed': search_performed 53 | } 54 | ) 55 | 56 | 57 | class SearchForm(forms.Form): 58 | search_term = forms.CharField(error_messages={'required': 'Search term is required!'}) 59 | search_version = forms.CharField(required=False) 60 | include_doc = forms.BooleanField(required=False, initial=True) 61 | case_insensitive = forms.BooleanField(required=False, initial=True) 62 | 63 | -------------------------------------------------------------------------------- /src/rfdoc/rfdocapp/views/upload.py: -------------------------------------------------------------------------------- 1 | # Copyright 2009-2013 Nokia Siemens Networks Oyj 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 | from django import forms 16 | from django.forms.util import ErrorList 17 | from django.shortcuts import render_to_response 18 | from xml.etree import cElementTree as ET 19 | 20 | from rfdoc.rfdocapp.models import Library 21 | 22 | 23 | def upload(request): 24 | lib = None 25 | if request.method == 'POST': 26 | form = UploadFileForm(request.POST, request.FILES) 27 | if form.is_valid(): 28 | lib = form.parse_kw_spec(request.FILES['file'], 29 | form.cleaned_data['override'], 30 | form.cleaned_data['override_name'].strip(), 31 | form.cleaned_data['override_version'].strip()) 32 | else: 33 | form = UploadFileForm() 34 | return render_to_response('upload.html', { 35 | 'form': form, 36 | 'lib': lib 37 | } 38 | ) 39 | 40 | 41 | class UploadFileForm(forms.Form): 42 | file = forms.FileField() 43 | file.widget.attrs['size'] = 40 44 | override = forms.BooleanField(required=False) 45 | override_name = forms.CharField(required=False) 46 | override_version = forms.CharField(required=False) 47 | override_version.widget.attrs['size'] = 10 48 | 49 | def parse_kw_spec(self, fileobj, override, override_name, override_version): 50 | try: 51 | libdata = LibraryData(fileobj) 52 | if override_version: 53 | libdata.version = override_version 54 | if override_name: 55 | libdata.name = override_name 56 | if Library.objects.filter(name=libdata.name).filter(version=libdata.version): 57 | if not override: 58 | raise InvalidXmlError("Library %s version %s already exists." % (libdata.name, libdata.version)) 59 | else: 60 | Library.objects.filter(name=libdata.name).filter(version=libdata.version).delete() 61 | lib = Library(name=libdata.name, doc=libdata.doc, version=libdata.version) 62 | lib.save() 63 | for init in libdata.inits: 64 | lib.init_set.create(doc=init.doc, args=init.args) 65 | for kw in libdata.kws: 66 | lib.keyword_set.create(name=kw.name, doc=kw.doc, args=kw.args) 67 | except InvalidXmlError, err: 68 | self._errors['file'] = ErrorList([str(err)]) 69 | return None 70 | return lib 71 | 72 | 73 | class LibraryData(object): 74 | 75 | def __init__(self, fileobj): 76 | self.filename = fileobj.name 77 | root = self._get_root(fileobj) 78 | try: 79 | self.name = self._get_name(root) 80 | self.version = self._get_version(root) 81 | self.doc = self._get_doc(root) 82 | except InvalidXmlError as e: 83 | raise InvalidXmlError('Library parsing error. Given file %s contains invalid XML: %s' % (self.filename, e)) 84 | self.inits = [ InitData(data, self.filename) for data in self._get_inits(root) ] 85 | self.kws = [ KeywordData(data, self.filename) for data in self._get_keywords(root) ] 86 | 87 | def _get_root(self, fileobj): 88 | try: 89 | root = ET.parse(fileobj).getroot() 90 | except SyntaxError as e: 91 | raise InvalidXmlError('Given file %s is not XML: %s' % (self.filename, e)) 92 | if root.tag != 'keywordspec': 93 | raise InvalidXmlError('Given file %s contains invalid XML: Root tag must be keywordspec' % self.filename) 94 | return root 95 | 96 | def _get_name(self, elem): 97 | return get_attr(elem, 'name') 98 | 99 | def _get_doc(self, elem): 100 | return get_child_element(elem, 'doc').text or '' 101 | 102 | def _get_version(self, elem): 103 | # libdoc.py didn't add version in 2.1 and earlier 104 | try: 105 | version_elem = get_child_element(elem, 'version') 106 | except InvalidXmlError: 107 | version = None 108 | else: 109 | version = version_elem.text 110 | return version or '' 111 | 112 | def _get_inits(self, elem): 113 | return elem.findall('init') 114 | 115 | def _get_keywords(self, elem): 116 | # 'keywords/kw' is backwards compatibility for libdoc.py 2.1 and earlier 117 | kws = elem.findall('keywords/kw') + elem.findall('kw') 118 | if not kws: 119 | raise InvalidXmlError('Given test library file %s contains no keywords.' % self.filename) 120 | return kws 121 | 122 | 123 | class KeywordData(object): 124 | 125 | def __init__(self, elem, filename): 126 | try: 127 | self.name = self._get_name(elem) 128 | self.doc = self._get_doc(elem) 129 | self.args = ', '.join(arg.text for arg in self._get_args(elem)) 130 | except InvalidXmlError as e: 131 | raise InvalidXmlError('Keyword parsing error. Given file %s contains invalid XML: %s' % (filename, e)) 132 | 133 | def _get_name(self, elem): 134 | return get_attr(elem, 'name') 135 | 136 | def _get_doc(self, elem): 137 | return get_child_element(elem, 'doc').text or '' 138 | 139 | def _get_args(self, elem): 140 | return get_child_element(elem, 'arguments').findall('arg') 141 | 142 | 143 | class InitData(KeywordData): 144 | 145 | def _get_name(self, elem): 146 | return '' 147 | 148 | 149 | def get_attr(elem, attr_name): 150 | attr = elem.get(attr_name) 151 | if not attr: 152 | raise InvalidXmlError('Attribute %s not found' % attr_name) 153 | return attr 154 | 155 | def get_child_element(elem, child_name): 156 | child = elem.find(child_name) 157 | if child is None: 158 | raise InvalidXmlError('Child element %s not found' % child_name) 159 | return child 160 | 161 | 162 | class InvalidXmlError(TypeError): 163 | pass 164 | 165 | -------------------------------------------------------------------------------- /src/rfdoc/rfdocsettings_defaults.py: -------------------------------------------------------------------------------- 1 | # Local RFDoc configuration file. 2 | # 3 | # Settings are first read from `rfdocsettings.py` and then from 4 | # `rfdocsettings_defaults.py`. Thus `rfdocsettings.py` only needs to have 5 | # the settings that are different to the defaults. 6 | # 7 | # Notice that `rfdocsettings.py` can be anywhere in the file system, 8 | # as long as it is in your PYTHONPATH when running RFDoc. 9 | 10 | 11 | # If PRODUCTION is True, static assets must be served by a separate web server. 12 | PRODUCTION = False 13 | 14 | # If DEBUG is True, stack traces are shown instead of normal 404 and 500 pages. 15 | DEBUG = False 16 | 17 | # Path to the SQLite3 database file. The default location is in system temporary 18 | # directory which can be automatically cleared. You should therefore always 19 | # change the path unless using RFDoc for testing purposes. 20 | import os, tempfile 21 | DATABASE_NAME = os.path.join(tempfile.gettempdir(), 'rfdoc.db') 22 | 23 | # A list of strings representing the host/domain names that this Django site 24 | # can serve. 25 | # 26 | # If this RFDoc instance is accessible from the public Internet, please set 27 | # this according to your hosts unless you want compromise security. 28 | # 29 | # For more information, see: 30 | # https://docs.djangoproject.com/en/1.6/ref/settings/#std:setting-ALLOWED_HOSTS 31 | ALLOWED_HOSTS = ['*'] 32 | 33 | # Local time zone for this installation. 34 | # 35 | # Choices are listed at http://en.wikipedia.org/wiki/List_of_tz_zones_by_name, 36 | # although not all choices may be available on all operating systems. 37 | # 38 | # If running on Windows, this must be set to the same as your system time zone. 39 | TIME_ZONE = 'Europe/Helsinki' 40 | 41 | 42 | # --- NO NEED TO EDIT BELOW --- 43 | if __name__ == "__main__": 44 | from sys import argv 45 | from shutil import copy 46 | from os.path import isdir, abspath 47 | if len(argv) != 2 or not isdir(argv[1]): 48 | exit("""Usage: python -m rfdoc.rfdocsettings_defaults SETTINGS_DIR 49 | 50 | SETTINGS_DIR Path to an existing directory where the settings file is 51 | created. This directory must be in your PYTHONPATH when 52 | running RFdoc. 53 | """) 54 | TARGET_FILE = os.path.join(argv[1], 'rfdocsettings.py') 55 | if os.path.exists(TARGET_FILE): 56 | msg = "File '%s' already exists. Override?" % TARGET_FILE 57 | if not raw_input('%s [y/N] ' % msg).lower() == 'y': 58 | exit('User aborted.\n') 59 | copy(__file__, TARGET_FILE) 60 | print "\nCreated the settings file '%s'.\n" \ 61 | "Make sure to have '%s' in your PYTHONPATH when running further manage commands."\ 62 | % (abspath(TARGET_FILE), abspath(argv[1])) 63 | -------------------------------------------------------------------------------- /src/rfdoc/settings.py: -------------------------------------------------------------------------------- 1 | # Django settings for RFDoc project. 2 | # 3 | # Local configuration should be done in `rfdocsettings.py` file as explained 4 | # in `rfdocsettings_defaults.py`. 5 | 6 | from os.path import dirname, join 7 | 8 | import rfdocsettings_defaults 9 | try: 10 | import rfdocsettings 11 | except ImportError: 12 | rfdocsettings = None 13 | 14 | 15 | # Helper method to get settings either from `rfdocsettings.py` 16 | # or `rfdocsettings_defaults.py`. 17 | def localsetting(name): 18 | try: 19 | return getattr(rfdocsettings, name) 20 | except AttributeError: 21 | return getattr(rfdocsettings_defaults, name) 22 | 23 | ## The following settings can be overridden in own settings 24 | 25 | PRODUCTION = localsetting('PRODUCTION') 26 | DEBUG = localsetting('DEBUG') 27 | TEMPLATE_DEBUG = DEBUG 28 | TIME_ZONE = localsetting('TIME_ZONE') 29 | DATABASES = { 30 | 'default': { 31 | 'ENGINE': 'django.db.backends.sqlite3', 32 | 'NAME': localsetting('DATABASE_NAME') 33 | } 34 | } 35 | ALLOWED_HOSTS = localsetting('ALLOWED_HOSTS') 36 | 37 | ### The following settings are not overridable 38 | 39 | _PROJECT_DIR = dirname(__file__) 40 | SITE_ID = 1 41 | USE_I18N = False 42 | SECRET_KEY = 'pmst_958#g=ks#i+(ci!pnf5=1b73@nf(c%h8)p&sc7wongki6' 43 | ROOT_URLCONF = 'rfdoc.urls' 44 | STATIC_URL = '/static/' 45 | STATIC_ROOT = join(_PROJECT_DIR, 'rfdocapp', 'static') 46 | TEMPLATE_DIRS = ( 47 | join(_PROJECT_DIR, 'rfdocapp', 'templates').replace('\\', '/') 48 | ) 49 | INSTALLED_APPS = ( 50 | 'django.contrib.auth', 51 | 'django.contrib.admin', 52 | 'django.contrib.contenttypes', 53 | 'django.contrib.messages', 54 | 'django.contrib.sessions', 55 | 'django.contrib.sites', 56 | 'django.contrib.staticfiles', 57 | 'rfdoc.rfdocapp' 58 | ) 59 | 60 | # CSRF removed from the defaults due to src/rfdoc/upload.py 61 | MIDDLEWARE_CLASSES = ( 62 | 'django.middleware.common.CommonMiddleware', 63 | 'django.contrib.sessions.middleware.SessionMiddleware', 64 | 'django.contrib.auth.middleware.AuthenticationMiddleware', 65 | 'django.contrib.messages.middleware.MessageMiddleware' 66 | ) 67 | -------------------------------------------------------------------------------- /src/rfdoc/upload.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python 2 | 3 | # Copyright 2008-2013 Nokia Siemens Networks Oyj 4 | # 5 | # Licensed under the Apache License, Version 2.0 (the "License"); 6 | # you may not use this file except in compliance with the License. 7 | # You may obtain a copy of the License at 8 | # 9 | # http://www.apache.org/licenses/LICENSE-2.0 10 | # 11 | # Unless required by applicable law or agreed to in writing, software 12 | # distributed under the License is distributed on an "AS IS" BASIS, 13 | # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 14 | # See the License for the specific language governing permissions and 15 | # limitations under the License. 16 | 17 | from __future__ import with_statement 18 | 19 | import os 20 | import sys 21 | 22 | from contextlib import closing 23 | from HTMLParser import HTMLParser 24 | from httplib import HTTPConnection 25 | from optparse import OptionParser 26 | from re import match 27 | from StringIO import StringIO 28 | from urlparse import urlsplit 29 | 30 | from robot.errors import DataError 31 | from robot.libdocpkg import LibraryDocumentation 32 | from robot.parsing.populators import READERS 33 | 34 | 35 | class Uploader(object): 36 | 37 | def __init__(self): 38 | self._options = CommandlineUI() 39 | self._uploader = XmlUploader(self._options.target_url) 40 | 41 | def run(self): 42 | failed = [] 43 | for library in self._options.libraries: 44 | try: 45 | xml_doc = StringIO() 46 | # LibraryDocumentation().save() calls close() for the underlying 47 | # file but closing StringIO object discards its data. 48 | # This is why close() is overridden below. 49 | xml_doc.original_close = xml_doc.close 50 | try: 51 | try: 52 | if library.endswith('.xml'): 53 | with open(library) as xml_file: 54 | xml_doc.write(xml_file.read()) 55 | else: 56 | xml_doc.close = lambda: None 57 | LibraryDocumentation(library).save(xml_doc, 'xml') 58 | except DataError, e: 59 | message = "Library not found" if 'ImportError' in e.message else e.message 60 | failed.append(library) 61 | sys.stderr.write("Skipping '%s' due to an error: %s.\n" % 62 | (library, message)) 63 | continue 64 | xml_doc.name = library 65 | self._uploader.upload_file(xml_doc, self._options.lib_name, self._options.lib_version) 66 | sys.stdout.write("Updated documentation for '%s'.\n" % library) 67 | finally: 68 | xml_doc.original_close() 69 | except DataError, e: 70 | failed.append(library) 71 | sys.stderr.write('%s: Remote error: %s\n' % (os.path.basename(__file__), 72 | e.message)) 73 | if failed: 74 | sys.stderr.write('\nERROR: Uploading %d file(s) failed:\n' % len(failed)) 75 | for name in failed: 76 | sys.stderr.write('* %s\n' % name) 77 | exit(1) 78 | 79 | class ImprovedOptionParser(OptionParser): 80 | 81 | # This prevents newlines from getting discarded from the help text. 82 | def format_description(self, formatter): 83 | return self.description 84 | 85 | # This adds "try --help for information" message. 86 | def error(self, message): 87 | progname = os.path.basename(sys.argv[0]) 88 | sys.stderr.write('%s: error: %s\n'% (progname, message)) 89 | sys.stderr.write("Try '%s --help' for more information.\n" % progname) 90 | exit(2) 91 | 92 | 93 | class CommandlineUI(object): 94 | 95 | valid_lib_exts = tuple(READERS.keys() + ['py', 'java', 'xml']) 96 | default_url = 'localhost:8000' 97 | usage_text = 'usage: %prog [options] PATH ...' 98 | help_text = """ 99 | This script updates the documentation at Robot Framework RFDoc server. 100 | 101 | PATH is one of the following (multiple can be given, separated by a space): 102 | 1) A path to a library source file (e.g. src/libraries/example_lib.py) 103 | 2) A path to a resource file (e.g. atest/resources/utils.txt) 104 | 3) A path to a library XML, generated using LibDoc (e.g. libdoc/BuiltIn.xml) 105 | 4) A path to a directory, in which case it's recursively traversed for any of 106 | files mentioned in 1-3. 107 | """[1:] 108 | epilog_text = """ 109 | The script is intended to be used as part of the CI pipeline or as an SCM 110 | post-change hook to update the documentation in RFDoc automatically. 111 | """[1:] 112 | 113 | def __init__(self): 114 | self._parser = ImprovedOptionParser( 115 | usage=self.usage_text, 116 | description=self.help_text, 117 | epilog=self.epilog_text 118 | ) 119 | self._add_commandline_options() 120 | self._options = self._get_validated_options() 121 | 122 | @property 123 | def target_url(self): 124 | return self._options.target_url 125 | 126 | @property 127 | def libraries(self): 128 | return self._options.libraries 129 | 130 | @property 131 | def lib_version(self): 132 | return self._options.lib_version 133 | 134 | @property 135 | def lib_name(self): 136 | return self._options.lib_name 137 | 138 | def _add_commandline_options(self): 139 | self._parser.add_option( 140 | '-u', '--url', 141 | dest='target_url', 142 | default=self.default_url, 143 | help="""Target RFDoc URL to update, e.g. '192.168.1.100:8000' or 144 | 'my.server.com/rfdoc'. If this option is not given, '%s' is assumed 145 | as target.""" % self.default_url 146 | ) 147 | self._parser.add_option( 148 | '-v', '--version', 149 | dest='lib_version', 150 | default=None, 151 | help="""Override library version""") 152 | self._parser.add_option( 153 | '-n', '--name', 154 | dest='lib_name', 155 | default=None, 156 | help="""Override library name""") 157 | 158 | def _get_validated_options(self): 159 | options, targets = self._parser.parse_args() 160 | if len(targets) < 1: 161 | self._exit_with_help() 162 | options.libraries = self._traverse_targets_for_libraries(targets) 163 | options.target_url = self._host_from_url(options.target_url) 164 | return options 165 | 166 | def _traverse_targets_for_libraries(self, targets): 167 | libraries = targets 168 | for directory in self._only_directories(targets): 169 | libraries.remove(directory) 170 | for path, _, filenames in os.walk(directory): 171 | for filename in self._only_library_files(filenames): 172 | libraries.append(os.path.join(path, filename)) 173 | return libraries 174 | 175 | def _only_directories(self, targets): 176 | for target in targets: 177 | if os.path.isdir(target): 178 | yield target 179 | 180 | def _only_library_files(self, filenames): 181 | for filename in filenames: 182 | if self._is_library_file(filename): 183 | yield filename 184 | 185 | def _is_library_file(self, filename): 186 | return any(filename.endswith(ext) for ext in self.valid_lib_exts) 187 | 188 | def _host_from_url(self, url): 189 | if not match(r'http(s?)\:', url): 190 | url = 'http://' + url 191 | return url 192 | 193 | def _exit_with_help(self): 194 | self._parser.print_help() 195 | exit(1) 196 | 197 | 198 | class XmlUploader(object): 199 | default_endpoint = 'upload' 200 | required_content_template = """--%(boundary)s 201 | Content-Disposition: form-data; name="override" 202 | 203 | on 204 | --%(boundary)s 205 | Content-Disposition: form-data; name="file"; filename="%(filename)s" 206 | Content-Type: text/xml 207 | 208 | %(content)s 209 | """ 210 | optional_field_template = """--%(boundary)s 211 | Content-Disposition: form-data; name="%(name)s" 212 | 213 | %(content)s 214 | """ 215 | end_boundary_template = '--%(boundary)s--\n' 216 | 217 | def __init__(self, target_url): 218 | self.url = urlsplit(target_url) 219 | 220 | def upload_file(self, xml_doc, override_name=None, override_version=None): 221 | with closing(HTTPConnection(self.url.netloc)) as connection: 222 | try: 223 | response = self._post_multipart(connection, xml_doc, override_name, override_version) 224 | except Exception, message: 225 | if 'Connection refused' in message: 226 | message = "connection refused to '%s', " % self.url.netloc 227 | message += 'check that the host responds and is reachable.' 228 | raise DataError(message) 229 | self._validate_success(response) 230 | 231 | def _post_multipart(self, connection, xml_doc, override_name, override_version): 232 | connection.connect() 233 | content_type, body = self._encode_multipart_formdata(xml_doc, override_name, override_version) 234 | headers = {'User-Agent': 'RFDoc uploader', 'Content-Type': content_type} 235 | connection.request('POST', self._get_endpoint(), body, headers) 236 | return connection.getresponse() 237 | 238 | def _get_endpoint(self): 239 | return self.url.path + '/' + self.default_endpoint 240 | 241 | def _encode_multipart_formdata(self, xml_doc, override_name, override_version): 242 | boundary = '----------ThIs_Is_tHe_bouNdaRY_$' 243 | body = self.required_content_template % { 244 | 'boundary': boundary, 245 | 'filename': xml_doc.name, 246 | 'content': xml_doc.getvalue() 247 | } 248 | if override_name: 249 | body += self.optional_field_template % { 250 | 'boundary': boundary, 251 | 'name': 'override_name', 252 | 'content': override_name 253 | } 254 | if override_version: 255 | body += self.optional_field_template % { 256 | 'boundary': boundary, 257 | 'name': 'override_version', 258 | 'content': override_version 259 | } 260 | 261 | body += self.end_boundary_template % {'boundary': boundary} 262 | content_type = 'multipart/form-data; boundary=%s' % boundary 263 | return content_type, body.replace('\n', '\r\n') 264 | 265 | def _validate_success(self, response): 266 | if response.status != 200: 267 | raise DataError(response.reason.strip()) 268 | html = response.read() 269 | if 'Successfully uploaded library' not in html: 270 | raise DataError('\n'.join(self._ErrorParser(html).errors)) 271 | 272 | class _ErrorParser(HTMLParser): 273 | 274 | def __init__(self, html): 275 | HTMLParser.__init__(self) 276 | self._inside_errors = False 277 | self.errors = [] 278 | self.feed(html) 279 | self.close() 280 | 281 | def handle_starttag(self, tag, attributes): 282 | if ('class', 'errorlist') in attributes: 283 | self._inside_errors = True 284 | 285 | def handle_endtag(self, tag): 286 | if tag == 'ul': 287 | self._inside_errors = False 288 | 289 | def handle_data(self, data): 290 | if self._inside_errors and data.strip(): 291 | self.errors.append(data) 292 | 293 | 294 | if __name__ == '__main__': 295 | Uploader().run() 296 | -------------------------------------------------------------------------------- /src/rfdoc/urls.py: -------------------------------------------------------------------------------- 1 | from django.conf.urls import include, patterns, url 2 | from django.contrib import admin 3 | 4 | from rfdoc import settings 5 | from rfdoc.rfdocapp import views 6 | 7 | admin.autodiscover() 8 | 9 | urlpatterns = patterns('', 10 | url(r'^admin/', include(admin.site.urls), name='admin'), 11 | url(r'^upload/?$', views.upload, name='upload'), 12 | url(r'^search/?$', views.search, name='search'), 13 | url(r'^lib/([^/]*)$', views.library, name='library'), 14 | url(r'^lib/(.*)/(.*)$', views.library, name='version'), 15 | url(r'^$', views.index, name='root') 16 | ) 17 | 18 | # Force Django to serve static assets, if this is not the production 19 | if settings.PRODUCTION is False: 20 | urlpatterns += patterns('', ( 21 | r'^static/(?P.*)$', 'django.views.static.serve', { 22 | 'document_root': settings.STATIC_ROOT 23 | } 24 | )) 25 | -------------------------------------------------------------------------------- /src/rfdoc/version.py: -------------------------------------------------------------------------------- 1 | VERSION = '0.5' 2 | --------------------------------------------------------------------------------