# Copyright 2008-2015 Nokia Networks
# Copyright 2016- Robot Framework Foundation
#
# Licensed under the Apache License, Version 2.0 (the "License");
# you may not use this file except in compliance with the License.
# You may obtain a copy of the License at
#
# http://www.apache.org/licenses/LICENSE-2.0
#
# Unless required by applicable law or agreed to in writing, software
# distributed under the License is distributed on an "AS IS" BASIS,
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
# See the License for the specific language governing permissions and
# limitations under the License.
from datetime import datetime
from robot.result import Keyword, ResultVisitor, TestCase, TestSuite
from robot.utils import NullMarkupWriter, XmlWriter
from robot.version import get_full_version
[docs]
class XmlLogger(ResultVisitor):
generator = "Robot"
def __init__(self, output, rpa=False, suite_only=False):
self._writer = self._get_writer(output, preamble=not suite_only)
if not suite_only:
self._writer.start("robot", self._get_start_attrs(rpa))
def _get_writer(self, output, preamble=True):
return XmlWriter(output, usage="output", write_empty=False, preamble=preamble)
def _get_start_attrs(self, rpa):
return {
"generator": get_full_version(self.generator),
"generated": datetime.now().isoformat(),
"rpa": "true" if rpa else "false",
"schemaversion": "5",
}
[docs]
def close(self):
self._writer.end("robot")
self._writer.close()
[docs]
def visit_message(self, msg):
self._write_message(msg)
[docs]
def message(self, msg):
self._write_message(msg)
def _write_message(self, msg):
attrs = {
"time": msg.timestamp.isoformat() if msg.timestamp else None,
"level": msg.level,
}
if msg.html:
attrs["html"] = "true"
self._writer.element("msg", msg.message, attrs)
[docs]
def start_keyword(self, kw):
self._writer.start("kw", self._get_start_keyword_attrs(kw))
def _get_start_keyword_attrs(self, kw):
attrs = {"name": kw.name, "owner": kw.owner}
if kw.type != "KEYWORD":
attrs["type"] = kw.type
if kw.source_name:
attrs["source_name"] = kw.source_name
return attrs
[docs]
def end_keyword(self, kw):
self._write_list("var", kw.assign)
self._write_list("arg", [str(a) for a in kw.args])
self._write_list("tag", kw.tags)
self._writer.element("doc", kw.doc)
if kw.timeout:
self._writer.element("timeout", attrs={"value": str(kw.timeout)})
self._write_status(kw)
self._writer.end("kw")
[docs]
def start_if(self, if_):
self._writer.start("if")
[docs]
def end_if(self, if_):
self._write_status(if_)
self._writer.end("if")
[docs]
def start_if_branch(self, branch):
attrs = {"type": branch.type, "condition": branch.condition}
self._writer.start("branch", attrs)
[docs]
def end_if_branch(self, branch):
self._write_status(branch)
self._writer.end("branch")
[docs]
def start_for(self, for_):
attrs = {
"flavor": for_.flavor,
"start": for_.start,
"mode": for_.mode,
"fill": for_.fill,
}
self._writer.start("for", attrs)
[docs]
def end_for(self, for_):
for name in for_.assign:
self._writer.element("var", name)
for value in for_.values:
self._writer.element("value", value)
self._write_status(for_)
self._writer.end("for")
[docs]
def start_for_iteration(self, iteration):
self._writer.start("iter")
[docs]
def end_for_iteration(self, iteration):
for name, value in iteration.assign.items():
self._writer.element("var", value, {"name": name})
self._write_status(iteration)
self._writer.end("iter")
[docs]
def start_try(self, root):
self._writer.start("try")
[docs]
def end_try(self, root):
self._write_status(root)
self._writer.end("try")
[docs]
def start_try_branch(self, branch):
attrs = {
"type": "EXCEPT",
"pattern_type": branch.pattern_type,
"assign": branch.assign,
}
if branch.type == branch.EXCEPT:
self._writer.start("branch", attrs)
self._write_list("pattern", branch.patterns)
else:
self._writer.start("branch", attrs={"type": branch.type})
[docs]
def end_try_branch(self, branch):
self._write_status(branch)
self._writer.end("branch")
[docs]
def start_while(self, while_):
attrs = {
"condition": while_.condition,
"limit": while_.limit,
"on_limit": while_.on_limit,
"on_limit_message": while_.on_limit_message,
}
self._writer.start("while", attrs)
[docs]
def end_while(self, while_):
self._write_status(while_)
self._writer.end("while")
[docs]
def start_while_iteration(self, iteration):
self._writer.start("iter")
[docs]
def end_while_iteration(self, iteration):
self._write_status(iteration)
self._writer.end("iter")
[docs]
def start_group(self, group):
self._writer.start("group", {"name": group.name})
[docs]
def end_group(self, group):
self._write_status(group)
self._writer.end("group")
[docs]
def start_var(self, var):
attr = {"name": var.name}
if var.scope is not None:
attr["scope"] = var.scope
if var.separator is not None:
attr["separator"] = var.separator
self._writer.start("variable", attr, write_empty=True)
[docs]
def end_var(self, var):
for val in var.value:
self._writer.element("var", val)
self._write_status(var)
self._writer.end("variable")
[docs]
def start_return(self, return_):
self._writer.start("return")
[docs]
def end_return(self, return_):
for value in return_.values:
self._writer.element("value", value)
self._write_status(return_)
self._writer.end("return")
[docs]
def start_continue(self, continue_):
self._writer.start("continue")
[docs]
def end_continue(self, continue_):
self._write_status(continue_)
self._writer.end("continue")
[docs]
def start_break(self, break_):
self._writer.start("break")
[docs]
def end_break(self, break_):
self._write_status(break_)
self._writer.end("break")
[docs]
def start_error(self, error):
self._writer.start("error")
[docs]
def end_error(self, error):
for value in error.values:
self._writer.element("value", value)
self._write_status(error)
self._writer.end("error")
[docs]
def start_test(self, test):
attrs = {"id": test.id, "name": test.name, "line": str(test.lineno or "")}
self._writer.start("test", attrs)
[docs]
def end_test(self, test):
self._writer.element("doc", test.doc)
self._write_list("tag", test.tags)
if test.timeout:
self._writer.element("timeout", attrs={"value": str(test.timeout)})
self._write_status(test)
self._writer.end("test")
[docs]
def start_suite(self, suite):
attrs = {"id": suite.id, "name": suite.name}
if suite.source:
attrs["source"] = str(suite.source)
self._writer.start("suite", attrs)
[docs]
def end_suite(self, suite):
self._writer.element("doc", suite.doc)
for name, value in suite.metadata.items():
self._writer.element("meta", value, {"name": name})
self._write_status(suite)
self._writer.end("suite")
[docs]
def statistics(self, stats):
self.visit_statistics(stats)
[docs]
def start_statistics(self, stats):
self._writer.start("statistics")
[docs]
def end_statistics(self, stats):
self._writer.end("statistics")
[docs]
def start_total_statistics(self, total_stats):
self._writer.start("total")
[docs]
def end_total_statistics(self, total_stats):
self._writer.end("total")
[docs]
def start_tag_statistics(self, tag_stats):
self._writer.start("tag")
[docs]
def end_tag_statistics(self, tag_stats):
self._writer.end("tag")
[docs]
def start_suite_statistics(self, tag_stats):
self._writer.start("suite")
[docs]
def end_suite_statistics(self, tag_stats):
self._writer.end("suite")
[docs]
def visit_stat(self, stat):
attrs = stat.get_attributes(values_as_strings=True)
self._writer.element("stat", stat.name, attrs)
[docs]
def errors(self, errors):
self.visit_errors(errors)
[docs]
def start_errors(self, errors):
self._writer.start("errors")
[docs]
def end_errors(self, errors):
self._writer.end("errors")
def _write_list(self, tag, items):
for item in items:
self._writer.element(tag, item)
def _write_status(self, item):
attrs = {
"status": item.status,
"start": item.start_time.isoformat() if item.start_time else None,
"elapsed": format(item.elapsed_time.total_seconds(), "f"),
}
self._writer.element("status", item.message, attrs)
[docs]
class LegacyXmlLogger(XmlLogger):
def _get_start_attrs(self, rpa):
return {
"generator": get_full_version(self.generator),
"generated": self._datetime_to_timestamp(datetime.now()),
"rpa": "true" if rpa else "false",
"schemaversion": "4",
}
def _datetime_to_timestamp(self, dt):
if dt is None:
return None
return dt.isoformat(" ", timespec="milliseconds").replace("-", "")
def _get_start_keyword_attrs(self, kw):
attrs = {"name": kw.kwname, "library": kw.libname}
if kw.type != "KEYWORD":
attrs["type"] = kw.type
if kw.source_name:
attrs["sourcename"] = kw.source_name
return attrs
def _write_status(self, item):
attrs = {
"status": item.status,
"starttime": self._datetime_to_timestamp(item.start_time),
"endtime": self._datetime_to_timestamp(item.end_time),
}
if (
isinstance(item, (TestSuite, TestCase))
or isinstance(item, Keyword)
and item.type == "TEARDOWN"
):
message = item.message
else:
message = ""
self._writer.element("status", message, attrs)
def _write_message(self, msg):
ts = self._datetime_to_timestamp(msg.timestamp) if msg.timestamp else None
attrs = {"timestamp": ts, "level": msg.level}
if msg.html:
attrs["html"] = "true"
self._writer.element("msg", msg.message, attrs)
[docs]
class NullLogger(XmlLogger):
def __init__(self):
super().__init__(None)
def _get_writer(self, output, preamble=True):
return NullMarkupWriter()