--- a/behave/compat/collections.py
+++ b/behave/compat/collections.py
@@ -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
--- a/behave/configuration.py
+++ b/behave/configuration.py
@@ -4,6 +4,7 @@ import os
import re
import sys
import argparse
+import codecs
import ConfigParser
import logging
import shlex
--- a/behave/formatter/formatters.py
+++ b/behave/formatter/formatters.py
@@ -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")
# -----------------------------------------------------------------------------
--- /dev/null
+++ b/behave/formatter/html.py
@@ -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))
--- /dev/null
+++ b/behave/formatter/report.css
@@ -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;
+}
--- a/behave/runner.py
+++ b/behave/runner.py
@@ -254,6 +254,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.
--- a/features/formatter.help.feature
+++ b/features/formatter.help.feature
@@ -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.
--- /dev/null
+++ b/features/formatter.html.feature
@@ -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>
+ """
--- a/setup.py
+++ b/setup.py
@@ -73,6 +73,8 @@ setup(
"behave_test = setuptools_behave:behave_test"
]
},
+ package_data={'': ['report.css']},
+ include_package_data=True,
install_requires=requirements,
test_suite="nose.collector",
tests_require=["nose>=1.3", "mock>=1.0", "PyHamcrest>=1.8"],