# 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 robot.utils import py3to2, setter
from .configurer import SuiteConfigurer
from .filter import Filter, EmptySuiteRemover
from .fixture import create_fixture
from .itemlist import ItemList
from .keyword import Keyword, Keywords
from .metadata import Metadata
from .modelobject import ModelObject
from .tagsetter import TagSetter
from .testcase import TestCase, TestCases
[docs]@py3to2
class TestSuite(ModelObject):
"""Base model for single suite.
Extended by :class:`robot.running.model.TestSuite` and
:class:`robot.result.model.TestSuite`.
"""
test_class = TestCase #: Internal usage only.
fixture_class = Keyword #: Internal usage only.
repr_args = ('name',)
__slots__ = ['parent', 'source', '_name', 'doc', '_my_visitors', 'rpa']
def __init__(self, name='', doc='', metadata=None, source=None, rpa=False,
parent=None):
self._name = name
self.doc = doc
self.metadata = metadata
self.source = source #: Path to the source file or directory.
self.parent = parent #: Parent suite. ``None`` with the root suite.
self.rpa = rpa #: ``True`` when RPA mode is enabled.
self.suites = None
self.tests = None
self.setup = None
self.teardown = None
self._my_visitors = []
@property
def _visitors(self):
parent_visitors = self.parent._visitors if self.parent else []
return self._my_visitors + parent_visitors
@property
def name(self):
"""Test suite name. If not set, constructed from child suite names."""
return self._name or ' & '.join(s.name for s in self.suites)
@name.setter
def name(self, name):
self._name = name
@property
def longname(self):
"""Suite name prefixed with the long name of the parent suite."""
if not self.parent:
return self.name
return '%s.%s' % (self.parent.longname, self.name)
@setter
def metadata(self, metadata):
"""Free test suite metadata as a dictionary."""
return Metadata(metadata)
@setter
def suites(self, suites):
"""Child suites as a :class:`~.TestSuites` object."""
return TestSuites(self.__class__, self, suites)
@setter
def tests(self, tests):
"""Tests as a :class:`~.TestCases` object."""
return TestCases(self.test_class, self, tests)
@setter
def setup(self, setup):
"""Suite setup as a :class:`~.model.keyword.Keyword` object.
This attribute is a ``Keyword`` object also when a suite has no setup
but in that case its truth value is ``False``.
Setup can be modified by setting attributes directly::
suite.setup.name = 'Example'
suite.setup.args = ('First', 'Second')
Alternatively the :meth:`config` method can be used to set multiple
attributes in one call::
suite.setup.config(name='Example', args=('First', 'Second'))
The easiest way to reset the whole setup is setting it to ``None``.
It will automatically recreate the underlying ``Keyword`` object::
suite.setup = None
New in Robot Framework 4.0. Earlier setup was accessed like
``suite.keywords.setup``.
"""
return create_fixture(setup, self, Keyword.SETUP)
@setter
def teardown(self, teardown):
"""Suite teardown as a :class:`~.model.keyword.Keyword` object.
See :attr:`setup` for more information.
"""
return create_fixture(teardown, self, Keyword.TEARDOWN)
@property
def keywords(self):
"""Deprecated since Robot Framework 4.0
Use :attr:`setup` or :attr:`teardown` instead.
"""
keywords = [self.setup, self.teardown]
return Keywords(self, [kw for kw in keywords if kw])
@keywords.setter
def keywords(self, keywords):
Keywords.raise_deprecation_error()
@property
def id(self):
"""An automatically generated unique id.
The root suite has id ``s1``, its child suites have ids ``s1-s1``,
``s1-s2``, ..., their child suites get ids ``s1-s1-s1``, ``s1-s1-s2``,
..., ``s1-s2-s1``, ..., and so on.
The first test in a suite has an id like ``s1-t1``, the second has an
id ``s1-t2``, and so on. Similarly keywords in suites (setup/teardown)
and in tests get ids like ``s1-k1``, ``s1-t1-k1``, and ``s1-s4-t2-k5``.
"""
if not self.parent:
return 's1'
return '%s-s%d' % (self.parent.id, self.parent.suites.index(self)+1)
@property
def test_count(self):
"""Number of the tests in this suite, recursively."""
return len(self.tests) + sum(suite.test_count for suite in self.suites)
@property
def has_tests(self):
if self.tests:
return True
return any(s.has_tests for s in self.suites)
[docs] def filter(self, included_suites=None, included_tests=None,
included_tags=None, excluded_tags=None):
"""Select test cases and remove others from this suite.
Parameters have the same semantics as ``--suite``, ``--test``,
``--include``, and ``--exclude`` command line options. All of them
can be given as a list of strings, or when selecting only one, as
a single string.
Child suites that contain no tests after filtering are automatically
removed.
Example::
suite.filter(included_tests=['Test 1', '* Example'],
included_tags='priority-1')
"""
self.visit(Filter(included_suites, included_tests,
included_tags, excluded_tags))
[docs] def remove_empty_suites(self, preserve_direct_children=False):
"""Removes all child suites not containing any tests, recursively."""
self.visit(EmptySuiteRemover(preserve_direct_children))
[docs] def visit(self, visitor):
""":mod:`Visitor interface <robot.model.visitor>` entry-point."""
visitor.visit_suite(self)
def __str__(self):
return self.name
[docs]class TestSuites(ItemList):
__slots__ = []
def __init__(self, suite_class=TestSuite, parent=None, suites=None):
ItemList.__init__(self, suite_class, {'parent': parent}, suites)