diff -up behave-1.2.3/behave/compat/collections.py.HTMLformatter behave-1.2.3/behave/compat/collections.py --- behave-1.2.3/behave/compat/collections.py.HTMLformatter 2013-04-19 22:35:23.000000000 +0200 +++ behave-1.2.3/behave/compat/collections.py 2013-10-29 18:17:28.944010009 +0100 @@ -18,3 +18,188 @@ except ImportError: # pragma: no cov warnings.warn(message) # -- BACKWARD-COMPATIBLE: Better than nothing (for behave use case). OrderedDict = dict + +try: + # -- SINCE: Python2.7 + from collections import Counter +except ImportError: # pragma: no cover + class Counter(dict): + '''Dict subclass for counting hashable objects. Sometimes called a bag + or multiset. Elements are stored as dictionary keys and their counts + are stored as dictionary values. + + >>> Counter('zyzygy') + Counter({'y': 3, 'z': 2, 'g': 1}) + + ''' + + def __init__(self, iterable=None, **kwds): + '''Create a new, empty Counter object. And if given, count elements + from an input iterable. Or, initialize the count from another mapping + of elements to their counts. + + >>> c = Counter() # a new, empty counter + >>> c = Counter('gallahad') # a new counter from an iterable + >>> c = Counter({'a': 4, 'b': 2}) # a new counter from a mapping + >>> c = Counter(a=4, b=2) # a new counter from keyword args + + ''' + self.update(iterable, **kwds) + + def __missing__(self, key): + return 0 + + def most_common(self, n=None): + '''List the n most common elements and their counts from the most + common to the least. If n is None, then list all element counts. + + >>> Counter('abracadabra').most_common(3) + [('a', 5), ('r', 2), ('b', 2)] + + ''' + if n is None: + return sorted(self.iteritems(), key=itemgetter(1), reverse=True) + return nlargest(n, self.iteritems(), key=itemgetter(1)) + + def elements(self): + '''Iterator over elements repeating each as many times as its count. + + >>> c = Counter('ABCABC') + >>> sorted(c.elements()) + ['A', 'A', 'B', 'B', 'C', 'C'] + + If an element's count has been set to zero or is a negative number, + elements() will ignore it. + + ''' + for elem, count in self.iteritems(): + for _ in repeat(None, count): + yield elem + + # Override dict methods where the meaning changes for Counter objects. + + @classmethod + def fromkeys(cls, iterable, v=None): + raise NotImplementedError( + 'Counter.fromkeys() is undefined. Use Counter(iterable) instead.') + + def update(self, iterable=None, **kwds): + '''Like dict.update() but add counts instead of replacing them. + + Source can be an iterable, a dictionary, or another Counter instance. + + >>> c = Counter('which') + >>> c.update('witch') # add elements from another iterable + >>> d = Counter('watch') + >>> c.update(d) # add elements from another counter + >>> c['h'] # four 'h' in which, witch, and watch + 4 + + ''' + if iterable is not None: + if hasattr(iterable, 'iteritems'): + if self: + self_get = self.get + for elem, count in iterable.iteritems(): + self[elem] = self_get(elem, 0) + count + else: + dict.update(self, iterable) # fast path when counter is empty + else: + self_get = self.get + for elem in iterable: + self[elem] = self_get(elem, 0) + 1 + if kwds: + self.update(kwds) + + def copy(self): + 'Like dict.copy() but returns a Counter instance instead of a dict.' + return Counter(self) + + def __delitem__(self, elem): + 'Like dict.__delitem__() but does not raise KeyError for missing values.' + if elem in self: + dict.__delitem__(self, elem) + + def __repr__(self): + if not self: + return '%s()' % self.__class__.__name__ + items = ', '.join(map('%r: %r'.__mod__, self.most_common())) + return '%s({%s})' % (self.__class__.__name__, items) + + # Multiset-style mathematical operations discussed in: + # Knuth TAOCP Volume II section 4.6.3 exercise 19 + # and at http://en.wikipedia.org/wiki/Multiset + # + # Outputs guaranteed to only include positive counts. + # + # To strip negative and zero counts, add-in an empty counter: + # c += Counter() + + def __add__(self, other): + '''Add counts from two counters. + + >>> Counter('abbb') + Counter('bcc') + Counter({'b': 4, 'c': 2, 'a': 1}) + + + ''' + if not isinstance(other, Counter): + return NotImplemented + result = Counter() + for elem in set(self) | set(other): + newcount = self[elem] + other[elem] + if newcount > 0: + result[elem] = newcount + return result + + def __sub__(self, other): + ''' Subtract count, but keep only results with positive counts. + + >>> Counter('abbbc') - Counter('bccd') + Counter({'b': 2, 'a': 1}) + + ''' + if not isinstance(other, Counter): + return NotImplemented + result = Counter() + for elem in set(self) | set(other): + newcount = self[elem] - other[elem] + if newcount > 0: + result[elem] = newcount + return result + + def __or__(self, other): + '''Union is the maximum of value in either of the input counters. + + >>> Counter('abbb') | Counter('bcc') + Counter({'b': 3, 'c': 2, 'a': 1}) + + ''' + if not isinstance(other, Counter): + return NotImplemented + _max = max + result = Counter() + for elem in set(self) | set(other): + newcount = _max(self[elem], other[elem]) + if newcount > 0: + result[elem] = newcount + return result + + def __and__(self, other): + ''' Intersection is the minimum of corresponding counts. + + >>> Counter('abbb') & Counter('bcc') + Counter({'b': 1}) + + ''' + if not isinstance(other, Counter): + return NotImplemented + _min = min + result = Counter() + if len(self) < len(other): + self, other = other, self + for elem in ifilter(self.__contains__, other): + newcount = _min(self[elem], other[elem]) + if newcount > 0: + result[elem] = newcount + return result diff -up behave-1.2.3/behave/configuration.py.HTMLformatter behave-1.2.3/behave/configuration.py --- behave-1.2.3/behave/configuration.py.HTMLformatter 2013-07-03 21:51:08.000000000 +0200 +++ behave-1.2.3/behave/configuration.py 2013-10-29 18:17:28.944010009 +0100 @@ -4,6 +4,7 @@ import os import re import sys import argparse +import codecs import ConfigParser from behave.reporter.junit import JUnitReporter diff -up behave-1.2.3/behave/formatter/formatters.py.HTMLformatter behave-1.2.3/behave/formatter/formatters.py --- behave-1.2.3/behave/formatter/formatters.py.HTMLformatter 2013-06-27 00:19:16.000000000 +0200 +++ behave-1.2.3/behave/formatter/formatters.py 2013-10-29 18:17:28.945010011 +0100 @@ -76,6 +76,7 @@ def setup_formatters(): register_as(_L("behave.formatter.steps:StepsUsageFormatter"), "steps.usage") register_as(_L("behave.formatter.sphinx_steps:SphinxStepsFormatter"), "sphinx.steps") + register_as(_L("behave.formatter.html:HTMLFormatter"), "html") # ----------------------------------------------------------------------------- diff -up behave-1.2.3/behave/formatter/html.py.HTMLformatter behave-1.2.3/behave/formatter/html.py --- behave-1.2.3/behave/formatter/html.py.HTMLformatter 2013-10-29 18:17:28.945010011 +0100 +++ behave-1.2.3/behave/formatter/html.py 2013-10-29 18:17:28.945010011 +0100 @@ -0,0 +1,311 @@ +from behave.formatter.base import Formatter +import lxml.etree as ET +import base64 +import os.path +from behave.compat.collections import Counter + + +class HTMLFormatter(Formatter): + name = 'html' + description = 'Very basic HTML formatter' + + def __init__(self, stream, config): + super(HTMLFormatter, self).__init__(stream, config) + + self.html = ET.Element('html') + + head = ET.SubElement(self.html, 'head') + ET.SubElement(head, 'title').text = u'Behave steps' + ET.SubElement(head, 'meta', {'content': 'text/html;charset=utf-8'}) + ET.SubElement(head, 'style').text =\ + open(os.path.join(os.path.dirname(__file__), ("report.css")), + 'r').read().encode('utf-8') + + self.stream = self.open() + body = ET.SubElement(self.html, 'body') + self.suite = ET.SubElement(body, 'div', {'class': 'behave'}) + + #Summary + self.header = ET.SubElement(self.suite, 'div', id='behave-header') + label = ET.SubElement(self.header, 'div', id='label') + ET.SubElement(label, 'h1').text = u'Behave features' + + summary = ET.SubElement(self.header, 'div', id='summary') + + totals = ET.SubElement(summary, 'p', id='totals') + + self.current_feature_totals = ET.SubElement(totals, 'p', id='feature_totals') + self.scenario_totals = ET.SubElement(totals, 'p', id='scenario_totals') + self.step_totals = ET.SubElement(totals, 'p', id='step_totals') + self.duration = ET.SubElement(summary, 'p', id='duration') + + expand_collapse = ET.SubElement(summary, 'div', id='expand-collapse') + + expander = ET.SubElement(expand_collapse, 'span', id='expander') + expander.set('onclick', \ + "var ols=document.getElementsByClassName('scenario_steps');" + + "for (var i=0; i< ols.length; i++) {" + + "ols[i].style.display = 'block';" + + "}; " + + "return false") + expander.text = u'Expand All' + + spacer = ET.SubElement(expand_collapse, 'span') + spacer.text = u" " + + collapser = ET.SubElement(expand_collapse, 'span', id='collapser') + collapser.set('onclick', \ + "var ols=document.getElementsByClassName('scenario_steps');" + + "for (var i=0; i< ols.length; i++) {" + + "ols[i].style.display = 'none';" + + "}; " + + "return false") + collapser.text = u'Collapse All' + + self.embed_id = 0 + self.embed_in_this_step = None + self.embed_data = None + self.embed_mime_type = None + self.scenario_id = 0 + + def feature(self, feature): + if not hasattr(self, "all_features"): + self.all_features = [] + self.all_features.append(feature) + + self.current_feature = ET.SubElement(self.suite, 'div', {'class': 'feature'}) + if feature.tags: + tags_element = ET.SubElement(self.current_feature, 'span', {'class': 'tag'}) + tags_element.text = u'@' + reduce(lambda d, x: "%s, @%s" % (d, x), feature.tags) + h2 = ET.SubElement(self.current_feature, 'h2') + feature_element = ET.SubElement(h2, 'span', {'class': 'val'}) + feature_element.text = u'%s: %s' % (feature.keyword, feature.name) + if feature.description: + description_element = ET.SubElement(self.current_feature, 'pre', {'class': 'message'}) + description_element.text = reduce(lambda d, x: "%s\n%s" % (d, x), feature.description) + + def background(self, background): + + self.current_background = ET.SubElement(self.suite, 'div', {'class': 'background'}) + + h3 = ET.SubElement(self.current_background, 'h3') + ET.SubElement(h3, 'span', {'class': 'val'}).text = \ + u'%s: %s' % (background.keyword, background.name) + + + self.steps = ET.SubElement(self.current_background, 'ol') + + def scenario(self, scenario): + if scenario.feature not in self.all_features: + self.all_features.append(scenario.feature) + self.scenario_el = ET.SubElement(self.suite, 'div', {'class': 'scenario'}) + + scenario_file = ET.SubElement(self.scenario_el, 'span', {'class': 'scenario_file'}) + scenario_file.text = "%s:%s" % (scenario.location.filename, scenario.location.line) + + if scenario.tags: + tags = ET.SubElement(self.scenario_el, 'span', {'class': 'tag'}) + tags.text = u'@' + reduce(lambda d, x: "%s, @%s" % (d, x), scenario.tags) + + self.scenario_name = ET.SubElement(self.scenario_el, 'h3') + span = ET.SubElement(self.scenario_name, 'span', {'class': 'val'}) + span.text = u'%s: %s' % (scenario.keyword, scenario.name) + + if scenario.description: + description_element = ET.SubElement(self.scenario_el, 'pre', {'class': 'message'}) + description_element.text = reduce(lambda d, x: "%s\n%s" % (d, x), scenario.description) + + self.steps = ET.SubElement(self.scenario_el, 'ol', + {'class': 'scenario_steps', + 'id': 'scenario_%s' % self.scenario_id}) + + self.scenario_name.set('onclick', \ + "ol=document.getElementById('scenario_%s');" % self.scenario_id + + "ol.style.display =" + + "(ol.style.display == 'none' ? 'block' : 'none');" + + "return false") + self.scenario_id += 1 + + def scenario_outline(self, outline): + self.scenario(self, outline) + self.scenario_el.set('class', 'scenario outline') + + def match(self, match): + self.arguments = match.arguments + if match.location: + self.location = "%s:%s" % (match.location.filename, match.location.line) + else: + self.location = "<unknown>" + + def step(self, step): + self.arguments = None + self.embed_in_this_step = None + self.last_step = step + + def result(self, result): + self.last_step = result + step = ET.SubElement(self.steps, 'li', {'class': 'step %s' % result.status}) + step_name = ET.SubElement(step, 'div', {'class': 'step_name'}) + + keyword = ET.SubElement(step_name, 'span', {'class': 'keyword'}) + keyword.text = result.keyword + u' ' + + step_text = ET.SubElement(step_name, 'span', {'class': 'step val'}) + step_text.text = result.name + if self.arguments: + text_start = 0 + for argument in self.arguments: + if text_start == 0: + step_text.text = result.name[:argument.start] + else: + bold.tail = result.name[text_start:argument.start] + bold = ET.SubElement(step_text, 'b') + bold.text = str(argument.value) + text_start = argument.end + # Add remaining tail + bold.tail = result.name[self.arguments[-1].end:] + + step_file = ET.SubElement(step, 'div', {'class': 'step_file'}) + ET.SubElement(step_file, 'span').text = self.location + + self.last_step_embed_span = ET.SubElement(step, 'span') + self.last_step_embed_span.set('class', 'embed') + + if result.text: + message = ET.SubElement(step, 'div', {'class': 'message'}) + pre = ET.SubElement(message, 'pre', {'style': 'white-space: pre-wrap;'}) + pre.text = result.text + + if result.table: + table = ET.SubElement(step, 'table') + tr = ET.SubElement(table, 'tr') + for heading in result.table.headings: + ET.SubElement(tr, 'th').text = heading + + for row in result.table.rows: + tr = ET.SubElement(table, 'tr') + for cell in row.cells: + ET.SubElement(tr, 'td').text = cell + + if result.error_message: + self.embed_id += 1 + link = ET.SubElement(step, 'a', {'class': 'message'}) + link.set("onclick", \ + "rslt=document.getElementById('embed_%s');" % self.embed_id + + "rslt.style.display =" + + "(rslt.style.display == 'none' ? 'block' : 'none');" + + "return false") + link.text = u'Error message' + + embed = ET.SubElement(step, 'pre', + {'id': "embed_%s" % self.embed_id, + 'style': 'display: none; white-space: pre-wrap;'}) + embed.text = result.error_message + embed.tail = u' ' + + if result.status == 'failed': + style = 'background: #C40D0D; color: #FFFFFF' + self.scenario_name.set('style', style) + self.header.set('style', style) + + if result.status == 'undefined': + style = 'background: #FAF834; color: #000000' + self.scenario_name.set('style', style) + self.header.set('style', style) + + if hasattr(self, 'embed_in_this_step') and self.embed_in_this_step: + self._doEmbed(self.last_step_embed_span, + self.embed_mime_type, + self.embed_data) + self.embed_in_this_step = None + + def _doEmbed(self, span, mime_type, data): + self.embed_id += 1 + + link = ET.SubElement(span, 'a') + link.set("onclick", \ + "embd=document.getElementById('embed_%s');" % self.embed_id + + "embd.style.display =" + + "(embd.style.display == 'none' ? 'block' : 'none');" + + "return false") + + if 'image/' in mime_type: + link.text = u'Screenshot' + + embed = ET.SubElement(span, 'img', + {'id': 'embed_%s' % self.embed_id, + 'style': 'display: none', + 'src': u'data:%s;base64,%s' % (mime_type, base64.b64encode(data)) + }) + embed.tail = u' ' + + if 'text/' in mime_type: + link.text = u'Data' + + def valid_XML_char_ordinal(i): + return ( # conditions ordered by presumed frequency + 0x20 <= i <= 0xD7FF + or i in (0x9, 0xA, 0xD) + or 0xE000 <= i <= 0xFFFD + or 0x10000 <= i <= 0x10FFFF + ) + cleaned_data = ''.join( + c for c in data if valid_XML_char_ordinal(ord(c)) + ) + + embed = ET.SubElement(span, 'pre', + {'id': "embed_%s" % self.embed_id, + 'style': 'display: none'}) + embed.text = cleaned_data + embed.tail = u' ' + + def embedding(self, mime_type, data): + if self.last_step.status == 'untested': + # Embed called during step execution + self.embed_in_this_step = True + self.embed_mime_type = mime_type + self.embed_data = data + else: + # Embed called in after_* + self._doEmbed(self.last_step_embed_span, mime_type, data) + + def close(self): + if not hasattr(self, "all_features"): + self.all_features = [] + self.duration.text =\ + u"Finished in %0.1f seconds" %\ + sum(map(lambda x: x.duration, self.all_features)) + + # Filling in summary details + result = [] + statuses = map(lambda x: x.status, self.all_features) + status_counter = Counter(statuses) + for k in status_counter: + result.append('%s: %s' % (k, status_counter[k])) + self.current_feature_totals.text = u'Features: %s' % ', '.join(result) + + result = [] + scenarios_list = map(lambda x: x.scenarios, self.all_features) + scenarios = [] + if len(scenarios_list) > 0: + scenarios = reduce(lambda a, b: a + b, scenarios_list) + statuses = map(lambda x: x.status, scenarios) + status_counter = Counter(statuses) + for k in status_counter: + result.append('%s: %s' % (k, status_counter[k])) + self.scenario_totals.text = u'Scenarios: %s' % ', '.join(result) + + result = [] + step_list = map(lambda x: x.steps, scenarios) + steps = [] + if step_list: + steps = reduce(lambda a, b: a + b, step_list) + statuses = map(lambda x: x.status, steps) + status_counter = Counter(statuses) + for k in status_counter: + result.append('%s: %s' % (k, status_counter[k])) + self.step_totals.text = u'Steps: %s' % ', '.join(result) + + # Sending the report to stream + if len(self.all_features) > 0: + self.stream.write(ET.tostring(self.html, pretty_print = True)) diff -up behave-1.2.3/behave/formatter/report.css.HTMLformatter behave-1.2.3/behave/formatter/report.css --- behave-1.2.3/behave/formatter/report.css.HTMLformatter 2013-10-29 18:17:28.945010011 +0100 +++ behave-1.2.3/behave/formatter/report.css 2013-10-29 18:17:28.945010011 +0100 @@ -0,0 +1,212 @@ +body { + font-size: 0px; + color: white; + margin: 0px; + padding: 0px; +} + +.behave, td, th { + font: normal 11px "Lucida Grande", Helvetica, sans-serif; + background: white; + color: black; +} +.behave #behave-header, td #behave-header, th #behave-header { + background: #65c400; + color: white; + height: 8em; +} +.behave #behave-header #expand-collapse p, td #behave-header #expand-collapse p, th #behave-header #expand-collapse p { + float: right; + margin: 0 0 0 10px; +} +.behave .scenario h3, td .scenario h3, th .scenario h3, .background h3 { + font-size: 11px; + padding: 3px; + margin: 0; + background: #65c400; + color: white; + font-weight: bold; +} + +.background h3 { + font-size: 1.2em; + background: #666; +} + +.behave h1, td h1, th h1 { + margin: 0px 10px 0px 10px; + padding: 10px; + font-family: "Lucida Grande", Helvetica, sans-serif; + font-size: 2em; + position: absolute; +} +.behave h4, td h4, th h4 { + margin-bottom: 2px; +} +.behave div.feature, td div.feature, th div.feature { + padding: 2px; + margin: 0px 10px 5px 10px; +} +.behave div.examples, td div.examples, th div.examples { + padding: 0em 0em 0em 1em; +} +.behave .stats, td .stats, th .stats { + margin: 2em; +} +.behave .summary ul.features li, td .summary ul.features li, th .summary ul.features li { + display: inline; +} +.behave .step_name, td .step_name, th .step_name { + float: left; +} +.behave .step_file, td .step_file, th .step_file { + text-align: right; + color: #999999; +} +.behave .step_file a, td .step_file a, th .step_file a { + color: #999999; +} +.behave .scenario_file, td .scenario_file, th .scenario_file { + float: right; + color: #999999; +} +.behave .tag, td .tag, th .tag { + font-weight: bold; + color: #246ac1; +} +.behave .backtrace, td .backtrace, th .backtrace { + margin-top: 0; + margin-bottom: 0; + margin-left: 1em; + color: black; +} +.behave a, td a, th a { + text-decoration: none; + color: #be5c00; +} +.behave a:hover, td a:hover, th a:hover { + text-decoration: underline; +} +.behave a:visited, td a:visited, th a:visited { + font-weight: normal; +} +.behave a div.examples, td a div.examples, th a div.examples { + margin: 5px 0px 5px 15px; + color: black; +} +.behave .outline table, td .outline table, th .outline table { + margin: 0px 0px 5px 10px; +} +.behave table, td table, th table { + border-collapse: collapse; +} +.behave table td, td table td, th table td { + padding: 3px 3px 3px 5px; +} +.behave table td.failed, .behave table td.passed, .behave table td.skipped, .behave table td.pending, .behave table td.undefined, td table td.failed, td table td.passed, td table td.skipped, td table td.pending + padding-left: 18px; + padding-right: 10px; +} +.behave table td.failed, td table td.failed, th table td.failed { + border-left: 5px solid #c20000; + border-bottom: 1px solid #c20000; + background: #fffbd3; + color: #c20000; +} +.behave table td.passed, td table td.passed, th table td.passed { + border-left: 5px solid #65c400; + border-bottom: 1px solid #65c400; + background: #dbffb4; + color: #3d7700; +} +.behave table td.skipped, td table td.skipped, th table td.skipped { + border-left: 5px solid aqua; + border-bottom: 1px solid aqua; + background: #e0ffff; + color: #001111; +} +.behave table td.pending, td table td.pending, th table td.pending { + border-left: 5px solid #faf834; + border-bottom: 1px solid #faf834; + background: #fcfb98; + color: #131313; +} +.behave table td.undefined, td table td.undefined, th table td.undefined { + border-left: 5px solid #faf834; + border-bottom: 1px solid #faf834; + background: #fcfb98; + color: #131313; +} +.behave table td.message, td table td.message, th table td.message { + border-left: 5px solid aqua; + border-bottom: 1px solid aqua; + background: #e0ffff; + color: #001111; +} +.behave ol, td ol, th ol { + list-style: none; + margin: 0px; + padding: 0px; +} +.behave ol li.step, td ol li.step, th ol li.step { + padding: 3px 3px 3px 18px; + margin: 5px 0px 5px 5px; +} +.behave ol li, td ol li, th ol li { + margin: 0em 0em 0em 1em; + padding: 0em 0em 0em 0.2em; +} +.behave ol li span.param, td ol li span.param, th ol li span.param { + font-weight: bold; +} +.behave ol li.failed, td ol li.failed, th ol li.failed { + border-left: 5px solid #c20000; + border-bottom: 1px solid #c20000; + background: #fffbd3; + color: #c20000; +} +.behave ol li.passed, td ol li.passed, th ol li.passed { + border-left: 5px solid #65c400; + border-bottom: 1px solid #65c400; + background: #dbffb4; + color: #3d7700; +} +.behave ol li.skipped, td ol li.skipped, th ol li.skipped { + border-left: 5px solid aqua; + border-bottom: 1px solid aqua; + background: #e0ffff; + color: #001111; +} +.behave ol li.pending, td ol li.pending, th ol li.pending { + border-left: 5px solid #faf834; + border-bottom: 1px solid #faf834; + background: #fcfb98; + color: #131313; +} +.behave ol li.undefined, td ol li.undefined, th ol li.undefined { + border-left: 5px solid #faf834; + border-bottom: 1px solid #faf834; + background: #fcfb98; + color: #131313; +} +.behave ol li.message, td ol li.message, th ol li.message { + border-left: 5px solid aqua; + border-bottom: 1px solid aqua; + background: #e0ffff; + color: #001111; + margin-left: 10px; +} +.behave #summary, td #summary, th #summary { + margin: 0px; + padding: 5px 10px; + text-align: right; + top: 0px; + right: 0px; + float: right; +} +.behave #summary p, td #summary p, th #summary p { + margin: 0 0 0 2px; +} +.behave #summary #totals, td #summary #totals, th #summary #totals { + font-size: 1.2em; +} diff -up behave-1.2.3/behave/runner.py.HTMLformatter behave-1.2.3/behave/runner.py --- behave-1.2.3/behave/runner.py.HTMLformatter 2013-06-26 22:21:00.000000000 +0200 +++ behave-1.2.3/behave/runner.py 2013-10-29 18:17:28.945010011 +0100 @@ -241,6 +241,11 @@ class Context(object): return True return False + def embed(self, mime_type, data): + for formatter in self._runner.formatters: + if hasattr(formatter, 'embedding'): + formatter.embedding(mime_type, data) + def execute_steps(self, steps_text): '''The steps identified in the "steps" text string will be parsed and executed in turn just as though they were defined in a feature file. diff -up behave-1.2.3/features/formatter.help.feature.HTMLformatter behave-1.2.3/features/formatter.help.feature --- behave-1.2.3/features/formatter.help.feature.HTMLformatter 2013-06-27 00:19:16.000000000 +0200 +++ behave-1.2.3/features/formatter.help.feature 2013-10-29 18:17:28.946010012 +0100 @@ -11,6 +11,7 @@ Feature: Help Formatter And the command output should contain: """ Available formatters: + html Very basic HTML formatter json JSON dump of test run json.pretty JSON dump of test run (human readable) null Provides formatter that does not output anything. diff -up behave-1.2.3/features/formatter.html.feature.HTMLformatter behave-1.2.3/features/formatter.html.feature --- behave-1.2.3/features/formatter.html.feature.HTMLformatter 2013-10-29 18:17:28.946010012 +0100 +++ behave-1.2.3/features/formatter.html.feature 2013-10-29 18:17:28.946010012 +0100 @@ -0,0 +1,822 @@ +@sequential +Feature: HTML Formatter + + In order to export behave results + As a tester + I want that behave generates test run data in HTML format. + + + @setup + Scenario: Feature Setup + Given a new working directory + And a file named "features/steps/steps.py" with: + """ + from behave import step + + @step('a step passes') + def step_passes(context): + pass + + @step('a step fails') + def step_fails(context): + assert False, "XFAIL-STEP" + + @step('a step with parameter "{param:w}" passes') + def step_with_one_param_passes(context, param): + pass + + @step('a step with parameter "{param1:w}" and parameter "{param2:w}" passes') + def step_with_two_params_passes(context, param1, param2): + pass + """ + + Scenario: Use HTML formatter on feature without scenarios + Given a file named "features/feature_without_scenarios.feature" with: + """ + Feature: Simple, empty Feature + """ + When I run "behave -f html features/feature_without_scenarios.feature" + Then it should pass with: + """ + 0 features passed, 0 failed, 1 skipped + 0 scenarios passed, 0 failed, 0 skipped + """ + And the command output should contain: + """ + <div class="feature"> + <h2> + <span class="val">Feature: Simple, empty Feature</span> + </h2> + </div> + """ + + Scenario: Use HTML formatter on feature with description + Given a file named "features/feature_with_description.feature" with: + """ + Feature: Simple feature with description + + First feature description line. + Second feature description line. + + Third feature description line (following an empty line). + """ + When I run "behave -f html features/feature_with_description.feature" + Then it should pass with: + """ + 0 features passed, 0 failed, 1 skipped + 0 scenarios passed, 0 failed, 0 skipped + """ + And the command output should contain: + """ + <div class="feature"> + <h2> + <span class="val">Feature: Simple feature with description</span> + </h2> + <pre class="message">First feature description line. + Second feature description line. + Third feature description line (following an empty line).</pre> + </div> + """ + + Scenario: Use HTML formatter on feature with tags + Given a file named "features/feature_with_tags.feature" with: + """ + @foo @bar + Feature: Simple feature with tags + """ + When I run "behave -f html features/feature_with_tags.feature" + Then it should pass with: + """ + 0 features passed, 0 failed, 1 skipped + 0 scenarios passed, 0 failed, 0 skipped + """ + And the command output should contain: + """ + <div class="feature"> + <span class="tag">@foo, @bar</span> + <h2> + <span class="val">Feature: Simple feature with tags</span> + </h2> + </div> + """ + + Scenario: Use HTML formatter on feature on one empty scenario + Given a file named "features/feature_one_empty_scenario.feature" with: + """ + Feature: + Scenario: Simple scenario without steps + """ + When I run "behave -f html features/feature_one_empty_scenario.feature" + Then it should pass with: + """ + 1 feature passed, 0 failed, 0 skipped + 1 scenario passed, 0 failed, 0 skipped + """ + And the command output should contain: + """ + <div class="feature"> + <h2> + <span class="val">Feature: </span> + </h2> + </div> + <div class="scenario"> + <span class="scenario_file">features/feature_one_empty_scenario.feature:2</span> + <h3 onclick="ol=document.getElementById('scenario_0');ol.style.display =(ol.style.display == 'none' ? 'block' : 'none');return false"> + <span class="val">Scenario: Simple scenario without steps</span> + </h3> + <ol class="scenario_steps" id="scenario_0"/> + </div> + """ + + Scenario: Use HTML formatter on feature on one empty scenario with description + Given a file named "features/feature_one_empty_scenario_with_description.feature" with: + """ + Feature: + Scenario: Simple scenario with description but without steps + First scenario description line. + Second scenario description line. + + Third scenario description line (after an empty line). + """ + When I run "behave -f html features/feature_one_empty_scenario_with_description.feature" + Then it should pass with: + """ + 1 feature passed, 0 failed, 0 skipped + 1 scenario passed, 0 failed, 0 skipped + """ + And the command output should contain: + """ + <div class="feature"> + <h2> + <span class="val">Feature: </span> + </h2> + </div> + <div class="scenario"> + <span class="scenario_file">features/feature_one_empty_scenario_with_description.feature:2</span> + <h3 onclick="ol=document.getElementById('scenario_0');ol.style.display =(ol.style.display == 'none' ? 'block' : 'none');return false"> + <span class="val">Scenario: Simple scenario with description but without steps</span> + </h3> + <pre class="message">First scenario description line. + Second scenario description line. + Third scenario description line (after an empty line).</pre> + <ol class="scenario_steps" id="scenario_0"/> + </div> + """ + + Scenario: Use HTML formatter on feature on one empty scenario with tags + Given a file named "features/feature_one_empty_scenario_with_tags.feature" with: + """ + Feature: + @foo @bar + Scenario: Simple scenario with tags but without steps + """ + When I run "behave -f html features/feature_one_empty_scenario_with_tags.feature" + Then it should pass with: + """ + 1 feature passed, 0 failed, 0 skipped + 1 scenario passed, 0 failed, 0 skipped + """ + And the command output should contain: + """ + <div class="feature"> + <h2> + <span class="val">Feature: </span> + </h2> + </div> + <div class="scenario"> + <span class="scenario_file">features/feature_one_empty_scenario_with_tags.feature:3</span> + <span class="tag">@foo, @bar</span> + <h3 onclick="ol=document.getElementById('scenario_0');ol.style.display =(ol.style.display == 'none' ? 'block' : 'none');return false"> + <span class="val">Scenario: Simple scenario with tags but without steps</span> + </h3> + <ol class="scenario_steps" id="scenario_0"/> + </div> + """ + + Scenario: Use HTML formatter on feature on one passing scenario + Given a file named "features/feature_one_passing_scenario.feature" with: + """ + Feature: + Scenario: Simple scenario with passing steps + Given a step passes + When a step passes + Then a step passes + And a step passes + But a step passes + """ + When I run "behave -f html features/feature_one_passing_scenario.feature" + Then it should pass with: + """ + 1 feature passed, 0 failed, 0 skipped + 1 scenario passed, 0 failed, 0 skipped + """ + And the command output should contain: + """ + <div class="feature"> + <h2> + <span class="val">Feature: </span> + </h2> + </div> + <div class="scenario"> + <span class="scenario_file">features/feature_one_passing_scenario.feature:2</span> + <h3 onclick="ol=document.getElementById('scenario_0');ol.style.display =(ol.style.display == 'none' ? 'block' : 'none');return false"> + <span class="val">Scenario: Simple scenario with passing steps</span> + </h3> + <ol class="scenario_steps" id="scenario_0"> + <li class="step passed"> + <div class="step_name"> + <span class="keyword">Given </span> + <span class="step val">a step passes</span> + </div> + <div class="step_file"> + <span>features/steps/steps.py:3</span> + </div> + <span class="embed"/> + </li> + <li class="step passed"> + <div class="step_name"> + <span class="keyword">When </span> + <span class="step val">a step passes</span> + </div> + <div class="step_file"> + <span>features/steps/steps.py:3</span> + </div> + <span class="embed"/> + </li> + <li class="step passed"> + <div class="step_name"> + <span class="keyword">Then </span> + <span class="step val">a step passes</span> + </div> + <div class="step_file"> + <span>features/steps/steps.py:3</span> + </div> + <span class="embed"/> + </li> + <li class="step passed"> + <div class="step_name"> + <span class="keyword">And </span> + <span class="step val">a step passes</span> + </div> + <div class="step_file"> + <span>features/steps/steps.py:3</span> + </div> + <span class="embed"/> + </li> + <li class="step passed"> + <div class="step_name"> + <span class="keyword">But </span> + <span class="step val">a step passes</span> + </div> + <div class="step_file"> + <span>features/steps/steps.py:3</span> + </div> + <span class="embed"/> + </li> + </ol> + </div> + """ + + Scenario: Use HTML formatter on feature on one failing scenario + Given a file named "features/feature_one_failing_scenario.feature" with: + """ + Feature: + Scenario: Simple scenario with failing step + Given a step passes + When a step passes + Then a step passes + And a step passes + But a step fails + """ + When I run "behave -f html features/feature_one_failing_scenario.feature" + Then it should fail with: + """ + 0 features passed, 1 failed, 0 skipped + 0 scenarios passed, 1 failed, 0 skipped + """ + And the command output should contain: + """ + <div class="feature"> + <h2> + <span class="val">Feature: </span> + </h2> + </div> + <div class="scenario"> + <span class="scenario_file">features/feature_one_failing_scenario.feature:2</span> + <h3 onclick="ol=document.getElementById('scenario_0');ol.style.display =(ol.style.display == 'none' ? 'block' : 'none');return false" style="background: #C40D0D; color: #FFFFFF"> + <span class="val">Scenario: Simple scenario with failing step</span> + </h3> + <ol class="scenario_steps" id="scenario_0"> + <li class="step passed"> + <div class="step_name"> + <span class="keyword">Given </span> + <span class="step val">a step passes</span> + </div> + <div class="step_file"> + <span>features/steps/steps.py:3</span> + </div> + <span class="embed"/> + </li> + <li class="step passed"> + <div class="step_name"> + <span class="keyword">When </span> + <span class="step val">a step passes</span> + </div> + <div class="step_file"> + <span>features/steps/steps.py:3</span> + </div> + <span class="embed"/> + </li> + <li class="step passed"> + <div class="step_name"> + <span class="keyword">Then </span> + <span class="step val">a step passes</span> + </div> + <div class="step_file"> + <span>features/steps/steps.py:3</span> + </div> + <span class="embed"/> + </li> + <li class="step passed"> + <div class="step_name"> + <span class="keyword">And </span> + <span class="step val">a step passes</span> + </div> + <div class="step_file"> + <span>features/steps/steps.py:3</span> + </div> + <span class="embed"/> + </li> + <li class="step failed"><div class="step_name"><span class="keyword">But </span><span class="step val">a step fails</span></div><div class="step_file"><span>features/steps/steps.py:7</span></div><span class="embed"/><a class="message" onclick="rslt=document.getElementById('embed_1');rslt.style.display =(rslt.style.display == 'none' ? 'block' : 'none');return false">Error message</a><pre id="embed_1" style="display: none; white-space: pre-wrap;">Assertion Failed: XFAIL-STEP</pre> </li> + </ol> + </div> + """ + + Scenario: Use HTML formatter on feature with one scenario with skipped steps + Given a file named "features/feature_one_failing_scenario_with_skipped_steps.feature" with: + """ + Feature: + Scenario: Simple scenario with failing and skipped steps + Given a step passes + When a step fails + Then a step passes + And a step passes + But a step passes + """ + When I run "behave -f html features/feature_one_failing_scenario_with_skipped_steps.feature" + Then it should fail with: + """ + 0 features passed, 1 failed, 0 skipped + 0 scenarios passed, 1 failed, 0 skipped + """ + And the command output should contain: + """ + <div class="feature"> + <h2> + <span class="val">Feature: </span> + </h2> + </div> + <div class="scenario"> + <span class="scenario_file">features/feature_one_failing_scenario_with_skipped_steps.feature:2</span> + <h3 onclick="ol=document.getElementById('scenario_0');ol.style.display =(ol.style.display == 'none' ? 'block' : 'none');return false" style="background: #C40D0D; color: #FFFFFF"> + <span class="val">Scenario: Simple scenario with failing and skipped steps</span> + </h3> + <ol class="scenario_steps" id="scenario_0"> + <li class="step passed"> + <div class="step_name"> + <span class="keyword">Given </span> + <span class="step val">a step passes</span> + </div> + <div class="step_file"> + <span>features/steps/steps.py:3</span> + </div> + <span class="embed"/> + </li> + <li class="step failed"><div class="step_name"><span class="keyword">When </span><span class="step val">a step fails</span></div><div class="step_file"><span>features/steps/steps.py:7</span></div><span class="embed"/><a class="message" onclick="rslt=document.getElementById('embed_1');rslt.style.display =(rslt.style.display == 'none' ? 'block' : 'none');return false">Error message</a><pre id="embed_1" style="display: none; white-space: pre-wrap;">Assertion Failed: XFAIL-STEP</pre> </li> + </ol> + </div> + """ + + Scenario: Use HTML formatter on feature with three scenarios + Given a file named "features/feature_three_scenarios.feature" with: + """ + Feature: + Scenario: Simple passing scenario + Given a step passes + When a step passes + Then a step passes + And a step passes + But a step passes + Scenario: Simple failing scenario + Given a step passes + When a step passes + Then a step passes + And a step passes + But a step fails + Scenario: Simple failing scenario with skipped steps + Given a step passes + When a step passes + Then a step passes + And a step passes + But a step fails + """ + When I run "behave -f html features/feature_three_scenarios.feature" + Then it should fail with: + """ + 0 features passed, 1 failed, 0 skipped + 1 scenario passed, 2 failed, 0 skipped + """ + And the command output should contain: + """ + <div class="scenario"> + <span class="scenario_file">features/feature_three_scenarios.feature:2</span> + <h3 onclick="ol=document.getElementById('scenario_0');ol.style.display =(ol.style.display == 'none' ? 'block' : 'none');return false"> + <span class="val">Scenario: Simple passing scenario</span> + </h3> + <ol class="scenario_steps" id="scenario_0"> + <li class="step passed"> + <div class="step_name"> + <span class="keyword">Given </span> + <span class="step val">a step passes</span> + </div> + <div class="step_file"> + <span>features/steps/steps.py:3</span> + </div> + <span class="embed"/> + </li> + <li class="step passed"> + <div class="step_name"> + <span class="keyword">When </span> + <span class="step val">a step passes</span> + </div> + <div class="step_file"> + <span>features/steps/steps.py:3</span> + </div> + <span class="embed"/> + </li> + <li class="step passed"> + <div class="step_name"> + <span class="keyword">Then </span> + <span class="step val">a step passes</span> + </div> + <div class="step_file"> + <span>features/steps/steps.py:3</span> + </div> + <span class="embed"/> + </li> + <li class="step passed"> + <div class="step_name"> + <span class="keyword">And </span> + <span class="step val">a step passes</span> + </div> + <div class="step_file"> + <span>features/steps/steps.py:3</span> + </div> + <span class="embed"/> + </li> + <li class="step passed"> + <div class="step_name"> + <span class="keyword">But </span> + <span class="step val">a step passes</span> + </div> + <div class="step_file"> + <span>features/steps/steps.py:3</span> + </div> + <span class="embed"/> + </li> + </ol> + </div> + <div class="scenario"> + <span class="scenario_file">features/feature_three_scenarios.feature:8</span> + <h3 onclick="ol=document.getElementById('scenario_1');ol.style.display =(ol.style.display == 'none' ? 'block' : 'none');return false" style="background: #C40D0D; color: #FFFFFF"> + <span class="val">Scenario: Simple failing scenario</span> + </h3> + <ol class="scenario_steps" id="scenario_1"> + <li class="step passed"> + <div class="step_name"> + <span class="keyword">Given </span> + <span class="step val">a step passes</span> + </div> + <div class="step_file"> + <span>features/steps/steps.py:3</span> + </div> + <span class="embed"/> + </li> + <li class="step passed"> + <div class="step_name"> + <span class="keyword">When </span> + <span class="step val">a step passes</span> + </div> + <div class="step_file"> + <span>features/steps/steps.py:3</span> + </div> + <span class="embed"/> + </li> + <li class="step passed"> + <div class="step_name"> + <span class="keyword">Then </span> + <span class="step val">a step passes</span> + </div> + <div class="step_file"> + <span>features/steps/steps.py:3</span> + </div> + <span class="embed"/> + </li> + <li class="step passed"> + <div class="step_name"> + <span class="keyword">And </span> + <span class="step val">a step passes</span> + </div> + <div class="step_file"> + <span>features/steps/steps.py:3</span> + </div> + <span class="embed"/> + </li> + <li class="step failed"><div class="step_name"><span class="keyword">But </span><span class="step val">a step fails</span></div><div class="step_file"><span>features/steps/steps.py:7</span></div><span class="embed"/><a class="message" onclick="rslt=document.getElementById('embed_1');rslt.style.display =(rslt.style.display == 'none' ? 'block' : 'none');return false">Error message</a><pre id="embed_1" style="display: none; white-space: pre-wrap;">Assertion Failed: XFAIL-STEP</pre> </li> + </ol> + </div> + <div class="scenario"> + <span class="scenario_file">features/feature_three_scenarios.feature:14</span> + <h3 onclick="ol=document.getElementById('scenario_2');ol.style.display =(ol.style.display == 'none' ? 'block' : 'none');return false" style="background: #C40D0D; color: #FFFFFF"> + <span class="val">Scenario: Simple failing scenario with skipped steps</span> + </h3> + <ol class="scenario_steps" id="scenario_2"> + <li class="step passed"> + <div class="step_name"> + <span class="keyword">Given </span> + <span class="step val">a step passes</span> + </div> + <div class="step_file"> + <span>features/steps/steps.py:3</span> + </div> + <span class="embed"/> + </li> + <li class="step passed"> + <div class="step_name"> + <span class="keyword">When </span> + <span class="step val">a step passes</span> + </div> + <div class="step_file"> + <span>features/steps/steps.py:3</span> + </div> + <span class="embed"/> + </li> + <li class="step passed"> + <div class="step_name"> + <span class="keyword">Then </span> + <span class="step val">a step passes</span> + </div> + <div class="step_file"> + <span>features/steps/steps.py:3</span> + </div> + <span class="embed"/> + </li> + <li class="step passed"> + <div class="step_name"> + <span class="keyword">And </span> + <span class="step val">a step passes</span> + </div> + <div class="step_file"> + <span>features/steps/steps.py:3</span> + </div> + <span class="embed"/> + </li> + <li class="step failed"><div class="step_name"><span class="keyword">But </span><span class="step val">a step fails</span></div><div class="step_file"><span>features/steps/steps.py:7</span></div><span class="embed"/><a class="message" onclick="rslt=document.getElementById('embed_2');rslt.style.display =(rslt.style.display == 'none' ? 'block' : 'none');return false">Error message</a><pre id="embed_2" style="display: none; white-space: pre-wrap;">Assertion Failed: XFAIL-STEP</pre> </li> + </ol> + </div> + """ + + Scenario: Use HTML formatter on step with one parameter + Given a file named "features/feature_step_with_one_parameter.feature" with: + """ + Feature: + Scenario: Simple scenario with one parameter in step + Given a step passes + When a step with parameter "foo" passes + Then a step passes + """ + When I run "behave -f html features/feature_step_with_one_parameter.feature" + Then it should pass with: + """ + 1 feature passed, 0 failed, 0 skipped + 1 scenario passed, 0 failed, 0 skipped + """ + And the command output should contain: + """ + <div class="scenario"> + <span class="scenario_file">features/feature_step_with_one_parameter.feature:2</span> + <h3 onclick="ol=document.getElementById('scenario_0');ol.style.display =(ol.style.display == 'none' ? 'block' : 'none');return false"> + <span class="val">Scenario: Simple scenario with one parameter in step</span> + </h3> + <ol class="scenario_steps" id="scenario_0"> + <li class="step passed"> + <div class="step_name"> + <span class="keyword">Given </span> + <span class="step val">a step passes</span> + </div> + <div class="step_file"> + <span>features/steps/steps.py:3</span> + </div> + <span class="embed"/> + </li> + <li class="step passed"> + <div class="step_name"> + <span class="keyword">When </span> + <span class="step val">a step with parameter "<b>foo</b>" passes</span> + </div> + <div class="step_file"> + <span>features/steps/steps.py:11</span> + </div> + <span class="embed"/> + </li> + <li class="step passed"> + <div class="step_name"> + <span class="keyword">Then </span> + <span class="step val">a step passes</span> + </div> + <div class="step_file"> + <span>features/steps/steps.py:3</span> + </div> + <span class="embed"/> + </li> + </ol> + </div> + """ + + Scenario: Use HTML formatter on step with several parameters + Given a file named "features/feature_step_with_parameters.feature" with: + """ + Feature: + Scenario: Simple scenario with parameters in step + Given a step passes + When a step with parameter "foo" and parameter "bar" passes + Then a step passes + """ + When I run "behave -f html features/feature_step_with_parameters.feature" + Then it should pass with: + """ + 1 feature passed, 0 failed, 0 skipped + 1 scenario passed, 0 failed, 0 skipped + """ + And the command output should contain: + """ + <div class="scenario"> + <span class="scenario_file">features/feature_step_with_parameters.feature:2</span> + <h3 onclick="ol=document.getElementById('scenario_0');ol.style.display =(ol.style.display == 'none' ? 'block' : 'none');return false"> + <span class="val">Scenario: Simple scenario with parameters in step</span> + </h3> + <ol class="scenario_steps" id="scenario_0"> + <li class="step passed"> + <div class="step_name"> + <span class="keyword">Given </span> + <span class="step val">a step passes</span> + </div> + <div class="step_file"> + <span>features/steps/steps.py:3</span> + </div> + <span class="embed"/> + </li> + <li class="step passed"> + <div class="step_name"> + <span class="keyword">When </span> + <span class="step val">a step with parameter "<b>foo</b>" and parameter "<b>bar</b>" passes</span> + </div> + <div class="step_file"> + <span>features/steps/steps.py:15</span> + </div> + <span class="embed"/> + </li> + <li class="step passed"> + <div class="step_name"> + <span class="keyword">Then </span> + <span class="step val">a step passes</span> + </div> + <div class="step_file"> + <span>features/steps/steps.py:3</span> + </div> + <span class="embed"/> + </li> + </ol> + </div> + """ + + Scenario: Use HTML formatter on step with multiline + Given a file named "features/feature_multiline_step.feature" with: + """ + Feature: + Scenario: Simple scenario with multiline string in step + Given a step passes + When a step passes: + ''' + Tiger, tiger, burning bright + In the forests of the night, + What immortal hand or eye + Could frame thy fearful symmetry? + ''' + """ + When I run "behave -f html features/feature_multiline_step.feature" + Then it should pass with: + """ + 1 feature passed, 0 failed, 0 skipped + 1 scenario passed, 0 failed, 0 skipped + """ + And the command output should contain: + """ + <div class="scenario"> + <span class="scenario_file">features/feature_multiline_step.feature:2</span> + <h3 onclick="ol=document.getElementById('scenario_0');ol.style.display =(ol.style.display == 'none' ? 'block' : 'none');return false"> + <span class="val">Scenario: Simple scenario with multiline string in step</span> + </h3> + <ol class="scenario_steps" id="scenario_0"> + <li class="step passed"> + <div class="step_name"> + <span class="keyword">Given </span> + <span class="step val">a step passes</span> + </div> + <div class="step_file"> + <span>features/steps/steps.py:3</span> + </div> + <span class="embed"/> + </li> + <li class="step passed"> + <div class="step_name"> + <span class="keyword">When </span> + <span class="step val">a step passes</span> + </div> + <div class="step_file"> + <span>features/steps/steps.py:3</span> + </div> + <span class="embed"/> + <div class="message"> + <pre style="white-space: pre-wrap;"> Tiger, tiger, burning bright + In the forests of the night, + What immortal hand or eye + Could frame thy fearful symmetry?</pre> + </div> + </li> + </ol> + </div> + """ + + Scenario: Use HTML formatter on step with table + Given a file named "features/feature_step_with_table.feature" with: + """ + Feature: + Scenario: Simple scenario with failing and skipped steps + Given a step passes + When a step passes: + | Field | Value | + | Foo | bar | + | baz | qux | + """ + When I run "behave -f html features/feature_step_with_table.feature" + Then it should pass with: + """ + 1 feature passed, 0 failed, 0 skipped + 1 scenario passed, 0 failed, 0 skipped + """ + And the command output should contain: + """ + <div class="scenario"> + <span class="scenario_file">features/feature_step_with_table.feature:2</span> + <h3 onclick="ol=document.getElementById('scenario_0');ol.style.display =(ol.style.display == 'none' ? 'block' : 'none');return false"> + <span class="val">Scenario: Simple scenario with failing and skipped steps</span> + </h3> + <ol class="scenario_steps" id="scenario_0"> + <li class="step passed"> + <div class="step_name"> + <span class="keyword">Given </span> + <span class="step val">a step passes</span> + </div> + <div class="step_file"> + <span>features/steps/steps.py:3</span> + </div> + <span class="embed"/> + </li> + <li class="step passed"> + <div class="step_name"> + <span class="keyword">When </span> + <span class="step val">a step passes</span> + </div> + <div class="step_file"> + <span>features/steps/steps.py:3</span> + </div> + <span class="embed"/> + <table> + <tr> + <th>Field</th> + <th>Value</th> + </tr> + <tr> + <td>Foo</td> + <td>bar</td> + </tr> + <tr> + <td>baz</td> + <td>qux</td> + </tr> + </table> + </li> + </ol> + </div> + """ diff -up behave-1.2.3/setup.py.HTMLformatter behave-1.2.3/setup.py --- behave-1.2.3/setup.py.HTMLformatter 2013-07-08 22:31:19.000000000 +0200 +++ behave-1.2.3/setup.py 2013-10-29 18:19:01.800131040 +0100 @@ -27,6 +27,8 @@ setup( entry_points={ 'console_scripts': ['behave = behave.__main__:main'], }, + package_data={'': ['report.css']}, + include_package_data=True, install_requires=requirements, use_2to3=True, license="BSD",