Blob Blame History Raw
--- 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"],