Robot Framework
testdoc.py
Go to the documentation of this file.
1 #!/usr/bin/env python
2 
3 # Copyright 2008-2015 Nokia Networks
4 # Copyright 2016- Robot Framework Foundation
5 #
6 # Licensed under the Apache License, Version 2.0 (the "License");
7 # you may not use this file except in compliance with the License.
8 # You may obtain a copy of the License at
9 #
10 # http://www.apache.org/licenses/LICENSE-2.0
11 #
12 # Unless required by applicable law or agreed to in writing, software
13 # distributed under the License is distributed on an "AS IS" BASIS,
14 # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
15 # See the License for the specific language governing permissions and
16 # limitations under the License.
17 
18 
31 
32 import os.path
33 import sys
34 import time
35 
36 # Allows running as a script. __name__ check needed with multiprocessing:
37 # https://github.com/robotframework/robotframework/issues/1137
38 if 'robot' not in sys.modules and __name__ == '__main__':
39  import pythonpathsetter
40 
41 from robot.conf import RobotSettings
42 from robot.htmldata import HtmlFileWriter, ModelWriter, JsonWriter, TESTDOC
43 from robot.running import TestSuiteBuilder
44 from robot.utils import (abspath, Application, file_writer, get_link_path,
45  html_escape, html_format, is_string, secs_to_timestr,
46  seq2str2, timestr_to_secs, unescape)
47 
48 
49 USAGE = """robot.testdoc -- Robot Framework test data documentation tool
50 
51 Version: <VERSION>
52 
53 Usage: python -m robot.testdoc [options] data_sources output_file
54 
55 Testdoc generates a high level test documentation based on Robot Framework
56 test data. Generated documentation includes name, documentation and other
57 metadata of each test suite and test case, as well as the top-level keywords
58 and their arguments.
59 
60 Options
61 =======
62 
63  -T --title title Set the title of the generated documentation.
64  Underscores in the title are converted to spaces.
65  The default title is the name of the top level suite.
66  -N --name name Override the name of the top level suite.
67  -D --doc document Override the documentation of the top level suite.
68  -M --metadata name:value * Set/override metadata of the top level suite.
69  -G --settag tag * Set given tag(s) to all test cases.
70  -t --test name * Include tests by name.
71  -s --suite name * Include suites by name.
72  -i --include tag * Include tests by tags.
73  -e --exclude tag * Exclude tests by tags.
74  -A --argumentfile path * Text file to read more arguments from. Use special
75  path `STDIN` to read contents from the standard input
76  stream. File can have both options and data sources
77  one per line. Contents do not need to be escaped but
78  spaces in the beginning and end of lines are removed.
79  Empty lines and lines starting with a hash character
80  (#) are ignored.
81  Example file:
82  | --name Example
83  | # This is a comment line
84  | my_tests.robot
85  | output.html
86  Examples:
87  --argumentfile argfile.txt --argumentfile STDIN
88  -h -? --help Print this help.
89 
90 All options except --title have exactly same semantics as same options have
91 when executing test cases.
92 
93 Execution
94 =========
95 
96 Data can be given as a single file, directory, or as multiple files and
97 directories. In all these cases, the last argument must be the file where
98 to write the output. The output is always created in HTML format.
99 
100 Testdoc works with all interpreters supported by Robot Framework.
101 It can be executed as an installed module like
102 `python -m robot.testdoc` or as a script like `python path/robot/testdoc.py`.
103 
104 Examples:
105 
106  python -m robot.testdoc my_test.robot testdoc.html
107  python path/to/robot/testdoc.py first_suite.txt second_suite.txt output.html
108 
109 For more information about Testdoc and other built-in tools, see
110 http://robotframework.org/robotframework/#built-in-tools.
111 """
112 
113 
114 class TestDoc(Application):
115 
116  def __init__(self):
117  Application.__init__(self, USAGE, arg_limits=(2,))
118 
119  def main(self, datasources, title=None, **options):
120  outfile = abspath(datasources.pop())
121  suite = TestSuiteFactory(datasources, **options)
122  self._write_test_doc_write_test_doc(suite, outfile, title)
123  self.console(outfile)
124 
125  def _write_test_doc(self, suite, outfile, title):
126  with file_writer(outfile, usage='Testdoc output') as output:
127  model_writer = TestdocModelWriter(output, suite, title)
128  HtmlFileWriter(output, model_writer).write(TESTDOC)
129 
130 
131 def TestSuiteFactory(datasources, **options):
132  settings = RobotSettings(options)
133  if is_string(datasources):
134  datasources = [datasources]
135  suite = TestSuiteBuilder(process_curdir=False).build(*datasources)
136  suite.configure(**settings.suite_config)
137  return suite
138 
139 
141 
142  def __init__(self, output, suite, title=None):
143  self._output_output = output
144  self._output_path_output_path = getattr(output, 'name', None)
145  self._suite_suite = suite
146  self._title_title = title.replace('_', ' ') if title else suite.name
147 
148  def write(self, line):
149  self._output_output.write('<script type="text/javascript">\n')
150  self.write_datawrite_data()
151  self._output_output.write('</script>\n')
152 
153  def write_data(self):
154  model = {
155  'suite': JsonConverter(self._output_path_output_path).convert(self._suite_suite),
156  'title': self._title_title,
157  'generated': int(time.time() * 1000)
158  }
159  JsonWriter(self._output_output).write_json('testdoc = ', model)
160 
161 
163 
164  def __init__(self, output_path=None):
165  self._output_path_output_path = output_path
166 
167  def convert(self, suite):
168  return self._convert_suite_convert_suite(suite)
169 
170  def _convert_suite(self, suite):
171  return {
172  'source': suite.source or '',
173  'relativeSource': self._get_relative_source_get_relative_source(suite.source),
174  'id': suite.id,
175  'name': self._escape_escape(suite.name),
176  'fullName': self._escape_escape(suite.longname),
177  'doc': self._html_html(suite.doc),
178  'metadata': [(self._escape_escape(name), self._html_html(value))
179  for name, value in suite.metadata.items()],
180  'numberOfTests': suite.test_count ,
181  'suites': self._convert_suites_convert_suites(suite),
182  'tests': self._convert_tests_convert_tests(suite),
183  'keywords': list(self._convert_keywords_convert_keywords((suite.setup, suite.teardown)))
184  }
185 
186  def _get_relative_source(self, source):
187  if not source or not self._output_path_output_path:
188  return ''
189  return get_link_path(source, os.path.dirname(self._output_path_output_path))
190 
191  def _escape(self, item):
192  return html_escape(item)
193 
194  def _html(self, item):
195  return html_format(unescape(item))
196 
197  def _convert_suites(self, suite):
198  return [self._convert_suite_convert_suite(s) for s in suite.suites]
199 
200  def _convert_tests(self, suite):
201  return [self._convert_test_convert_test(t) for t in suite.tests]
202 
203  def _convert_test(self, test):
204  if test.setup:
205  test.body.insert(0, test.setup)
206  if test.teardown:
207  test.body.append(test.teardown)
208  return {
209  'name': self._escape_escape(test.name),
210  'fullName': self._escape_escape(test.longname),
211  'id': test.id,
212  'doc': self._html_html(test.doc),
213  'tags': [self._escape_escape(t) for t in test.tags],
214  'timeout': self._get_timeout_get_timeout(test.timeout),
215  'keywords': list(self._convert_keywords_convert_keywords(test.body))
216  }
217 
218  def _convert_keywords(self, keywords):
219  for kw in keywords:
220  if not kw:
221  continue
222  if kw.type == kw.SETUP:
223  yield self._convert_keyword_convert_keyword(kw, 'SETUP')
224  elif kw.type == kw.TEARDOWN:
225  yield self._convert_keyword_convert_keyword(kw, 'TEARDOWN')
226  elif kw.type == kw.FOR:
227  yield self._convert_for_convert_for(kw)
228  elif kw.type == kw.WHILE:
229  yield self._convert_while_convert_while(kw)
230  elif kw.type == kw.IF_ELSE_ROOT:
231  yield from self._convert_if_convert_if(kw)
232  elif kw.type == kw.TRY_EXCEPT_ROOT:
233  yield from self._convert_try_convert_try(kw)
234  else:
235  yield self._convert_keyword_convert_keyword(kw, 'KEYWORD')
236 
237  def _convert_for(self, data):
238  name = '%s %s %s' % (', '.join(data.variables), data.flavor,
239  seq2str2(data.values))
240  return {'type': 'FOR', 'name': self._escape_escape(name), 'arguments': ''}
241 
242  def _convert_while(self, data):
243  return {'type': 'WHILE', 'name': self._escape_escape(data.condition), 'arguments': ''}
244 
245  def _convert_if(self, data):
246  for branch in data.body:
247  yield {'type': branch.type,
248  'name': self._escape_escape(branch.condition or ''),
249  'arguments': ''}
250 
251  def _convert_try(self, data):
252  for branch in data.body:
253  if branch.type == branch.EXCEPT:
254  patterns = ', '.join(branch.patterns)
255  as_var = f'AS {branch.variable}' if branch.variable else ''
256  name = f'{patterns} {as_var}'.strip()
257  else:
258  name = ''
259  yield {'type': branch.type, 'name': name, 'arguments': ''}
260 
261  def _convert_keyword(self, kw, kw_type):
262  return {
263  'type': kw_type,
264  'name': self._escape_escape(self._get_kw_name_get_kw_name(kw)),
265  'arguments': self._escape_escape(', '.join(kw.args))
266  }
267 
268  def _get_kw_name(self, kw):
269  if kw.assign:
270  return '%s = %s' % (', '.join(a.rstrip('= ') for a in kw.assign), kw.name)
271  return kw.name
272 
273  def _get_timeout(self, timeout):
274  if timeout is None:
275  return ''
276  try:
277  tout = secs_to_timestr(timestr_to_secs(timeout))
278  except ValueError:
279  tout = timeout
280  return tout
281 
282 
283 
297 def testdoc_cli(arguments):
298  TestDoc().execute_cli(arguments)
299 
300 
301 
312 def testdoc(*arguments, **options):
313  TestDoc().execute(*arguments, **options)
314 
315 
316 if __name__ == '__main__':
317  testdoc_cli(sys.argv[1:])
def _convert_suites(self, suite)
Definition: testdoc.py:197
def _convert_if(self, data)
Definition: testdoc.py:245
def _convert_suite(self, suite)
Definition: testdoc.py:170
def _get_relative_source(self, source)
Definition: testdoc.py:186
def convert(self, suite)
Definition: testdoc.py:167
def _convert_while(self, data)
Definition: testdoc.py:242
def _get_timeout(self, timeout)
Definition: testdoc.py:273
def _convert_try(self, data)
Definition: testdoc.py:251
def __init__(self, output_path=None)
Definition: testdoc.py:164
def _convert_test(self, test)
Definition: testdoc.py:203
def _convert_tests(self, suite)
Definition: testdoc.py:200
def _get_kw_name(self, kw)
Definition: testdoc.py:268
def _convert_keyword(self, kw, kw_type)
Definition: testdoc.py:261
def _html(self, item)
Definition: testdoc.py:194
def _convert_for(self, data)
Definition: testdoc.py:237
def _convert_keywords(self, keywords)
Definition: testdoc.py:218
def _escape(self, item)
Definition: testdoc.py:191
def _write_test_doc(self, suite, outfile, title)
Definition: testdoc.py:125
def __init__(self)
Definition: testdoc.py:116
def main(self, datasources, title=None, **options)
Definition: testdoc.py:119
def __init__(self, output, suite, title=None)
Definition: testdoc.py:142
def write(msg, level='INFO', html=False)
Writes the message to the log file using the given level.
Definition: logger.py:84
def testdoc_cli(arguments)
Executes Testdoc similarly as from the command line.
Definition: testdoc.py:297
def testdoc(*arguments, **options)
Executes Testdoc programmatically.
Definition: testdoc.py:312
def TestSuiteFactory(datasources, **options)
Definition: testdoc.py:131
def html_escape(text, linkify=True)
Definition: markuputils.py:44
def seq2str2(sequence)
Returns sequence in format [ item 1 | item 2 | ...
Definition: misc.py:90
def file_writer(path=None, encoding='UTF-8', newline=None, usage=None)
Definition: robotio.py:25
def abspath(path, case_normalize=False)
Replacement for os.path.abspath with some enhancements and bug fixes.
Definition: robotpath.py:65
def get_link_path(target, base)
Returns a relative path to target from base.
Definition: robotpath.py:78
def secs_to_timestr(secs, compact=False)
Converts time in seconds to a string representation.
Definition: robottime.py:151
def timestr_to_secs(timestr, round_to=3, accept_plain_values=True)
Parses time strings like '1h 10s', '01:00:10' and '42' and returns seconds.
Definition: robottime.py:55