Introduction
============
.. contents:: Table of contents
``plone.app.testing`` provides tools for writing integration and functional
tests for code that runs on top of Plone. It is based on `plone.testing`_.
If you are unfamiliar with ``plone.testing``, the concept of layers, or the
`zope.testing`_ testrunner, please take a look at the the ``plone.testing``
documentation. In fact, even if you are working exclusively with Plone, you
are likely to want to use some of its features for unit testing.
In short, ``plone.app.testing`` includes:
* A set of layers that set up fixtures containing a Plone site, intended for
writing integration and functional tests.
* A collection of helper functions, some useful for writing your own layers
and some applicable to tests themselves.
* A convenient layer base class, extending ``plone.testing.Layer``, which
makes it easier to write custom layers extending the Plone site fixture,
with proper isolation and tear-down.
* Cleanup hooks for ``zope.testing.cleanup`` to clean up global state found
in a Plone installation. This is useful for unit testing.
Compatibility
-------------
``plone.app.testing`` 5.x works with Plone 5.
``plone.app.testing`` 4.x works with Plone 4 and Zope 2.12. It may work with
newer versions. It will not work with earlier versions. Use
``plone.app.testing`` 3.x for Plone 3 and Zope 2.10.
Installation and usage
======================
To use ``plone.app.testing`` in your own package, you need to add it as a
dependency. Most people prefer to keep test-only dependencies separate, so
that they do not need to be installed in scenarios (such as on a production
server) where the tests will not be run. This can be achieved using a
``test`` extra.
In ``setup.py``, add or modify the ``extras_require`` option, like so::
extras_require = {
'test': [
'plone.app.testing',
]
},
This will also include ``plone.testing``, with the ``[z2]``, ``[zca]`` and
``[zodb]`` extras (which ``plone.app.testing`` itself relies on).
Please see the `plone.testing`_ documentation for more details about how to
add a test runner to your buildout, and how to write and run tests.
Layer reference
===============
This package contains a layer class,
``plone.app.testing.layers.PloneFixture``, which sets up a Plone site fixture.
It is combined with other layers from `plone.testing`_ to provide a number of
layer instances. It is important to realise that these layers all have the
same fundamental fixture: they just manage test setup and tear-down
differently.
When set up, the fixture will:
* Create a ZODB sandbox, via a stacked ``DemoStorage``. This ensures
persistent changes made during layer setup can be cleanly torn down.
* Configure a global component registry sandbox. This ensures that global
component registrations (e.g. as a result of loading ZCML configuration)
can be cleanly torn down.
* Create a configuration context with the ``disable-autoinclude`` feature
set. This has the effect of stopping Plone from automatically loading the
configuration of any installed package that uses the
``z3c.autoinclude.plugin:plone`` entry point via `z3c.autoinclude`_. (This
is to avoid accidentally polluting the test fixture - custom layers should
load packages' ZCML configuration explicitly if required).
* Install a number of Zope 2-style products on which Plone depends.
* Load the ZCML for these products, and for ``Products.CMFPlone``, which in
turn pulls in the configuration for the core of Plone.
* Create a default Plone site, with the default theme enabled, but with no
default content.
* Add a user to the root user folder with the ``Manager`` role.
* Add a test user to this instance with the ``Member`` role.
For each test:
* The test user is logged in
* The local component site is set
* Various global caches are cleaned up
Various constants in the module ``plone.app.testing.interfaces`` are defined
to describe this environment:
+----------------------+--------------------------------------------------+
| **Constant** | **Purpose** |
+----------------------+--------------------------------------------------+
| PLONE_SITE_ID | The id of the Plone site object inside the Zope |
| | application root. |
+----------------------+--------------------------------------------------+
| PLONE_SITE_TITLE | The title of the Plone site |
+----------------------+--------------------------------------------------+
| DEFAULT_LANGUAGE | The default language of the Plone site ('en') |
+----------------------+--------------------------------------------------+
| TEST_USER_ID | The id of the test user |
+----------------------+--------------------------------------------------+
| TEST_USER_NAME | The username of the test user |
+----------------------+--------------------------------------------------+
| TEST_USER_PASSWORD | The password of the test user |
+----------------------+--------------------------------------------------+
| TEST_USER_ROLES | The default global roles of the test user - |
| | ('Member',) |
+----------------------+--------------------------------------------------+
| SITE_OWNER_NAME | The username of the user owning the Plone site. |
+----------------------+--------------------------------------------------+
| SITE_OWNER_PASSWORD | The password of the user owning the Plone site. |
+----------------------+--------------------------------------------------+
All the layers also expose a resource in addition to those from their
base layers, made available during tests:
``portal``
The Plone site root.
Plone site fixture
------------------
+------------+--------------------------------------------------+
| Layer: | ``plone.app.testing.PLONE_FIXTURE`` |
+------------+--------------------------------------------------+
| Class: | ``plone.app.testing.layers.PloneFixture`` |
+------------+--------------------------------------------------+
| Bases: | ``plone.testing.z2.STARTUP`` |
+------------+--------------------------------------------------+
| Resources: | |
+------------+--------------------------------------------------+
This layer sets up the Plone site fixture on top of the ``z2.STARTUP``
fixture.
You should not use this layer directly, as it does not provide any test
lifecycle or transaction management. Instead, you should use a layer
created with either the ``IntegrationTesting`` or ``FunctionalTesting``
classes, as outlined below.
Mock MailHost
-------------
+------------+--------------------------------------------------+
| Layer: | ``plone.app.testing.MOCK_MAILHOST_FIXTURE`` |
+------------+--------------------------------------------------+
| Class: | ``plone.app.testing.layers.MockMailHostLayer`` |
+------------+--------------------------------------------------+
| Bases: | ``plone.app.testing.layers.PLONE_FIXTURE`` |
+------------+--------------------------------------------------+
| Resources: | |
+------------+--------------------------------------------------+
This layer builds on top of ``PLONE_FIXTURE`` to patch Plone's MailHost implementation.
With it,
any attempt to send an email will instead store each of them as a string in a list in ``portal.MailHost.messages``.
You should not use this layer directly, as it does not provide any test
lifecycle or transaction management. Instead, you should use a layer
created with either the ``IntegrationTesting`` or ``FunctionalTesting``
classes, like::
from plone.app.testing import MOCK_MAILHOST_FIXTURE
MY_INTEGRATION_TESTING = IntegrationTesting(
bases=(
MY_FIXTURE,
MOCK_MAILHOST_FIXTURE,
),
name="MyFixture:Integration"
)
PloneWithPackageLayer class
---------------------------
Most add-ons do not need more setup than loading a ZCML file and
running a GenericSetup profile.
With this helper class, a fixture can easily be instantiated::
from plone.app.testing import PloneWithPackageLayer
import my.addon
FIXTURE = PloneWithPackageLayer(
zcml_package=my.addon,
zcml_filename='configure.zcml',
gs_profile_id='my.addon:default',
name="MyAddonFixture"
)
PloneWithPackageLayer constructor takes two other keyword arguments:
``bases`` and ``additional_z2_products``.
The ``bases`` argument takes a sequence of base layer fixtures.
It is useful, among other reasons,
to pass a fixture which makes other calls to plone.app.testing API.
The need could arise in the development process.
``additional_z2_products`` argument takes a sequence of package names
that need to be installed as Zope2 Products and are dependencies of the tested add-on.
Integration and functional testing test lifecycles
--------------------------------------------------
``plone.app.testing`` comes with two layer classes, ``IntegrationTesting``
and ``FunctionalTesting``, which derive from the corresponding layer classes
in ``plone.testing.z2``.
These classes set up the ``app``, ``request`` and ``portal`` resources, and
reset the fixture (including various global caches) between each test run.
As with the classes in ``plone.testing``, the ``IntegrationTesting`` class
will create a new transaction for each test and roll it back on test tear-
down, which is efficient for integration testing, whilst ``FunctionalTesting``
will create a stacked ``DemoStorage`` for each test and pop it on test tear-
down, making it possible to exercise code that performs an explicit commit
(e.g. via tests that use ``zope.testbrowser``).
When creating a custom fixture, the usual pattern is to create a new layer
class that has ``PLONE_FIXTURE`` as its default base, instantiating that as a
separate "fixture" layer. This layer is not to be used in tests directly,
since it won't have test/transaction lifecycle management, but represents a
shared fixture, potentially for both functional and integration testing. It
is also the point of extension for other layers that follow the same pattern.
Once this fixture has been defined, "end-user" layers can be defined using
the ``IntegrationTesting`` and ``FunctionalTesting`` classes. For example::
from plone.testing import Layer
from plone.app.testing import PLONE_FIXTURE
from plone.app.testing import IntegrationTesting, FunctionalTesting
class MyFixture(Layer):
defaultBases = (PLONE_FIXTURE,)
...
MY_FIXTURE = MyFixture()
MY_INTEGRATION_TESTING = IntegrationTesting(bases=(MY_FIXTURE,), name="MyFixture:Integration")
MY_FUNCTIONAL_TESTING = FunctionalTesting(bases=(MY_FIXTURE,), name="MyFixture:Functional")
See the ``PloneSandboxLayer`` layer below for a more comprehensive example.
Plone integration testing
-------------------------
+------------+--------------------------------------------------+
| Layer: | ``plone.app.testing.PLONE_INTEGRATION_TESTING`` |
+------------+--------------------------------------------------+
| Class: | ``plone.app.testing.layers.IntegrationTesting`` |
+------------+--------------------------------------------------+
| Bases: | ``plone.app.testing.PLONE_FIXTURE`` |
+------------+--------------------------------------------------+
| Resources: | ``portal`` (test setup only) |
+------------+--------------------------------------------------+
This layer can be used for integration testing against the basic
``PLONE_FIXTURE`` layer.
You can use this directly in your tests if you do not need to set up any
other shared fixture.
However, you would normally not extend this layer - see above.
Plone functional testing
------------------------
+------------+--------------------------------------------------+
| Layer: | ``plone.app.testing.PLONE_FUNCTIONAL_TESTING`` |
+------------+--------------------------------------------------+
| Class: | ``plone.app.testing.layers.FunctionalTesting`` |
+------------+--------------------------------------------------+
| Bases: | ``plone.app.testing.PLONE_FIXTURE`` |
+------------+--------------------------------------------------+
| Resources: | ``portal`` (test setup only) |
+------------+--------------------------------------------------+
This layer can be used for functional testing against the basic
``PLONE_FIXTURE`` layer, for example using ``zope.testbrowser``.
You can use this directly in your tests if you do not need to set up any
other shared fixture.
Again, you would normally not extend this layer - see above.
Plone ZServer
-------------
+------------+--------------------------------------------------+
| Layer: | ``plone.app.testing.PLONE_ZSERVER`` |
+------------+--------------------------------------------------+
| Class: | ``plone.testing.z2.ZServer`` |
+------------+--------------------------------------------------+
| Bases: | ``plone.app.testing.PLONE_FUNCTIONAL_TESTING`` |
+------------+--------------------------------------------------+
| Resources: | ``portal`` (test setup only) |
+------------+--------------------------------------------------+
This is layer is intended for functional testing using a live, running HTTP
server, e.g. using Selenium or Windmill.
Again, you would not normally extend this layer. To create a custom layer
that has a running ZServer, you can use the same pattern as this one, e.g.::
from plone.testing import Layer
from plone.testing import z2
from plone.app.testing import PLONE_FIXTURE
from plone.app.testing import FunctionalTesting
class MyFixture(Layer):
defaultBases = (PLONE_FIXTURE,)
...
MY_FIXTURE = MyFixture()
MY_ZSERVER = FunctionalTesting(bases=(MY_FIXTURE, z2.ZSERVER_FIXTURE), name='MyFixture:ZServer')
See the description of the ``z2.ZSERVER`` layer in `plone.testing`_
for further details.
Plone FTP server
----------------
+------------+--------------------------------------------------+
| Layer: | ``plone.app.testing.PLONE_FTP_SERVER`` |
+------------+--------------------------------------------------+
| Class: | ``plone.app.testing.layers.FunctionalTesting`` |
+------------+--------------------------------------------------+
| Bases: | ``plone.app.testing.PLONE_FIXTURE`` |
| | ``plone.testing.z2.ZSERVER_FIXTURE`` |
+------------+--------------------------------------------------+
| Resources: | ``portal`` (test setup only) |
+------------+--------------------------------------------------+
This is layer is intended for functional testing using a live FTP server.
It is semantically equivalent to the ``PLONE_ZSERVER`` layer.
See the description of the ``z2.FTP_SERVER`` layer in `plone.testing`_
for further details.
Helper functions
================
A number of helper functions are provided for use in tests and custom layers.
Plone site context manager
--------------------------
``ploneSite(db=None, connection=None, environ=None)``
Use this context manager to access and make changes to the Plone site
during layer setup. In most cases, you will use it without arguments,
but if you have special needs, you can tie it to a particular database
instance. See the description of the ``zopeApp()`` context manager in
`plone.testing`_ (which this context manager uses internally) for details.
The usual pattern is to call it during ``setUp()`` or ``tearDown()`` in
your own layers::
from plone.testing import Layer
from plone.app.testing import ploneSite
class MyLayer(Layer):
def setUp(self):
...
with ploneSite() as portal:
# perform operations on the portal, e.g.
portal.title = u"New title"
Here, ``portal`` is the Plone site root. A transaction is begun before
entering the ``with`` block, and will be committed upon exiting the block,
unless an exception is raised, in which case it will be rolled back.
Inside the block, the local component site is set to the Plone site root,
so that local component lookups should work.
**Warning:** Do not attempt to load ZCML files inside a ``ploneSite``
block. Because the local site is set to the Plone site, you may end up
accidentally registering components in the local site manager, which can
cause pickling errors later.
**Note:** You should not use this in a test, or in a ``testSetUp()`` or
``testTearDown()`` method of a layer based on one of the layer in this
package. Use the ``portal`` resource instead.
**Also note:** If you are writing a layer setting up a Plone site fixture,
you may want to use the ``PloneSandboxLayer`` layer base class, and
implement the ``setUpZope()``, ``setUpPloneSite()``, ``tearDownZope()``
and/or ``tearDownPloneSite()`` methods instead. See below.
User management
---------------
``login(portal, userName)``
Simulate login as the given user. This is based on the ``z2.login()``
helper in `plone.testing`_, but instead of passing a specific user folder,
you pass the portal (e.g. as obtained via the ``portal`` layer resource).
For example::
import unittest2 as unittest
from plone.app.testing import PLONE_INTEGRATION_TESTING
from plone.app.testing import TEST_USER_NAME
from plone.app.testing import login
...
class MyTest(unittest.TestCase):
layer = PLONE_INTEGRATION_TESTING
def test_something(self):
portal = self.layer['portal']
login(portal, TEST_USER_NAME)
...
``logout()``
Simulate logging out, i.e. becoming the anonymous user. This is equivalent
to the ``z2.logout()`` helper in `plone.testing`_.
For example::
import unittest2 as unittest
from plone.app.testing import PLONE_INTEGRATION_TESTING
from plone.app.testing import logout
...
class MyTest(unittest.TestCase):
layer = PLONE_INTEGRATION_TESTING
def test_something(self):
portal = self.layer['portal']
logout()
...
``setRoles(portal, userId, roles)``
Set the roles for the given user. ``roles`` is a list of roles.
For example::
import unittest2 as unittest
from plone.app.testing import PLONE_INTEGRATION_TESTING
from plone.app.testing import TEST_USER_ID
from plone.app.testing import setRoles
...
class MyTest(unittest.TestCase):
layer = PLONE_INTEGRATION_TESTING
def test_something(self):
portal = self.layer['portal']
setRoles(portal, TEST_USER_ID, ['Manager'])
Product and profile installation
--------------------------------
``applyProfile(portal, profileName, blacklisted_steps=None)``
Install a GenericSetup profile (usually an extension profile) by name,
using the ``portal_setup`` tool. The name is normally made up of a package
name and a profile name. Do not use the ``profile-`` prefix.
For example::
from plone.testing import Layer
from plone.app.testing import ploneSite
from plone.app.testing import applyProfile
...
class MyLayer(Layer):
...
def setUp(self):
...
with ploneSite() as portal:
applyProfile(portal, 'my.product:default')
...
``quickInstallProduct(portal, productName, reinstall=False)``
Use this function to install a particular product into the given Plone site,
using the add-ons control panel code (portal setup).
If ``reinstall`` is ``False`` and the product is already installed, nothing will happen.
If ``reinstall`` is true, perform an uninstall and install if the product is installed already.
The ``productName`` should be a full dotted name, e.g. ``Products.MyProduct``,
or ``my.product``.
For example::
from plone.testing import Layer
from plone.app.testing import ploneSite
from plone.app.testing import quickInstallProduct
...
class MyLayer(Layer):
...
def setUp(self):
...
with ploneSite() as portal:
quickInstallProduct(portal, 'my.product')
...
Component architecture sandboxing
---------------------------------
``pushGlobalRegistry(portal, new=None, name=None)``
Create or obtain a stack of global component registries, and push a new
registry to the top of the stack. This allows Zope Component Architecture
registrations (e.g. loaded via ZCML) to be effectively torn down.
If you are going to use this function, please read the corresponding
documentation for ``zca.pushGlobalRegistry()`` in `plone.testing`_. In
particular, note that you *must* reciprocally call ``popGlobalRegistry()``
(see below).
This helper is based on ``zca.pushGlobalRegistry()``, but will also fix
up the local component registry in the Plone site ``portal`` so that it
has the correct bases.
For example::
from plone.testing import Layer
from plone.app.testing import ploneSite
from plone.app.testing import pushGlobalRegistry
from plone.app.testing import popGlobalRegistry
...
class MyLayer(Layer):
...
def setUp(self):
...
with ploneSite() as portal:
pushGlobalRegistry(portal)
...
``popGlobalRegistry(portal)``
Tear down the top of the component architecture stack, as created with
``pushGlobalRegistry()``
For example::
...
def tearDown(self):
with ploneSite() as portal:
popGlobalRegistry(portal)
Global state cleanup
--------------------
``tearDownMultiPluginRegistration(pluginName)``
PluggableAuthService "MultiPlugins" are kept in a global registry. If
you have registered a plugin, e.g. using the ``registerMultiPlugin()``
API, you should tear that registration down in your layer's ``tearDown()``
method. You can use this helper, passing a plugin name.
For example::
from plone.testing import Layer
from plone.app.testing import ploneSite
from plone.app.testing import tearDownMultiPluginRegistration
...
class MyLayer(Layer):
...
def tearDown(self):
tearDownMultiPluginRegistration('MyPlugin')
...
Layer base class
================
If you are writing a custom layer to test your own Plone add-on product, you
will often want to do the following on setup:
1. Stack a new ``DemoStorage`` on top of the one from the base layer. This
ensures that any persistent changes performed during layer setup can be
torn down completely, simply by popping the demo storage.
2. Stack a new ZCML configuration context. This keeps separate the information
about which ZCML files were loaded, in case other, independent layers want
to load those same files after this layer has been torn down.
3. Push a new global component registry. This allows you to register
components (e.g. by loading ZCML or using the test API from
``zope.component``) and tear down those registration easily by popping the
component registry.
4. Load your product's ZCML configuration
5. Install the product into the test fixture Plone site
Of course, you may wish to make other changes too, such as creating some base
content or changing some settings.
On tear-down, you will then want to:
1. Remove any Pluggable Authentication Service "multi-plugins" that were added
to the global registry during setup.
2. Pop the global component registry to unregister components loaded via ZCML.
3. Pop the configuration context resource to restore its state.
4. Pop the ``DemoStorage`` to undo any persistent changes.
If you have made other changes on setup that are not covered by this broad
tear-down, you'll also want to tear those down explicitly here.
Stacking a demo storage and component registry is the safest way to avoid
fixtures bleeding between tests. However, it can be tricky to ensure that
everything happens in the right order.
To make things easier, you can use the ``PloneSandboxLayer`` layer base class.
This extends ``plone.testing.Layer`` and implements ``setUp()`` and
``tearDown()`` for you. You simply have to override one or more of the
following methods:
``setUpZope(self, app, configurationContext)``
This is called during setup. ``app`` is the Zope application root.
``configurationContext`` is a newly stacked ZCML configuration context.
Use this to load ZCML, install products using the helper
``plone.testing.z2.installProduct()``, or manipulate other global state.
``setUpPloneSite(self, portal)``
This is called during setup. ``portal`` is the Plone site root as
configured by the ``ploneSite()`` context manager. Use this to make
persistent changes inside the Plone site, such as installing products
using the ``applyProfile()`` or ``quickInstallProduct()`` helpers, or
setting up default content.
``tearDownZope(self, app)``
This is called during tear-down, before the global component registry and
stacked ``DemoStorage`` are popped. Use this to tear down any additional
global state.
**Note:** Global component registrations PAS multi-plugin registrations are
automatically torn down. Product installations are not, so you should use
the ``uninstallProduct()`` helper if any products were installed during
``setUpZope()``.
``tearDownPloneSite(self, portal)``
This is called during tear-down, before the global component registry and
stacked ``DemoStorage`` are popped. During this method, the local
component site hook is set, giving you access to local components.
**Note:** Persistent changes to the ZODB are automatically torn down by
virtue of a stacked ``DemoStorage``. Thus, this method is less commonly
used than the others described here.
Let's show a more comprehensive example of what such a layer may look like.
Imagine we have a product ``my.product``. It has a ``configure.zcml`` file
that loads some components and registers a ``GenericSetup`` profile, making it
installable in the Plone site. On layer setup, we want to load the product's
configuration and install it into the Plone site.
The layer would conventionally live in a module ``testing.py`` at the root of
the package, i.e. ``my.product.testing``::
from plone.app.testing import PloneSandboxLayer
from plone.app.testing import PLONE_FIXTURE
from plone.app.testing import IntegrationTesting
from plone.testing import z2
class MyProduct(PloneSandboxLayer):
defaultBases = (PLONE_FIXTURE,)
def setUpZope(self, app, configurationContext):
# Load ZCML
import my.product
self.loadZCML(package=my.product)
# Install product and call its initialize() function
z2.installProduct(app, 'my.product')
# Note: you can skip this if my.product is not a Zope 2-style
# product, i.e. it is not in the Products.* namespace and it
# does not have a <five:registerPackage /> directive in its
# configure.zcml.
def setUpPloneSite(self, portal):
# Install into Plone site using portal_setup
self.applyProfile(portal, 'my.product:default')
def tearDownZope(self, app):
# Uninstall product
z2.uninstallProduct(app, 'my.product')
# Note: Again, you can skip this if my.product is not a Zope 2-
# style product
MY_PRODUCT_FIXTURE = MyProduct()
MY_PRODUCT_INTEGRATION_TESTING = IntegrationTesting(bases=(MY_PRODUCT_FIXTURE,), name="MyProduct:Integration")
Here, ``MY_PRODUCT_FIXTURE`` is the "fixture" base layer. Other layers can
use this as a base if they want to build on this fixture, but it would not
be used in tests directly. For that, we have created an ``IntegrationTesting``
instance, ``MY_PRODUCT_INTEGRATION_TESTING``.
Of course, we could have created a ``FunctionalTesting`` instance as
well, e.g.::
MY_PRODUCT_FUNCTIONAL_TESTING = FunctionalTesting(bases=(MY_PRODUCT_FIXTURE,), name="MyProduct:Functional")
Of course, we could do a lot more in the layer setup. For example, let's say
the product had a content type 'my.product.page' and we wanted to create some
test content. We could do that with::
from plone.app.testing import TEST_USER_ID
from plone.app.testing import TEST_USER_NAME
from plone.app.testing import login
from plone.app.testing import setRoles
...
def setUpPloneSite(self, portal):
...
setRoles(portal, TEST_USER_ID, ['Manager'])
login(portal, TEST_USER_NAME)
portal.invokeFactory('my.product.page', 'page-1', title=u"Page 1")
setRoles(portal, TEST_USER_ID, ['Member'])
...
Note that unlike in a test, there is no user logged in at layer setup time,
so we have to explicitly log in as the test user. Here, we also grant the test
user the ``Manager`` role temporarily, to allow object construction (which
performs an explicit permission check).
**Note:** Automatic tear down suffices for all the test setup above. If
the only changes made during layer setup are to persistent, in-ZODB data,
or the global component registry then no additional tear-down is required.
For any other global state being managed, you should write a
``tearDownPloneSite()`` method to perform the necessary cleanup.
Given this layer, we could write a test (e.g. in ``tests.py``) like::
import unittest2 as unittest
from my.product.testing import MY_PRODUCT_INTEGRATION_TESTING
class IntegrationTest(unittest.TestCase):
layer = MY_PRODUCT_INTEGRATION_TESTING
def test_page_dublin_core_title(self):
portal = self.layer['portal']
page1 = portal['page-1']
page1.title = u"Some title"
self.assertEqual(page1.Title(), u"Some title")
Please see `plone.testing`_ for more information about how to write and run
tests and assertions.
Common test patterns
====================
`plone.testing`_'s documentation contains details about the fundamental
techniques for writing tests of various kinds. In a Plone context, however,
some patterns tend to crop up time and again. Below, we will attempt to
catalogue some of the more commonly used patterns via short code samples.
The examples in this section are all intended to be used in tests. Some may
also be useful in layer set-up/tear-down. We have used ``unittest`` syntax
here, although most of these examples could equally be adopted to doctests.
We will assume that you are using a layer that has ``PLONE_FIXTURE`` as a base
(whether directly or indirectly) and uses the ``IntegrationTesting`` or
``FunctionalTesting`` classes as shown above.
We will also assume that the variables ``app``, ``portal`` and ``request`` are
defined from the relative layer resources, e.g. with::
app = self.layer['app']
portal = self.layer['portal']
request = self.layer['request']
Note that in a doctest set up using the ``layered()`` function from
``plone.testing``, ``layer`` is in the global namespace, so you would do e.g.
``portal = layer['portal']``.
Where imports are required, they are shown alongside the code example. If
a given import or variable is used more than once in the same section, it
will only be shown once.
Basic content management
------------------------
To create a content item of type 'Folder' with the id 'f1' in the root of
the portal::
portal.invokeFactory('Folder', 'f1', title=u"Folder 1")
The ``title`` argument is optional. Other basic properties, like
``description``, can be set as well.
Note that this may fail with an ``Unauthorized`` exception, since the test
user won't normally have permissions to add content in the portal root, and
the ``invokeFactory()`` method performs an explicit security check. You can
set the roles of the test user to ensure that he has the necessary
permissions::
from plone.app.testing import setRoles
from plone.app.testing import TEST_USER_ID
setRoles(portal, TEST_USER_ID, ['Manager'])
portal.invokeFactory('Folder', 'f1', title=u"Folder 1")
To obtain this object, acquisition-wrapped in its parent::
f1 = portal['f1']
To make an assertion against an attribute or method of this object::
self.assertEqual(f1.Title(), u"Folder 1")
To modify the object::
f1.setTitle(u"Some title")
To add another item inside the folder f1::
f1.invokeFactory('Document', 'd1', title=u"Document 1")
d1 = f1['d1']
To check if an object is in a container::
self.assertTrue('f1' in portal)
To delete an object from a container:
del portal['f1']
There is no content or workflows installed by default. You can enable workflows::
portal.portal_workflow.setDefaultChain("simple_publication_workflow")
Searching
---------
To obtain the ``portal_catalog`` tool::
from Products.CMFCore.utils import getToolByName
catalog = getToolByName(portal, 'portal_catalog')
To search the catalog::
results = catalog(portal_type="Document")
Keyword arguments are search parameters. The result is a lazy list. You can
call ``len()`` on it to get the number of search results, or iterate through
it. The items in the list are catalog brains. They have attributes that
correspond to the "metadata" columns configured for the catalog, e.g.
``Title``, ``Description``, etc. Note that these are simple attributes (not
methods), and contain the value of the corresponding attribute or method from
the source object at the time the object was cataloged (i.e. they are not
necessarily up to date).
To make assertions against the search results::
self.assertEqual(len(results), 1)
# Copy the list into memory so that we can use [] notation
results = list(results)
# Check the first (and in this case only) result in the list
self.assertEqual(results[0].Title, u"Document 1")
To get the path of a given item in the search results::
self.assertEqual(results[0].getPath(), portal.absolute_url_path() + '/f1/d1')
To get an absolute URL::
self.assertEqual(results[0].getURL(), portal.absolute_url() + '/f1/d1')
To get the original object::
obj = results[0].getObject()
To re-index an object d1 so that its catalog information is up to date::
d1.reindexObject()
User management
---------------
To create a new user::
from Products.CMFCore.utils import getToolByName
acl_users = getToolByName(portal, 'acl_users')
acl_users.userFolderAddUser('user1', 'secret', ['Member'], [])
The arguments are the username (which will also be the user id), the password,
a list of roles, and a list of domains (rarely used).
To make a particular user active ("logged in") in the integration testing
environment use the ``login`` method and pass it the username::
from plone.app.testing import login
login(portal, 'user1')
To log out (become anonymous)::
from plone.app.testing import logout
logout()
To obtain the current user::
from AccessControl import getSecurityManager
user = getSecurityManager().getUser()
To obtain a user by name::
user = acl_users.getUser('user1')
Or by user id (id and username are often the same, but can differ in real-world
scenarios)::
user = acl_users.getUserById('user1')
To get the user's user name::
userName = user.getUserName()
To get the user's id::
userId = user.getId()
Permissions and roles
---------------------
To get a user's roles in a particular context (taking local roles into
account)::
from AccessControl import getSecurityManager
user = getSecurityManager().getUser()
self.assertEqual(user.getRolesInContext(portal), ['Member'])
To change the test user's roles::
from plone.app.testing import setRoles
from plone.app.testing import TEST_USER_ID
setRoles(portal, TEST_USER_ID, ['Member', 'Manager'])
Pass a different user name to change the roles of another user.
To grant local roles to a user in the folder f1::
f1.manage_setLocalRoles(TEST_USER_ID, ('Reviewer',))
To check the local roles of a given user in the folder 'f1'::
self.assertEqual(f1.get_local_roles_for_userid(TEST_USER_ID), ('Reviewer',))
To grant the 'View' permission to the roles 'Member' and 'Manager' in the
portal root without acquiring additional roles from its parents::
portal.manage_permission('View', ['Member', 'Manager'], acquire=False)
This method can also be invoked on a folder or individual content item.
To assert which roles have the permission 'View' in the context of the
portal::
roles = [r['name'] for r in portal.rolesOfPermission('View') if r['selected']]
self.assertEqual(roles, ['Member', 'Manager'])
To assert which permissions have been granted to the 'Reviewer' role in the
context of the portal::
permissions = [p['name'] for p in portal.permissionsOfRole('Reviewer') if p['selected']]
self.assertTrue('Review portal content' in permissions)
To add a new role::
portal._addRole('Tester')
This can now be assigned to users globally (using the ``setRoles`` helper)
or locally (using ``manage_setLocalRoles()``).
To assert which roles are available in a given context::
self.assertTrue('Tester' in portal.valid_roles())
Workflow
--------
To set the default workflow chain::
from Products.CMFCore.utils import getToolByName
workflowTool = getToolByName(portal, 'portal_workflow')
workflowTool.setDefaultChain('my_workflow')
In Plone, most chains contain only one workflow, but the ``portal_workflow``
tool supports longer chains, where an item is subject to more than one
workflow simultaneously.
To set a multi-workflow chain, separate workflow names by commas.
To get the default workflow chain::
self.assertEqual(workflowTool.getDefaultChain(), ('my_workflow',))
To set the workflow chain for the 'Document' type::
workflowTool.setChainForPortalTypes(('Document',), 'my_workflow')
You can pass multiple type names to set multiple chains at once. To set a
multi-workflow chain, separate workflow names by commas. To indicate that a
type should use the default workflow, use the special chain name '(Default)'.
To get the workflow chain for the portal type 'Document'::
chains = dict(workflowTool.listChainOverrides())
defaultChain = workflowTool.getDefaultChain()
documentChain = chains.get('Document', defaultChain)
self.assertEqual(documentChain, ('my_other_workflow',))
To get the current workflow chain for the content object f1::
self.assertEqual(workflowTool.getChainFor(f1), ('my_workflow',))
To update all permissions after changing the workflow::
workflowTool.updateRoleMappings()
To change the workflow state of the content object f1 by invoking the
transaction 'publish'::
workflowTool.doActionFor(f1, 'publish')
Note that this performs an explicit permission check, so if the current user
doesn't have permission to perform this workflow action, you may get an error
indicating the action is not available. If so, use ``login()`` or
``setRoles()`` to ensure the current user is able to change the workflow
state.
To check the current workflow state of the content object f1::
self.assertEqual(workflowTool.getInfoFor(f1, 'review_state'), 'published')
Properties
----------
To set the value of a property on the portal root::
portal._setPropValue('title', u"My title")
To assert the value of a property on the portal root::
self.assertEqual(portal.getProperty('title'), u"My title")
To change the value of a property in a property sheet in the
``portal_properties`` tool::
from Products.CMFCore.utils import getToolByName
propertiesTool = getToolByName(portal, 'portal_properties')
siteProperties = propertiesTool['site_properties']
siteProperties._setPropValue('many_users', True)
To assert the value of a property in a property sheet in the
``portal_properties`` tool::
self.assertEqual(siteProperties.getProperty('many_users'), True)
Installing products and extension profiles
------------------------------------------
To apply a particular extension profile::
from plone.app.testing import applyProfile
applyProfile(portal, 'my.product:default')
This is the preferred method of installing a product's configuration.
To install an add-on product into the Plone site using the
add-ons control panel::
from plone.app.testing import quickInstallProduct
quickInstallProduct(portal, 'my.product')
To uninstall and install a product using the add-ons control panel::
quickInstallProduct(portal, 'my.product', reinstall=True)
Note that both of these assume the product's ZCML has been loaded, which is
usually done during layer setup. See the layer examples above for more details
on how to do that.
When writing a product that has an installation extension profile, it is often
desirable to write tests that inspect the state of the site after the profile
has been applied. Some of the more common such tests are shown below.
To verify that a product has been installed (e.g. as a dependency via
``metadata.xml``)::
from Products.CMFPlone.utils import get_installer
qi = get_installer(portal)
self.assertTrue(qi.is_product_installed('my.product'))
To verify that a particular content type has been installed (e.g. via
``types.xml``)::
typesTool = getToolByName(portal, 'portal_types')
self.assertNotEqual(typesTool.getTypeInfo('mytype'), None)
To verify that a new catalog index has been installed (e.g. via
``catalog.xml``)::
catalog = getToolByName(portal, 'portal_catalog')
self.assertTrue('myindex' in catalog.indexes())
To verify that a new catalog metadata column has been added (e.g. via
``catalog.xml``)::
self.assertTrue('myattr' in catalog.schema())
To verify that a new workflow has been installed (e.g. via
``workflows.xml``)::
workflowTool = getToolByName(portal, 'portal_workflow')
self.assertNotEqual(workflowTool.getWorkflowById('my_workflow'), None)
To verify that a new workflow has been assigned to a type (e.g. via
``workflows.xml``)::
self.assertEqual(dict(workflowTool.listChainOverrides())['mytype'], ('my_workflow',))
To verify that a new workflow has been set as the default (e.g. via
``workflows.xml``)::
self.assertEqual(workflowTool.getDefaultChain(), ('my_workflow',))
To test the value of a property in the ``portal_properties`` tool (e.g. set
via ``propertiestool.xml``):::
propertiesTool = getToolByName(portal, 'portal_properties')
siteProperties = propertiesTool['site_properties']
self.assertEqual(siteProperties.getProperty('some_property'), "some value")
To verify that a stylesheet has been installed in the ``portal_css`` tool
(e.g. via ``cssregistry.xml``)::
cssRegistry = getToolByName(portal, 'portal_css')
self.assertTrue('mystyles.css' in cssRegistry.getResourceIds())
To verify that a JavaScript resource has been installed in the
``portal_javascripts`` tool (e.g. via ``jsregistry.xml``)::
jsRegistry = getToolByName(portal, 'portal_javascripts')
self.assertTrue('myscript.js' in jsRegistry.getResourceIds())
To verify that a new role has been added (e.g. via ``rolemap.xml``)::
self.assertTrue('NewRole' in portal.valid_roles())
To verify that a permission has been granted to a given set of roles (e.g. via
``rolemap.xml``)::
roles = [r['name'] for r in portal.rolesOfPermission('My Permission') if r['selected']]
self.assertEqual(roles, ['Member', 'Manager'])
Traversal
---------
To traverse to a view, page template or other resource, use
``restrictedTraverse()`` with a relative path::
resource = portal.restrictedTraverse('f1/@@folder_contents')
The return value is a view object, page template object, or other resource.
It may be invoked to obtain an actual response (see below).
``restrictedTraverse()`` performs an explicit security check, and so may
raise ``Unauthorized`` if the current test user does not have permission to
view the given resource. If you don't want that, you can use::
resource = portal.unrestrictedTraverse('f1/@@folder_contents')
You can call this on a folder or other content item as well, to traverse from
that starting point, e.g. this is equivalent to the first example above::
f1 = portal['f1']
resource = f1.restrictedTraverse('@@folder_contents')
Note that this traversal will not take ``IPublishTraverse`` adapters into
account, and you cannot pass query string parameters. In fact,
``restrictedTraverse()`` and ``unrestrictedTraverse()`` implement the type of
traversal that happens with path expressions in TAL, which is similar, but not
identical to URL traversal.
To look up a view manually::
from zope.component import getMultiAdapter
view = getMultiAdapter((f1, request), name=u"folder_contents")
Note that the name here should not include the ``@@`` prefix.
To simulate an ``IPublishTraverse`` adapter call, presuming the view
implements ``IPublishTraverse``::
next = view.IPublishTraverse(request, u"some-name")
Or, if the ``IPublishTraverse`` adapter is separate from the view::
from zope.publisher.interfaces import IPublishTraverse
publishTraverse = getMultiAdapter((f1, request), IPublishTraverse)
next = view.IPublishTraverse(request, u"some-name")
To simulate a form submission or query string parameters::
request.form.update({
'name': "John Smith",
'age': 23
})
The ``form`` dictionary contains the marshalled request. That is, if you are
simulating a query string parameter or posted form variable that uses a
marshaller like ``:int`` (e.g. ``age:int`` in the example above), the value
in the ``form`` dictionary should be marshalled (an int instead of a string,
in the example above), and the name should be the base name (``age`` instead
of ``age:int``).
To invoke a view and obtain the response body as a string::
view = f1.restrictedTraverse('@@folder_contents')
body = view()
self.assertFalse(u"An unexpected error occurred" in body)
Please note that this approach is not perfect. In particular, the request
is will not have the right URL or path information. If your view depends on
this, you can fake it by setting the relevant keys in the request, e.g.::
request.set('URL', f1.absolute_url() + '/@@folder_contents')
request.set('ACTUAL_URL', f1.absolute_url() + '/@@folder_contents')
To inspect the state of the request (e.g. after a view has been invoked)::
self.assertEqual(request.get('disable_border'), True)
To inspect response headers (e.g. after a view has been invoked)::
response = request.response
self.assertEqual(response.getHeader('content-type'), 'text/plain')
Simulating browser interaction
------------------------------
End-to-end functional tests can use `zope.testbrowser`_ to simulate user
interaction. This acts as a web browser, connecting to Zope via a special
channel, making requests and obtaining responses.
**Note:** zope.testbrowser runs entirely in Python, and does not simulate
a JavaScript engine.
Note that to use ``zope.testbrowser``, you need to use one of the functional
testing layers, e.g. ``PLONE_FUNCTIONAL_TESTING``, or another layer
instantiated with the ``FunctionalTesting`` class.
If you want to create some initial content, you can do so either in a layer,
or in the test itself, before invoking the test browser client. In the latter
case, you need to commit the transaction before it becomes available, e.g.::
from plone.app.testing import setRoles
from plone.app.testing import TEST_USER_ID
# Make some changes
setRoles(portal, TEST_USER_ID, ['Manager'])
portal.invokeFactory('Folder', 'f1', title=u"Folder 1")
setRoles(portal, TEST_USER_ID, ['Member'])
# Commit so that the test browser sees these changes
import transaction
transaction.commit()
To obtain a new test browser client::
from plone.testing.z2 import Browser
# This is usually self.app (Zope root) or site.portal (test Plone site root)
browser = Browser(app)
To open a given URL::
portalURL = portal.absolute_url()
browser.open(portalURL)
To inspect the response::
self.assertTrue(u"Welcome" in browser.contents)
To inspect response headers::
self.assertEqual(browser.headers['content-type'], 'text/html; charset=utf-8')
To follow a link::
browser.getLink('Edit').click()
This gets a link by its text. To get a link by HTML id::
browser.getLink(id='edit-link').click()
To verify the current URL::
self.assertEqual(portalURL + '/edit', browser.url)
To set a form control value::
browser.getControl('Age').value = u"30"
This gets the control by its associated label. To get a control by its form
variable name::
browser.getControl(name='age:int').value = u"30"
See the `zope.testbrowser`_ documentation for more details on how to select
and manipulate various types of controls.
To submit a form by clicking a button::
browser.getControl('Save').click()
Again, this uses the label to find the control. To use the form variable
name::
browser.getControl(name='form.button.Save').click()
To simulate HTTP BASIC authentication and remain logged in for all
requests::
from plone.app.testing import TEST_USER_NAME, TEST_USER_PASSWORD
browser.addHeader('Authorization', 'Basic %s:%s' % (TEST_USER_NAME, TEST_USER_PASSWORD,))
To simulate logging in via the login form::
browser.open(portalURL + '/login_form')
browser.getControl(name='__ac_name').value = TEST_USER_NAME
browser.getControl(name='__ac_password').value = TEST_USER_PASSWORD
browser.getControl(name='submit').click()
To simulate logging out::
browser.open(portalURL + '/logout')
Debugging tips
~~~~~~~~~~~~~~
By default, only HTTP error codes (e.g. 500 Server Side Error) are shown when
an error occurs on the server. To see more details, set ``handleErrors`` to
False::
browser.handleErrors = False
To inspect the error log and obtain a full traceback of the latest entry::
from Products.CMFCore.utils import getToolByName
errorLog = getToolByName(portal, 'error_log')
print errorLog.getLogEntries()[-1]['tb_text']
To save the current response to an HTML file::
open('/tmp/testbrowser.html', 'w').write(browser.contents)
You can now open this file and use tools like Firebug to inspect the structure
of the page. You should remove the file afterwards.
Comparison with ZopeTestCase/PloneTestCase
==========================================
`plone.testing`_ and ``plone.app.testing`` have in part evolved from
``ZopeTestCase``, which ships with Zope 2 in the ``Testing`` package, and
`Products.PloneTestCase`_, which ships with Plone and is used by Plone itself
as well as numerous add-on products.
If you are familiar with ``ZopeTestCase`` and ``PloneTestCase``, the concepts
of these package should be familiar to you. However, there are some important
differences to bear in mind.
* ``plone.testing`` and ``plone.app.testing`` are unburdened by the legacy
support that ``ZopeTestCase`` and ``PloneTestCase`` have to include. This
makes them smaller and easier to understand and maintain.
* Conversely, ``plone.testing`` only works with Python 2.6 and Zope 2.12 and
later. ``plone.app.testing`` only works with Plone 4 and later. If you need
to write tests that run against older versions of Plone, you'll need to use
``PloneTestCase``.
* ``ZopeTestCase``/``PloneTestCase`` were written before layers were available
as a setup mechanism. ``plone.testing`` is very layer-oriented.
* ``PloneTestCase`` provides a base class, also called ``PloneTestCase``,
which you must use, as it performs setup and tear-down. ``plone.testing``
moves shared state to layers and layer resources, and does not impose any
particular base class for tests. This does sometimes mean a little more
typing (e.g. ``self.layer['portal']`` vs. ``self.portal``), but it makes
it much easier to control and reuse test fixtures. It also makes your
test code simpler and more explicit.
* ``ZopeTestCase`` has an ``installProduct()`` function and a corresponding
``installPackage()`` function. `plone.testing`_ has only an
``installProduct()``, which can configure any kind of Zope 2 product (i.e.
packages in the ``Products.*`` namespace, old-style products in a special
``Products`` folder, or packages in any namespace that have had their ZCML
loaded and which include a ``<five:registerPackage />`` directive in their
configuration). Note that you must pass a full dotted name to this function,
even for "old-style" products in the ``Products.*`` namespace, e.g.
``Products.LinguaPlone`` instead of ``LinguaPlone``.
* On setup, ``PloneTestCase`` will load Zope 2's default ``site.zcml``. This
in turn will load all ZCML for all packages in the ``Products.*`` namespace.
``plone.testing`` does not do this (and you are strongly encouraged from
doing it yourself), because it is easy to accidentally include packages in
your fixture that you didn't intend to be there (and which can actually
change the fixture substantially). You should load your package's ZCML
explicitly. See the `plone.testing`_ documentation for details.
* When using ``PloneTestCase``, any package that has been loaded onto
``sys.path`` and which defines the ``z3c.autoinclude.plugin:plone`` entry
point will be loaded via `z3c.autoinclude`_'s plugin mechanism. This loading
is explicitly disabled, for the same reasons that the ``Products.*`` auto-
loading is. You should load your packages' configuration explicitly.
* ``PloneTestCase`` sets up a basic fixture that has member folder enabled,
and in which the test user's member folder is available as ``self.folder``.
The ``plone_workflow`` workflow is also installed as the default.
``plone.app.testing`` takes a more minimalist approach. To create a test
folder owned by the test user that is similar to ``self.folder`` in a
``PloneTestCase``, you can do::
import unittest2 as unittest
from plone.app.testing import TEST_USER_ID, setRoles
from plone.app.testing import PLONE_INTEGRATION_TESTING
class MyTest(unitest.TestCase):
layer = PLONE_INTEGRATION_TESTING
def setUp(self):
self.portal = self.layer['portal']
setRoles(self.portal, TEST_USER_ID, ['Manager'])
self.portal.invokeFactory('Folder', 'test-folder')
setRoles(self.portal, TEST_USER_ID, ['Member'])
self.folder = self.portal['test-folder']
You could of course do this type of setup in your own layer and expose it
as a resource instead.
* To use `zope.testbrowser`_ with ``PloneTestCase``, you should use its
``FunctionalTestCase`` as a base class, and then use the following pattern::
from Products.Five.testbrowser import Browser
browser = Browser()
The equivalent pattern in ``plone.app.testing`` is to use the
``FunctionalTesting`` test lifecycle layer (see example above), and then
use::
from plone.testing.z2 import Browser
browser = Browser(self.layer['app'])
Also note that if you have made changes to the fixture prior to calling
``browser.open()``, they will *not* be visible until you perform an
explicit commit. See the ``zope.testbrowser`` examples above for details.
.. _plone.testing: http://pypi.python.org/pypi/plone.testing
.. _zope.testing: http://pypi.python.org/pypi/zope.testing
.. _z3c.autoinclude: http://pypi.python.org/pypi/z3c.autoinclude
.. _zope.testbrowser: http://pypi.python.org/pypi/zope.testbrowser
.. _Products.PloneTestCase: http://pypi.python.org/pypi/Products.PloneTestCase
Changelog
=========
.. You should *NOT* be adding new change log entries to this file.
You should create a file in the news directory instead.
For helpful instructions, please see:
https://github.com/plone/plone.releaser/blob/master/ADD-A-NEWS-ITEM.rst
.. towncrier release notes start
7.1.0 (2024-07-31)
------------------
New features:
- PloneFixture: explicitly install plone.app.contenttypes:default.
The `addPloneSite` factory in Plone 6.1 no longer installs this by default.
[maurits] (#3961)
Bug fixes:
- Remove setuptools fossils.
[maurits] (#72)
7.0.2 (2024-01-19)
------------------
Internal:
- Update configuration files.
[plone devs] (cfffba8c)
7.0.1 (2023-04-15)
------------------
Internal:
- Update configuration files.
[plone devs] (434550cc)
7.0.0 (2022-12-02)
------------------
Bug fixes:
- Final release for Plone 6.0.0. (#600)
7.0.0b2 (2022-10-11)
--------------------
Bug fixes:
- Restore the previously used admin password for tests ("secret") [davisagli] (#79)
7.0.0b1 (2022-09-30)
--------------------
Bug fixes:
- Increase the test password length. [davisagli] (#78)
7.0.0a3 (2022-04-28)
--------------------
New features:
- Load CMFFormController in Plone 5, be silent when this fails in Plone 6.
This restores compatibility with Plone 5.2 for now.
Note that this ``plone.app.testing`` version is only meant for Python 3.
[maurits] (#3467)
7.0.0a2 (2022-04-04)
--------------------
Bug fixes:
- Add savepoint after creating the portal in PloneFixture.
Fixes `issue 3467 <https://github.com/plone/Products.CMFPlone/issues/3467>`_.
[pbauer] (#76)
7.0.0a1 (2022-03-23)
--------------------
Breaking changes:
- Plone 6 only. (#75)
New features:
- remove CMFFormController
[petschki] (#75)
6.1.9 (2021-09-01)
------------------
Bug fixes:
- Fixed test that failed for dexterity site root.
[jaroel, ale-rt] (#60)
6.1.8 (2020-11-11)
------------------
Bug fixes:
- Before trying to load the zcml of plone.app.folder, double check if it is a real package or an alias provided by plone.app.upgrade (#72)
6.1.7 (2020-10-12)
------------------
New features:
- Removed backwards compatibility code for old quickinstaller.
Current plone.app.testing is only for Plone 5.2+, so this code was no longer used.
See also `PLIP 1775 <https://github.com/plone/Products.CMFPlone/issues/1775>`_.
[maurits] (#1775)
6.1.6 (2020-09-26)
------------------
Bug fixes:
- Fixed test failure on Python 3 with Products.MailHost 4.10.
[maurits] (#3178)
6.1.5 (2020-04-20)
------------------
Bug fixes:
- Minor packaging updates. (#1)
6.1.4 (2020-03-09)
------------------
Bug fixes:
- Fix a test isolation issue that was preventing the MOCK_MAILHOST_FIXTURE to be used in multiple testcases [ale-rt] (#61)
- MockMailHostLayer configures the mail sender setting the appropriate registry records (Fixes #62) (#62)
- Fix tests when using zope.testrunner internals since its version 5.1.
[jensens] (#68)
- Do not load Products/ZCML of no longer existing Products.ResourceRegistries.
[jensens] (#69)
6.1.3 (2019-02-16)
------------------
Bug fixes:
- Make plone.app.folder a optional product for PloneFixture in py2 (#59)
6.1.2 (2019-02-13)
------------------
Bug fixes:
- Fixed the travis build checking the Python versions Plone actually supports.
Also fixed Python versions in setup.py (#57)
6.1.1 (2018-11-05)
------------------
Bug fixes:
- Fix the package manifest that was not including some files
[ale-rt]
6.1.0 (2018-11-04)
------------------
Breaking changes:
- Require `plone.testing >= 7.0`.
New features:
- Add support for Python 3.5 and 3.6.
[loechel, ale-rt, icemac, davisagli, pbauer]
6.0.0 (2018-10-05)
------------------
New features:
- Install and load zcml of CMFQuickInstallerTool only when importable.
[maurits]
- Load negotiator from plone.i18n (PTS removed).
[jensens, ksuess]
- Add copy of bbb.PloneTestCase.
For Plone 5.2 the bbb.PloneTestCase will uses Dexterity instead of Archetypes.
Adding bbb_at.PloneTestCase for them to use allows to keep the AT tests working.
See https://github.com/plone/plone.app.testing/pull/51
[pbauer]
Bug fixes:
- Amended the doctests to work with automatically layer port picking from plone.testing.
[Rotonen]
5.0.8 (2017-10-25)
------------------
Bug fixes:
- Load Products.PageTemplates ZCML. [tschorr]
5.0.7 (2017-07-03)
------------------
Bug fixes:
- Remove deprecated __of__ calls on BrowserViews
[MrTango]
- Remove unittest2 dependency
[kakshay21]
5.0.6 (2016-12-19)
------------------
Bug fixes:
- No longer try to load `Products.SecureMailHost` and its zcml.
This is not shipped with Plone 5.0 or higher. [maurits]
5.0.5 (2016-11-19)
------------------
Bug fixes:
- Do not use install Products.PasswordResetTool in the PloneFixture if it isn't available.
[thet]
5.0.4 (2016-09-23)
------------------
New features:
- Use get_installer instead of portal_quickinstaller when available, for
Plone 5.1 and higher. [maurits]
- In PloneSandboxLayer make profile upgrade versions persistent. This
way installed profile versions get reset in teardown. [maurits]
5.0.3 (2016-09-07)
------------------
Bug fixes:
- Load Products.CMFFormController in tests. It is still used by core
Plone, also without Archetypes. This makes the CMFFormController
tests pass. [maurits]
5.0.2 (2016-06-07)
------------------
Fixes:
- Do not use install Products.SecureMailHost in the PloneFixture if it isn't available
[vangheem]
5.0.1 (2016-02-26)
------------------
Fixes:
- Replace deprecated ``zope.site.hooks`` import with ``zope.component.hooks``.
[thet]
5.0.0 (2016-02-20)
------------------
New:
- Add a MOCK_MAILHOST_FIXTURE fixture that integration and functional tests layers can depend on.
This allows to easily check how mails are sent from Plone.
[gforcada]
Fixes:
- Fix ``layers.rst`` doctest to be compatible with older and newer zope.testrunner layer ordering.
[thet]
- Depend on ``zope.testrunner`` and fix deprecated usage of ``zope.testing.testrunner``.
[thet]
- Cleanup code, flake8, sort imports, etc.
[gforcada]
- Fix RAM cache error with bbb.PloneTestCase.
[ebrehault]
5.0b6 (2015-08-22)
------------------
- No need for unittest2.
[gforcada]
5.0b5 (2015-07-18)
------------------
- Do not install CMFDefault.
[tomgross]
- Document PloneWithPackageLayer.
[gotcha]
5.0b4 (2015-05-04)
------------------
- Do not install CMFFormController.
[timo]
- Do not install CMFDefault
[tomgross]
5.0b3 (2015-03-26)
------------------
- Remove PloneLanguageTool from PloneFixture.
[timo]
5.0b2 (2015-03-13)
------------------
- remove test of applying an extension profile, we don't have a good one to
test now.
[davidagli]
- fix test, plone.app.theming does not get recorded as installed .
[davisagli]
- fix: ``Products.CMFPlone`` needs the ``gopip`` index from
``plone.app.folder``. So latter has to be initialized before CMFPlones
profile is applied (which installs the index to catalog). At the moment
CMFPlone therefore registers the index itself, but plone.app.folder
registers it too, which resulted in plone/Products.CMFPlone#313
"GopipIndex registered twice" In tests the registration does not succeed,
because plone.app.folder was never initialized as z2 products. In order to
remove the misleading regisatration from CMFPlone we must take care that the
index is available, which is achieved with this change. Also minor pep8
optimizations in the file touched.
[jensens]
- create memberfolder, if it is not there for testing.
[tomgross]
5.0b1 (2014-10-23)
------------------
- Allow applyProfile to skip steps and all other options supported by
runAllImportStepsFromProfile of portal_setup-tool.
[pbauer, tomgross]
5.0a2 (2014-04-19)
------------------
- Install Products.DateRecurringIndex for the PLONE_FIXTURE Layer.
[thet]
5.0a1 (2014-02-22)
------------------
- Add 'ROBOT_TEST_LEVEL' to interfaces, so other packages can import it. This
makes things easier if we decide to change the value.
[timo]
- Replace deprecated test assert statements.
[timo]
- plonetheme.classic no longer ships with Plone, don't use it for
testing.
[esteele]
- Clean up the zodbDB and configurationContext resources if there
is an error during the PloneSandboxLayer setUp.
[davisagli]
- Make PLONE_FIXTURE not install a content type system.
Packages that need content types to run their tests should
pick the appropriate fixture from plone.app.contenttypes
or Products.ATContentTypes.
[davisagli]
- Pin [robot] extra to ``robotsuite>=1.4.0``.
[saily]
- Fix wrong spelling of ``reinstallProducts`` method in quickInstallProduct.
[saily]
- Sync bbb PloneTestCase class with original one.
[tomgross]
4.2.2 (2013-02-09)
------------------
- Add [robot] extras for requiring dependnecies for Robot Framework
tests with Selenium2Library
[datakurre]
- Install PythonScripts as zope product
[mikejmets]
4.2.1 (2012-12-15)
------------------
- Allow testing with non standard port. Allows running multiple test suites
in parallel.
[do3cc]
- Documentation updates.
[moo]
4.2 (2012-04-15)
----------------
- Branch as 4.2 as the plone.app.collection addition breaks backwards
compatibility.
[esteele]
- Fixed spurious failure in our own tests by using a longer timeout.
[maurits]
- plone.app.collection added to PloneFixture.
[timo]
4.0.2 (2011-08-31)
------------------
- Load ZCML before installing Zope products in ``PloneWithPackageLayer``;
it enables package registration.
[gotcha]
4.0.1 (2011-07-14)
------------------
- Add ``additional_z2_products`` parameter to ``PloneWithPackageLayer``
helper class to install additional Zope 2 products.
[jfroche]
4.0 - 2011-05-13
------------------
- 4.0 Final release.
[esteele]
- Add MANIFEST.in.
[WouterVH]
4.0a6 - 2011-04-06
------------------
- Added helper functions for selenium layer. (Copied from SeleniumTestCase
within Products.CMFPlone/Products/CMFPlone/tests/selenium/base.py)
[emanlove]
- Rework layer setup of SeleniumLayer so that z2.ZSERVER_FIXTURE is a
default_base.
[esteele]
- Convert the passed-in selenium webdriver name to lowercase before doing a
module lookup.
[esteele]
- Moved selenium start up and tear down to testSetUp and testTearDown,
respectively. This was done to help further isolate individual tests.
For example, logging in under one test would require either logging out
or shutting down the browser, which is what the selenium_layer will now
do under testTearDown, in order to have a "clean" state within the next
test.
[emanlove]
- Corrected module path for the various selenium webdrivers using
selenium 2.0b2.
[emanlove]
4.0a5 - 2011-03-02
------------------
- Use the new ``plone.testing.security`` module to ensure isolation of
security checkers when setting up and tearing down layers based on the
``PloneSandboxLayer`` helper base class. This would cause problems when
running multiple test suites in the same test run, in particular if one of
those suites were setting up ZCML that used ``five.grok``.
[optilude]
4.0a4 - 2011-01-11
------------------
- Automatically tear down PAS registrations via snapshotting when using
``PloneSandboxLayer``. It's too difficult to do this manually when you
consider that plugins may be registered in ZCML via transitive dependencies.
There should be no backwards compatibility concern - using
``tearDownMultiPlugin()`` is still supported, and it's generally safe to
call it once.
[optilude]
- Try to make sure ``tearDownMultiPlugin()`` and the generic PAS plugin
cleanup handler do not interfere with the cleanup handler from the PAS
ZCML directive.
[optilude]
- Do not install ``Products.kupu`` or ``Products.CMFPlacefulWorkflow``.
[elro]
- Depend on ``Products.CMFPlone`` instead of ``Plone``.
[elro]
4.0a3 - 2010-12-14
------------------
- Allow top-level import of PloneTestLifecycle.
[stefan]
- Added a warning not to use 'default' Firefox profile for selenium tests.
[zupo]
- Fixed distribution dependency declarations.
[hannosch]
- Correct license to GPL version 2 only.
[hannosch]
- Make some module imports helper methods on the already policy-heavy
helper class per optilude's suggestion.
[rossp]
- Add a layer and test case for running selenium tests.
[rossp]
- Give the default test user differing user id and login name. This helps reveal
problems with userid vs login name errors, an overly common error.
[wichert]
1.0a2 - 2010-09-05
------------------
- Make sure plone.app.imaging is installed properly during layer setup.
[optilude]
1.0a1 - 2010-08-01
------------------
- Initial release
Raw data
{
"_id": null,
"home_page": "https://pypi.org/project/plone.app.testing",
"name": "plone.app.testing",
"maintainer": null,
"docs_url": null,
"requires_python": ">=3.7",
"maintainer_email": null,
"keywords": "plone tests",
"author": "Plone Foundation",
"author_email": "plone-developers@lists.sourceforge.net",
"download_url": "https://files.pythonhosted.org/packages/6c/a2/66273ba2a9a5f2a45f7efd8dad2c9460040e3d8fbc94c1f039209d8d2270/plone_app_testing-7.1.0.tar.gz",
"platform": null,
"description": "Introduction\n============\n\n.. contents:: Table of contents\n\n``plone.app.testing`` provides tools for writing integration and functional\ntests for code that runs on top of Plone. It is based on `plone.testing`_.\nIf you are unfamiliar with ``plone.testing``, the concept of layers, or the\n`zope.testing`_ testrunner, please take a look at the the ``plone.testing``\ndocumentation. In fact, even if you are working exclusively with Plone, you\nare likely to want to use some of its features for unit testing.\n\nIn short, ``plone.app.testing`` includes:\n\n* A set of layers that set up fixtures containing a Plone site, intended for\n writing integration and functional tests.\n* A collection of helper functions, some useful for writing your own layers\n and some applicable to tests themselves.\n* A convenient layer base class, extending ``plone.testing.Layer``, which\n makes it easier to write custom layers extending the Plone site fixture,\n with proper isolation and tear-down.\n* Cleanup hooks for ``zope.testing.cleanup`` to clean up global state found\n in a Plone installation. This is useful for unit testing.\n\nCompatibility\n-------------\n\n``plone.app.testing`` 5.x works with Plone 5.\n``plone.app.testing`` 4.x works with Plone 4 and Zope 2.12. It may work with\nnewer versions. It will not work with earlier versions. Use\n``plone.app.testing`` 3.x for Plone 3 and Zope 2.10.\n\nInstallation and usage\n======================\n\nTo use ``plone.app.testing`` in your own package, you need to add it as a\ndependency. Most people prefer to keep test-only dependencies separate, so\nthat they do not need to be installed in scenarios (such as on a production\nserver) where the tests will not be run. This can be achieved using a\n``test`` extra.\n\nIn ``setup.py``, add or modify the ``extras_require`` option, like so::\n\n extras_require = {\n 'test': [\n 'plone.app.testing',\n ]\n },\n\nThis will also include ``plone.testing``, with the ``[z2]``, ``[zca]`` and\n``[zodb]`` extras (which ``plone.app.testing`` itself relies on).\n\nPlease see the `plone.testing`_ documentation for more details about how to\nadd a test runner to your buildout, and how to write and run tests.\n\nLayer reference\n===============\n\nThis package contains a layer class,\n``plone.app.testing.layers.PloneFixture``, which sets up a Plone site fixture.\nIt is combined with other layers from `plone.testing`_ to provide a number of\nlayer instances. It is important to realise that these layers all have the\nsame fundamental fixture: they just manage test setup and tear-down\ndifferently.\n\nWhen set up, the fixture will:\n\n* Create a ZODB sandbox, via a stacked ``DemoStorage``. This ensures\n persistent changes made during layer setup can be cleanly torn down.\n* Configure a global component registry sandbox. This ensures that global\n component registrations (e.g. as a result of loading ZCML configuration)\n can be cleanly torn down.\n* Create a configuration context with the ``disable-autoinclude`` feature\n set. This has the effect of stopping Plone from automatically loading the\n configuration of any installed package that uses the\n ``z3c.autoinclude.plugin:plone`` entry point via `z3c.autoinclude`_. (This\n is to avoid accidentally polluting the test fixture - custom layers should\n load packages' ZCML configuration explicitly if required).\n* Install a number of Zope 2-style products on which Plone depends.\n* Load the ZCML for these products, and for ``Products.CMFPlone``, which in\n turn pulls in the configuration for the core of Plone.\n* Create a default Plone site, with the default theme enabled, but with no\n default content.\n* Add a user to the root user folder with the ``Manager`` role.\n* Add a test user to this instance with the ``Member`` role.\n\nFor each test:\n\n* The test user is logged in\n* The local component site is set\n* Various global caches are cleaned up\n\nVarious constants in the module ``plone.app.testing.interfaces`` are defined\nto describe this environment:\n\n+----------------------+--------------------------------------------------+\n| **Constant** | **Purpose** |\n+----------------------+--------------------------------------------------+\n| PLONE_SITE_ID | The id of the Plone site object inside the Zope |\n| | application root. |\n+----------------------+--------------------------------------------------+\n| PLONE_SITE_TITLE | The title of the Plone site |\n+----------------------+--------------------------------------------------+\n| DEFAULT_LANGUAGE | The default language of the Plone site ('en') |\n+----------------------+--------------------------------------------------+\n| TEST_USER_ID | The id of the test user |\n+----------------------+--------------------------------------------------+\n| TEST_USER_NAME | The username of the test user |\n+----------------------+--------------------------------------------------+\n| TEST_USER_PASSWORD | The password of the test user |\n+----------------------+--------------------------------------------------+\n| TEST_USER_ROLES | The default global roles of the test user - |\n| | ('Member',) |\n+----------------------+--------------------------------------------------+\n| SITE_OWNER_NAME | The username of the user owning the Plone site. |\n+----------------------+--------------------------------------------------+\n| SITE_OWNER_PASSWORD | The password of the user owning the Plone site. |\n+----------------------+--------------------------------------------------+\n\nAll the layers also expose a resource in addition to those from their\nbase layers, made available during tests:\n\n``portal``\n The Plone site root.\n\nPlone site fixture\n------------------\n\n+------------+--------------------------------------------------+\n| Layer: | ``plone.app.testing.PLONE_FIXTURE`` |\n+------------+--------------------------------------------------+\n| Class: | ``plone.app.testing.layers.PloneFixture`` |\n+------------+--------------------------------------------------+\n| Bases: | ``plone.testing.z2.STARTUP`` |\n+------------+--------------------------------------------------+\n| Resources: | |\n+------------+--------------------------------------------------+\n\nThis layer sets up the Plone site fixture on top of the ``z2.STARTUP``\nfixture.\n\nYou should not use this layer directly, as it does not provide any test\nlifecycle or transaction management. Instead, you should use a layer\ncreated with either the ``IntegrationTesting`` or ``FunctionalTesting``\nclasses, as outlined below.\n\nMock MailHost\n-------------\n\n+------------+--------------------------------------------------+\n| Layer: | ``plone.app.testing.MOCK_MAILHOST_FIXTURE`` |\n+------------+--------------------------------------------------+\n| Class: | ``plone.app.testing.layers.MockMailHostLayer`` |\n+------------+--------------------------------------------------+\n| Bases: | ``plone.app.testing.layers.PLONE_FIXTURE`` |\n+------------+--------------------------------------------------+\n| Resources: | |\n+------------+--------------------------------------------------+\n\nThis layer builds on top of ``PLONE_FIXTURE`` to patch Plone's MailHost implementation.\n\nWith it,\nany attempt to send an email will instead store each of them as a string in a list in ``portal.MailHost.messages``.\n\nYou should not use this layer directly, as it does not provide any test\nlifecycle or transaction management. Instead, you should use a layer\ncreated with either the ``IntegrationTesting`` or ``FunctionalTesting``\nclasses, like::\n\n from plone.app.testing import MOCK_MAILHOST_FIXTURE\n\n MY_INTEGRATION_TESTING = IntegrationTesting(\n bases=(\n MY_FIXTURE,\n MOCK_MAILHOST_FIXTURE,\n ),\n name=\"MyFixture:Integration\"\n )\n\n\nPloneWithPackageLayer class\n---------------------------\n\nMost add-ons do not need more setup than loading a ZCML file and\nrunning a GenericSetup profile.\n\nWith this helper class, a fixture can easily be instantiated::\n\n from plone.app.testing import PloneWithPackageLayer\n import my.addon\n\n FIXTURE = PloneWithPackageLayer(\n zcml_package=my.addon,\n zcml_filename='configure.zcml',\n gs_profile_id='my.addon:default',\n name=\"MyAddonFixture\"\n )\n\nPloneWithPackageLayer constructor takes two other keyword arguments:\n``bases`` and ``additional_z2_products``.\n\nThe ``bases`` argument takes a sequence of base layer fixtures.\nIt is useful, among other reasons,\nto pass a fixture which makes other calls to plone.app.testing API.\nThe need could arise in the development process.\n\n``additional_z2_products`` argument takes a sequence of package names\nthat need to be installed as Zope2 Products and are dependencies of the tested add-on.\n\nIntegration and functional testing test lifecycles\n--------------------------------------------------\n\n``plone.app.testing`` comes with two layer classes, ``IntegrationTesting``\nand ``FunctionalTesting``, which derive from the corresponding layer classes\nin ``plone.testing.z2``.\n\nThese classes set up the ``app``, ``request`` and ``portal`` resources, and\nreset the fixture (including various global caches) between each test run.\n\nAs with the classes in ``plone.testing``, the ``IntegrationTesting`` class\nwill create a new transaction for each test and roll it back on test tear-\ndown, which is efficient for integration testing, whilst ``FunctionalTesting``\nwill create a stacked ``DemoStorage`` for each test and pop it on test tear-\ndown, making it possible to exercise code that performs an explicit commit\n(e.g. via tests that use ``zope.testbrowser``).\n\nWhen creating a custom fixture, the usual pattern is to create a new layer\nclass that has ``PLONE_FIXTURE`` as its default base, instantiating that as a\nseparate \"fixture\" layer. This layer is not to be used in tests directly,\nsince it won't have test/transaction lifecycle management, but represents a\nshared fixture, potentially for both functional and integration testing. It\nis also the point of extension for other layers that follow the same pattern.\n\nOnce this fixture has been defined, \"end-user\" layers can be defined using\nthe ``IntegrationTesting`` and ``FunctionalTesting`` classes. For example::\n\n from plone.testing import Layer\n from plone.app.testing import PLONE_FIXTURE\n from plone.app.testing import IntegrationTesting, FunctionalTesting\n\n class MyFixture(Layer):\n defaultBases = (PLONE_FIXTURE,)\n\n ...\n\n MY_FIXTURE = MyFixture()\n\n MY_INTEGRATION_TESTING = IntegrationTesting(bases=(MY_FIXTURE,), name=\"MyFixture:Integration\")\n MY_FUNCTIONAL_TESTING = FunctionalTesting(bases=(MY_FIXTURE,), name=\"MyFixture:Functional\")\n\nSee the ``PloneSandboxLayer`` layer below for a more comprehensive example.\n\nPlone integration testing\n-------------------------\n\n+------------+--------------------------------------------------+\n| Layer: | ``plone.app.testing.PLONE_INTEGRATION_TESTING`` |\n+------------+--------------------------------------------------+\n| Class: | ``plone.app.testing.layers.IntegrationTesting`` |\n+------------+--------------------------------------------------+\n| Bases: | ``plone.app.testing.PLONE_FIXTURE`` |\n+------------+--------------------------------------------------+\n| Resources: | ``portal`` (test setup only) |\n+------------+--------------------------------------------------+\n\nThis layer can be used for integration testing against the basic\n``PLONE_FIXTURE`` layer.\n\nYou can use this directly in your tests if you do not need to set up any\nother shared fixture.\n\nHowever, you would normally not extend this layer - see above.\n\n\nPlone functional testing\n------------------------\n\n+------------+--------------------------------------------------+\n| Layer: | ``plone.app.testing.PLONE_FUNCTIONAL_TESTING`` |\n+------------+--------------------------------------------------+\n| Class: | ``plone.app.testing.layers.FunctionalTesting`` |\n+------------+--------------------------------------------------+\n| Bases: | ``plone.app.testing.PLONE_FIXTURE`` |\n+------------+--------------------------------------------------+\n| Resources: | ``portal`` (test setup only) |\n+------------+--------------------------------------------------+\n\nThis layer can be used for functional testing against the basic\n``PLONE_FIXTURE`` layer, for example using ``zope.testbrowser``.\n\nYou can use this directly in your tests if you do not need to set up any\nother shared fixture.\n\nAgain, you would normally not extend this layer - see above.\n\nPlone ZServer\n-------------\n\n+------------+--------------------------------------------------+\n| Layer: | ``plone.app.testing.PLONE_ZSERVER`` |\n+------------+--------------------------------------------------+\n| Class: | ``plone.testing.z2.ZServer`` |\n+------------+--------------------------------------------------+\n| Bases: | ``plone.app.testing.PLONE_FUNCTIONAL_TESTING`` |\n+------------+--------------------------------------------------+\n| Resources: | ``portal`` (test setup only) |\n+------------+--------------------------------------------------+\n\nThis is layer is intended for functional testing using a live, running HTTP\nserver, e.g. using Selenium or Windmill.\n\nAgain, you would not normally extend this layer. To create a custom layer\nthat has a running ZServer, you can use the same pattern as this one, e.g.::\n\n from plone.testing import Layer\n from plone.testing import z2\n from plone.app.testing import PLONE_FIXTURE\n from plone.app.testing import FunctionalTesting\n\n class MyFixture(Layer):\n defaultBases = (PLONE_FIXTURE,)\n\n ...\n\n MY_FIXTURE = MyFixture()\n MY_ZSERVER = FunctionalTesting(bases=(MY_FIXTURE, z2.ZSERVER_FIXTURE), name='MyFixture:ZServer')\n\nSee the description of the ``z2.ZSERVER`` layer in `plone.testing`_\nfor further details.\n\nPlone FTP server\n----------------\n\n+------------+--------------------------------------------------+\n| Layer: | ``plone.app.testing.PLONE_FTP_SERVER`` |\n+------------+--------------------------------------------------+\n| Class: | ``plone.app.testing.layers.FunctionalTesting`` |\n+------------+--------------------------------------------------+\n| Bases: | ``plone.app.testing.PLONE_FIXTURE`` |\n| | ``plone.testing.z2.ZSERVER_FIXTURE`` |\n+------------+--------------------------------------------------+\n| Resources: | ``portal`` (test setup only) |\n+------------+--------------------------------------------------+\n\nThis is layer is intended for functional testing using a live FTP server.\n\nIt is semantically equivalent to the ``PLONE_ZSERVER`` layer.\n\nSee the description of the ``z2.FTP_SERVER`` layer in `plone.testing`_\nfor further details.\n\nHelper functions\n================\n\nA number of helper functions are provided for use in tests and custom layers.\n\nPlone site context manager\n--------------------------\n\n``ploneSite(db=None, connection=None, environ=None)``\n Use this context manager to access and make changes to the Plone site\n during layer setup. In most cases, you will use it without arguments,\n but if you have special needs, you can tie it to a particular database\n instance. See the description of the ``zopeApp()`` context manager in\n `plone.testing`_ (which this context manager uses internally) for details.\n\n The usual pattern is to call it during ``setUp()`` or ``tearDown()`` in\n your own layers::\n\n from plone.testing import Layer\n from plone.app.testing import ploneSite\n\n class MyLayer(Layer):\n\n def setUp(self):\n\n ...\n\n with ploneSite() as portal:\n\n # perform operations on the portal, e.g.\n portal.title = u\"New title\"\n\n Here, ``portal`` is the Plone site root. A transaction is begun before\n entering the ``with`` block, and will be committed upon exiting the block,\n unless an exception is raised, in which case it will be rolled back.\n\n Inside the block, the local component site is set to the Plone site root,\n so that local component lookups should work.\n\n **Warning:** Do not attempt to load ZCML files inside a ``ploneSite``\n block. Because the local site is set to the Plone site, you may end up\n accidentally registering components in the local site manager, which can\n cause pickling errors later.\n\n **Note:** You should not use this in a test, or in a ``testSetUp()`` or\n ``testTearDown()`` method of a layer based on one of the layer in this\n package. Use the ``portal`` resource instead.\n\n **Also note:** If you are writing a layer setting up a Plone site fixture,\n you may want to use the ``PloneSandboxLayer`` layer base class, and\n implement the ``setUpZope()``, ``setUpPloneSite()``, ``tearDownZope()``\n and/or ``tearDownPloneSite()`` methods instead. See below.\n\nUser management\n---------------\n\n``login(portal, userName)``\n Simulate login as the given user. This is based on the ``z2.login()``\n helper in `plone.testing`_, but instead of passing a specific user folder,\n you pass the portal (e.g. as obtained via the ``portal`` layer resource).\n\n For example::\n\n import unittest2 as unittest\n\n from plone.app.testing import PLONE_INTEGRATION_TESTING\n from plone.app.testing import TEST_USER_NAME\n from plone.app.testing import login\n\n ...\n\n class MyTest(unittest.TestCase):\n\n layer = PLONE_INTEGRATION_TESTING\n\n def test_something(self):\n portal = self.layer['portal']\n login(portal, TEST_USER_NAME)\n\n ...\n\n``logout()``\n Simulate logging out, i.e. becoming the anonymous user. This is equivalent\n to the ``z2.logout()`` helper in `plone.testing`_.\n\n For example::\n\n import unittest2 as unittest\n\n from plone.app.testing import PLONE_INTEGRATION_TESTING\n from plone.app.testing import logout\n\n ...\n\n class MyTest(unittest.TestCase):\n\n layer = PLONE_INTEGRATION_TESTING\n\n def test_something(self):\n portal = self.layer['portal']\n logout()\n\n ...\n\n``setRoles(portal, userId, roles)``\n Set the roles for the given user. ``roles`` is a list of roles.\n\n For example::\n\n import unittest2 as unittest\n\n from plone.app.testing import PLONE_INTEGRATION_TESTING\n from plone.app.testing import TEST_USER_ID\n from plone.app.testing import setRoles\n\n ...\n\n class MyTest(unittest.TestCase):\n\n layer = PLONE_INTEGRATION_TESTING\n\n def test_something(self):\n portal = self.layer['portal']\n setRoles(portal, TEST_USER_ID, ['Manager'])\n\nProduct and profile installation\n--------------------------------\n\n``applyProfile(portal, profileName, blacklisted_steps=None)``\n Install a GenericSetup profile (usually an extension profile) by name,\n using the ``portal_setup`` tool. The name is normally made up of a package\n name and a profile name. Do not use the ``profile-`` prefix.\n\n For example::\n\n from plone.testing import Layer\n\n from plone.app.testing import ploneSite\n from plone.app.testing import applyProfile\n\n ...\n\n class MyLayer(Layer):\n\n ...\n\n def setUp(self):\n\n ...\n\n with ploneSite() as portal:\n applyProfile(portal, 'my.product:default')\n\n ...\n\n``quickInstallProduct(portal, productName, reinstall=False)``\n Use this function to install a particular product into the given Plone site,\n using the add-ons control panel code (portal setup).\n If ``reinstall`` is ``False`` and the product is already installed, nothing will happen.\n If ``reinstall`` is true, perform an uninstall and install if the product is installed already.\n The ``productName`` should be a full dotted name, e.g. ``Products.MyProduct``,\n or ``my.product``.\n\n For example::\n\n from plone.testing import Layer\n\n from plone.app.testing import ploneSite\n from plone.app.testing import quickInstallProduct\n\n ...\n\n class MyLayer(Layer):\n\n ...\n\n def setUp(self):\n\n ...\n\n with ploneSite() as portal:\n quickInstallProduct(portal, 'my.product')\n\n ...\n\nComponent architecture sandboxing\n---------------------------------\n\n``pushGlobalRegistry(portal, new=None, name=None)``\n Create or obtain a stack of global component registries, and push a new\n registry to the top of the stack. This allows Zope Component Architecture\n registrations (e.g. loaded via ZCML) to be effectively torn down.\n\n If you are going to use this function, please read the corresponding\n documentation for ``zca.pushGlobalRegistry()`` in `plone.testing`_. In\n particular, note that you *must* reciprocally call ``popGlobalRegistry()``\n (see below).\n\n This helper is based on ``zca.pushGlobalRegistry()``, but will also fix\n up the local component registry in the Plone site ``portal`` so that it\n has the correct bases.\n\n For example::\n\n from plone.testing import Layer\n\n from plone.app.testing import ploneSite\n from plone.app.testing import pushGlobalRegistry\n from plone.app.testing import popGlobalRegistry\n\n ...\n\n class MyLayer(Layer):\n\n ...\n\n def setUp(self):\n\n ...\n\n with ploneSite() as portal:\n pushGlobalRegistry(portal)\n\n ...\n\n``popGlobalRegistry(portal)``\n Tear down the top of the component architecture stack, as created with\n ``pushGlobalRegistry()``\n\n For example::\n\n ...\n\n def tearDown(self):\n\n with ploneSite() as portal:\n popGlobalRegistry(portal)\n\nGlobal state cleanup\n--------------------\n\n``tearDownMultiPluginRegistration(pluginName)``\n PluggableAuthService \"MultiPlugins\" are kept in a global registry. If\n you have registered a plugin, e.g. using the ``registerMultiPlugin()``\n API, you should tear that registration down in your layer's ``tearDown()``\n method. You can use this helper, passing a plugin name.\n\n For example::\n\n from plone.testing import Layer\n\n from plone.app.testing import ploneSite\n from plone.app.testing import tearDownMultiPluginRegistration\n\n ...\n\n class MyLayer(Layer):\n\n ...\n\n def tearDown(self):\n\n tearDownMultiPluginRegistration('MyPlugin')\n\n ...\n\nLayer base class\n================\n\nIf you are writing a custom layer to test your own Plone add-on product, you\nwill often want to do the following on setup:\n\n1. Stack a new ``DemoStorage`` on top of the one from the base layer. This\n ensures that any persistent changes performed during layer setup can be\n torn down completely, simply by popping the demo storage.\n\n2. Stack a new ZCML configuration context. This keeps separate the information\n about which ZCML files were loaded, in case other, independent layers want\n to load those same files after this layer has been torn down.\n\n3. Push a new global component registry. This allows you to register\n components (e.g. by loading ZCML or using the test API from\n ``zope.component``) and tear down those registration easily by popping the\n component registry.\n\n4. Load your product's ZCML configuration\n\n5. Install the product into the test fixture Plone site\n\nOf course, you may wish to make other changes too, such as creating some base\ncontent or changing some settings.\n\nOn tear-down, you will then want to:\n\n1. Remove any Pluggable Authentication Service \"multi-plugins\" that were added\n to the global registry during setup.\n\n2. Pop the global component registry to unregister components loaded via ZCML.\n\n3. Pop the configuration context resource to restore its state.\n\n4. Pop the ``DemoStorage`` to undo any persistent changes.\n\nIf you have made other changes on setup that are not covered by this broad\ntear-down, you'll also want to tear those down explicitly here.\n\nStacking a demo storage and component registry is the safest way to avoid\nfixtures bleeding between tests. However, it can be tricky to ensure that\neverything happens in the right order.\n\nTo make things easier, you can use the ``PloneSandboxLayer`` layer base class.\nThis extends ``plone.testing.Layer`` and implements ``setUp()`` and\n``tearDown()`` for you. You simply have to override one or more of the\nfollowing methods:\n\n``setUpZope(self, app, configurationContext)``\n This is called during setup. ``app`` is the Zope application root.\n ``configurationContext`` is a newly stacked ZCML configuration context.\n Use this to load ZCML, install products using the helper\n ``plone.testing.z2.installProduct()``, or manipulate other global state.\n\n``setUpPloneSite(self, portal)``\n This is called during setup. ``portal`` is the Plone site root as\n configured by the ``ploneSite()`` context manager. Use this to make\n persistent changes inside the Plone site, such as installing products\n using the ``applyProfile()`` or ``quickInstallProduct()`` helpers, or\n setting up default content.\n\n``tearDownZope(self, app)``\n This is called during tear-down, before the global component registry and\n stacked ``DemoStorage`` are popped. Use this to tear down any additional\n global state.\n\n **Note:** Global component registrations PAS multi-plugin registrations are\n automatically torn down. Product installations are not, so you should use\n the ``uninstallProduct()`` helper if any products were installed during\n ``setUpZope()``.\n\n``tearDownPloneSite(self, portal)``\n This is called during tear-down, before the global component registry and\n stacked ``DemoStorage`` are popped. During this method, the local\n component site hook is set, giving you access to local components.\n\n **Note:** Persistent changes to the ZODB are automatically torn down by\n virtue of a stacked ``DemoStorage``. Thus, this method is less commonly\n used than the others described here.\n\nLet's show a more comprehensive example of what such a layer may look like.\nImagine we have a product ``my.product``. It has a ``configure.zcml`` file\nthat loads some components and registers a ``GenericSetup`` profile, making it\ninstallable in the Plone site. On layer setup, we want to load the product's\nconfiguration and install it into the Plone site.\n\nThe layer would conventionally live in a module ``testing.py`` at the root of\nthe package, i.e. ``my.product.testing``::\n\n from plone.app.testing import PloneSandboxLayer\n from plone.app.testing import PLONE_FIXTURE\n from plone.app.testing import IntegrationTesting\n\n from plone.testing import z2\n\n class MyProduct(PloneSandboxLayer):\n\n defaultBases = (PLONE_FIXTURE,)\n\n def setUpZope(self, app, configurationContext):\n # Load ZCML\n import my.product\n self.loadZCML(package=my.product)\n\n # Install product and call its initialize() function\n z2.installProduct(app, 'my.product')\n\n # Note: you can skip this if my.product is not a Zope 2-style\n # product, i.e. it is not in the Products.* namespace and it\n # does not have a <five:registerPackage /> directive in its\n # configure.zcml.\n\n def setUpPloneSite(self, portal):\n # Install into Plone site using portal_setup\n self.applyProfile(portal, 'my.product:default')\n\n def tearDownZope(self, app):\n # Uninstall product\n z2.uninstallProduct(app, 'my.product')\n\n # Note: Again, you can skip this if my.product is not a Zope 2-\n # style product\n\n MY_PRODUCT_FIXTURE = MyProduct()\n MY_PRODUCT_INTEGRATION_TESTING = IntegrationTesting(bases=(MY_PRODUCT_FIXTURE,), name=\"MyProduct:Integration\")\n\nHere, ``MY_PRODUCT_FIXTURE`` is the \"fixture\" base layer. Other layers can\nuse this as a base if they want to build on this fixture, but it would not\nbe used in tests directly. For that, we have created an ``IntegrationTesting``\ninstance, ``MY_PRODUCT_INTEGRATION_TESTING``.\n\nOf course, we could have created a ``FunctionalTesting`` instance as\nwell, e.g.::\n\n MY_PRODUCT_FUNCTIONAL_TESTING = FunctionalTesting(bases=(MY_PRODUCT_FIXTURE,), name=\"MyProduct:Functional\")\n\nOf course, we could do a lot more in the layer setup. For example, let's say\nthe product had a content type 'my.product.page' and we wanted to create some\ntest content. We could do that with::\n\n from plone.app.testing import TEST_USER_ID\n from plone.app.testing import TEST_USER_NAME\n from plone.app.testing import login\n from plone.app.testing import setRoles\n\n ...\n\n def setUpPloneSite(self, portal):\n\n ...\n\n setRoles(portal, TEST_USER_ID, ['Manager'])\n login(portal, TEST_USER_NAME)\n portal.invokeFactory('my.product.page', 'page-1', title=u\"Page 1\")\n setRoles(portal, TEST_USER_ID, ['Member'])\n\n ...\n\nNote that unlike in a test, there is no user logged in at layer setup time,\nso we have to explicitly log in as the test user. Here, we also grant the test\nuser the ``Manager`` role temporarily, to allow object construction (which\nperforms an explicit permission check).\n\n **Note:** Automatic tear down suffices for all the test setup above. If\n the only changes made during layer setup are to persistent, in-ZODB data,\n or the global component registry then no additional tear-down is required.\n For any other global state being managed, you should write a\n ``tearDownPloneSite()`` method to perform the necessary cleanup.\n\nGiven this layer, we could write a test (e.g. in ``tests.py``) like::\n\n import unittest2 as unittest\n from my.product.testing import MY_PRODUCT_INTEGRATION_TESTING\n\n class IntegrationTest(unittest.TestCase):\n\n layer = MY_PRODUCT_INTEGRATION_TESTING\n\n def test_page_dublin_core_title(self):\n portal = self.layer['portal']\n\n page1 = portal['page-1']\n page1.title = u\"Some title\"\n\n self.assertEqual(page1.Title(), u\"Some title\")\n\nPlease see `plone.testing`_ for more information about how to write and run\ntests and assertions.\n\nCommon test patterns\n====================\n\n`plone.testing`_'s documentation contains details about the fundamental\ntechniques for writing tests of various kinds. In a Plone context, however,\nsome patterns tend to crop up time and again. Below, we will attempt to\ncatalogue some of the more commonly used patterns via short code samples.\n\nThe examples in this section are all intended to be used in tests. Some may\nalso be useful in layer set-up/tear-down. We have used ``unittest`` syntax\nhere, although most of these examples could equally be adopted to doctests.\n\nWe will assume that you are using a layer that has ``PLONE_FIXTURE`` as a base\n(whether directly or indirectly) and uses the ``IntegrationTesting`` or\n``FunctionalTesting`` classes as shown above.\n\nWe will also assume that the variables ``app``, ``portal`` and ``request`` are\ndefined from the relative layer resources, e.g. with::\n\n app = self.layer['app']\n portal = self.layer['portal']\n request = self.layer['request']\n\nNote that in a doctest set up using the ``layered()`` function from\n``plone.testing``, ``layer`` is in the global namespace, so you would do e.g.\n``portal = layer['portal']``.\n\nWhere imports are required, they are shown alongside the code example. If\na given import or variable is used more than once in the same section, it\nwill only be shown once.\n\nBasic content management\n------------------------\n\nTo create a content item of type 'Folder' with the id 'f1' in the root of\nthe portal::\n\n portal.invokeFactory('Folder', 'f1', title=u\"Folder 1\")\n\nThe ``title`` argument is optional. Other basic properties, like\n``description``, can be set as well.\n\nNote that this may fail with an ``Unauthorized`` exception, since the test\nuser won't normally have permissions to add content in the portal root, and\nthe ``invokeFactory()`` method performs an explicit security check. You can\nset the roles of the test user to ensure that he has the necessary\npermissions::\n\n from plone.app.testing import setRoles\n from plone.app.testing import TEST_USER_ID\n\n setRoles(portal, TEST_USER_ID, ['Manager'])\n portal.invokeFactory('Folder', 'f1', title=u\"Folder 1\")\n\nTo obtain this object, acquisition-wrapped in its parent::\n\n f1 = portal['f1']\n\nTo make an assertion against an attribute or method of this object::\n\n self.assertEqual(f1.Title(), u\"Folder 1\")\n\nTo modify the object::\n\n f1.setTitle(u\"Some title\")\n\nTo add another item inside the folder f1::\n\n f1.invokeFactory('Document', 'd1', title=u\"Document 1\")\n d1 = f1['d1']\n\nTo check if an object is in a container::\n\n self.assertTrue('f1' in portal)\n\nTo delete an object from a container:\n\n del portal['f1']\n\nThere is no content or workflows installed by default. You can enable workflows::\n\n portal.portal_workflow.setDefaultChain(\"simple_publication_workflow\")\n\nSearching\n---------\n\nTo obtain the ``portal_catalog`` tool::\n\n from Products.CMFCore.utils import getToolByName\n\n catalog = getToolByName(portal, 'portal_catalog')\n\nTo search the catalog::\n\n results = catalog(portal_type=\"Document\")\n\nKeyword arguments are search parameters. The result is a lazy list. You can\ncall ``len()`` on it to get the number of search results, or iterate through\nit. The items in the list are catalog brains. They have attributes that\ncorrespond to the \"metadata\" columns configured for the catalog, e.g.\n``Title``, ``Description``, etc. Note that these are simple attributes (not\nmethods), and contain the value of the corresponding attribute or method from\nthe source object at the time the object was cataloged (i.e. they are not\nnecessarily up to date).\n\nTo make assertions against the search results::\n\n self.assertEqual(len(results), 1)\n\n # Copy the list into memory so that we can use [] notation\n results = list(results)\n\n # Check the first (and in this case only) result in the list\n self.assertEqual(results[0].Title, u\"Document 1\")\n\nTo get the path of a given item in the search results::\n\n self.assertEqual(results[0].getPath(), portal.absolute_url_path() + '/f1/d1')\n\nTo get an absolute URL::\n\n self.assertEqual(results[0].getURL(), portal.absolute_url() + '/f1/d1')\n\nTo get the original object::\n\n obj = results[0].getObject()\n\nTo re-index an object d1 so that its catalog information is up to date::\n\n d1.reindexObject()\n\nUser management\n---------------\n\nTo create a new user::\n\n from Products.CMFCore.utils import getToolByName\n\n acl_users = getToolByName(portal, 'acl_users')\n\n acl_users.userFolderAddUser('user1', 'secret', ['Member'], [])\n\nThe arguments are the username (which will also be the user id), the password,\na list of roles, and a list of domains (rarely used).\n\nTo make a particular user active (\"logged in\") in the integration testing\nenvironment use the ``login`` method and pass it the username::\n\n from plone.app.testing import login\n\n login(portal, 'user1')\n\nTo log out (become anonymous)::\n\n from plone.app.testing import logout\n\n logout()\n\nTo obtain the current user::\n\n from AccessControl import getSecurityManager\n\n user = getSecurityManager().getUser()\n\nTo obtain a user by name::\n\n user = acl_users.getUser('user1')\n\nOr by user id (id and username are often the same, but can differ in real-world\nscenarios)::\n\n user = acl_users.getUserById('user1')\n\nTo get the user's user name::\n\n userName = user.getUserName()\n\nTo get the user's id::\n\n userId = user.getId()\n\nPermissions and roles\n---------------------\n\nTo get a user's roles in a particular context (taking local roles into\naccount)::\n\n from AccessControl import getSecurityManager\n\n user = getSecurityManager().getUser()\n\n self.assertEqual(user.getRolesInContext(portal), ['Member'])\n\nTo change the test user's roles::\n\n from plone.app.testing import setRoles\n from plone.app.testing import TEST_USER_ID\n\n setRoles(portal, TEST_USER_ID, ['Member', 'Manager'])\n\nPass a different user name to change the roles of another user.\n\nTo grant local roles to a user in the folder f1::\n\n f1.manage_setLocalRoles(TEST_USER_ID, ('Reviewer',))\n\nTo check the local roles of a given user in the folder 'f1'::\n\n self.assertEqual(f1.get_local_roles_for_userid(TEST_USER_ID), ('Reviewer',))\n\nTo grant the 'View' permission to the roles 'Member' and 'Manager' in the\nportal root without acquiring additional roles from its parents::\n\n portal.manage_permission('View', ['Member', 'Manager'], acquire=False)\n\nThis method can also be invoked on a folder or individual content item.\n\nTo assert which roles have the permission 'View' in the context of the\nportal::\n\n roles = [r['name'] for r in portal.rolesOfPermission('View') if r['selected']]\n self.assertEqual(roles, ['Member', 'Manager'])\n\nTo assert which permissions have been granted to the 'Reviewer' role in the\ncontext of the portal::\n\n permissions = [p['name'] for p in portal.permissionsOfRole('Reviewer') if p['selected']]\n self.assertTrue('Review portal content' in permissions)\n\nTo add a new role::\n\n portal._addRole('Tester')\n\nThis can now be assigned to users globally (using the ``setRoles`` helper)\nor locally (using ``manage_setLocalRoles()``).\n\nTo assert which roles are available in a given context::\n\n self.assertTrue('Tester' in portal.valid_roles())\n\nWorkflow\n--------\n\nTo set the default workflow chain::\n\n from Products.CMFCore.utils import getToolByName\n\n workflowTool = getToolByName(portal, 'portal_workflow')\n\n workflowTool.setDefaultChain('my_workflow')\n\nIn Plone, most chains contain only one workflow, but the ``portal_workflow``\ntool supports longer chains, where an item is subject to more than one\nworkflow simultaneously.\n\nTo set a multi-workflow chain, separate workflow names by commas.\n\nTo get the default workflow chain::\n\n self.assertEqual(workflowTool.getDefaultChain(), ('my_workflow',))\n\nTo set the workflow chain for the 'Document' type::\n\n workflowTool.setChainForPortalTypes(('Document',), 'my_workflow')\n\nYou can pass multiple type names to set multiple chains at once. To set a\nmulti-workflow chain, separate workflow names by commas. To indicate that a\ntype should use the default workflow, use the special chain name '(Default)'.\n\nTo get the workflow chain for the portal type 'Document'::\n\n chains = dict(workflowTool.listChainOverrides())\n defaultChain = workflowTool.getDefaultChain()\n documentChain = chains.get('Document', defaultChain)\n\n self.assertEqual(documentChain, ('my_other_workflow',))\n\nTo get the current workflow chain for the content object f1::\n\n self.assertEqual(workflowTool.getChainFor(f1), ('my_workflow',))\n\nTo update all permissions after changing the workflow::\n\n workflowTool.updateRoleMappings()\n\nTo change the workflow state of the content object f1 by invoking the\ntransaction 'publish'::\n\n workflowTool.doActionFor(f1, 'publish')\n\nNote that this performs an explicit permission check, so if the current user\ndoesn't have permission to perform this workflow action, you may get an error\nindicating the action is not available. If so, use ``login()`` or\n``setRoles()`` to ensure the current user is able to change the workflow\nstate.\n\nTo check the current workflow state of the content object f1::\n\n self.assertEqual(workflowTool.getInfoFor(f1, 'review_state'), 'published')\n\nProperties\n----------\n\nTo set the value of a property on the portal root::\n\n portal._setPropValue('title', u\"My title\")\n\nTo assert the value of a property on the portal root::\n\n self.assertEqual(portal.getProperty('title'), u\"My title\")\n\nTo change the value of a property in a property sheet in the\n``portal_properties`` tool::\n\n from Products.CMFCore.utils import getToolByName\n\n propertiesTool = getToolByName(portal, 'portal_properties')\n siteProperties = propertiesTool['site_properties']\n\n siteProperties._setPropValue('many_users', True)\n\nTo assert the value of a property in a property sheet in the\n``portal_properties`` tool::\n\n self.assertEqual(siteProperties.getProperty('many_users'), True)\n\nInstalling products and extension profiles\n------------------------------------------\n\nTo apply a particular extension profile::\n\n from plone.app.testing import applyProfile\n\n applyProfile(portal, 'my.product:default')\n\nThis is the preferred method of installing a product's configuration.\n\nTo install an add-on product into the Plone site using the\nadd-ons control panel::\n\n from plone.app.testing import quickInstallProduct\n\n quickInstallProduct(portal, 'my.product')\n\nTo uninstall and install a product using the add-ons control panel::\n\n quickInstallProduct(portal, 'my.product', reinstall=True)\n\nNote that both of these assume the product's ZCML has been loaded, which is\nusually done during layer setup. See the layer examples above for more details\non how to do that.\n\nWhen writing a product that has an installation extension profile, it is often\ndesirable to write tests that inspect the state of the site after the profile\nhas been applied. Some of the more common such tests are shown below.\n\nTo verify that a product has been installed (e.g. as a dependency via\n``metadata.xml``)::\n\n from Products.CMFPlone.utils import get_installer\n\n qi = get_installer(portal)\n self.assertTrue(qi.is_product_installed('my.product'))\n\nTo verify that a particular content type has been installed (e.g. via\n``types.xml``)::\n\n typesTool = getToolByName(portal, 'portal_types')\n\n self.assertNotEqual(typesTool.getTypeInfo('mytype'), None)\n\nTo verify that a new catalog index has been installed (e.g. via\n``catalog.xml``)::\n\n catalog = getToolByName(portal, 'portal_catalog')\n\n self.assertTrue('myindex' in catalog.indexes())\n\nTo verify that a new catalog metadata column has been added (e.g. via\n``catalog.xml``)::\n\n self.assertTrue('myattr' in catalog.schema())\n\nTo verify that a new workflow has been installed (e.g. via\n``workflows.xml``)::\n\n workflowTool = getToolByName(portal, 'portal_workflow')\n\n self.assertNotEqual(workflowTool.getWorkflowById('my_workflow'), None)\n\nTo verify that a new workflow has been assigned to a type (e.g. via\n``workflows.xml``)::\n\n self.assertEqual(dict(workflowTool.listChainOverrides())['mytype'], ('my_workflow',))\n\nTo verify that a new workflow has been set as the default (e.g. via\n``workflows.xml``)::\n\n self.assertEqual(workflowTool.getDefaultChain(), ('my_workflow',))\n\nTo test the value of a property in the ``portal_properties`` tool (e.g. set\nvia ``propertiestool.xml``):::\n\n propertiesTool = getToolByName(portal, 'portal_properties')\n siteProperties = propertiesTool['site_properties']\n\n self.assertEqual(siteProperties.getProperty('some_property'), \"some value\")\n\nTo verify that a stylesheet has been installed in the ``portal_css`` tool\n(e.g. via ``cssregistry.xml``)::\n\n cssRegistry = getToolByName(portal, 'portal_css')\n\n self.assertTrue('mystyles.css' in cssRegistry.getResourceIds())\n\nTo verify that a JavaScript resource has been installed in the\n``portal_javascripts`` tool (e.g. via ``jsregistry.xml``)::\n\n jsRegistry = getToolByName(portal, 'portal_javascripts')\n\n self.assertTrue('myscript.js' in jsRegistry.getResourceIds())\n\nTo verify that a new role has been added (e.g. via ``rolemap.xml``)::\n\n self.assertTrue('NewRole' in portal.valid_roles())\n\nTo verify that a permission has been granted to a given set of roles (e.g. via\n``rolemap.xml``)::\n\n roles = [r['name'] for r in portal.rolesOfPermission('My Permission') if r['selected']]\n self.assertEqual(roles, ['Member', 'Manager'])\n\nTraversal\n---------\n\nTo traverse to a view, page template or other resource, use\n``restrictedTraverse()`` with a relative path::\n\n resource = portal.restrictedTraverse('f1/@@folder_contents')\n\nThe return value is a view object, page template object, or other resource.\nIt may be invoked to obtain an actual response (see below).\n\n``restrictedTraverse()`` performs an explicit security check, and so may\nraise ``Unauthorized`` if the current test user does not have permission to\nview the given resource. If you don't want that, you can use::\n\n resource = portal.unrestrictedTraverse('f1/@@folder_contents')\n\nYou can call this on a folder or other content item as well, to traverse from\nthat starting point, e.g. this is equivalent to the first example above::\n\n f1 = portal['f1']\n resource = f1.restrictedTraverse('@@folder_contents')\n\nNote that this traversal will not take ``IPublishTraverse`` adapters into\naccount, and you cannot pass query string parameters. In fact,\n``restrictedTraverse()`` and ``unrestrictedTraverse()`` implement the type of\ntraversal that happens with path expressions in TAL, which is similar, but not\nidentical to URL traversal.\n\nTo look up a view manually::\n\n from zope.component import getMultiAdapter\n\n view = getMultiAdapter((f1, request), name=u\"folder_contents\")\n\nNote that the name here should not include the ``@@`` prefix.\n\nTo simulate an ``IPublishTraverse`` adapter call, presuming the view\nimplements ``IPublishTraverse``::\n\n next = view.IPublishTraverse(request, u\"some-name\")\n\nOr, if the ``IPublishTraverse`` adapter is separate from the view::\n\n from zope.publisher.interfaces import IPublishTraverse\n\n publishTraverse = getMultiAdapter((f1, request), IPublishTraverse)\n next = view.IPublishTraverse(request, u\"some-name\")\n\nTo simulate a form submission or query string parameters::\n\n request.form.update({\n 'name': \"John Smith\",\n 'age': 23\n })\n\nThe ``form`` dictionary contains the marshalled request. That is, if you are\nsimulating a query string parameter or posted form variable that uses a\nmarshaller like ``:int`` (e.g. ``age:int`` in the example above), the value\nin the ``form`` dictionary should be marshalled (an int instead of a string,\nin the example above), and the name should be the base name (``age`` instead\nof ``age:int``).\n\nTo invoke a view and obtain the response body as a string::\n\n view = f1.restrictedTraverse('@@folder_contents')\n body = view()\n\n self.assertFalse(u\"An unexpected error occurred\" in body)\n\nPlease note that this approach is not perfect. In particular, the request\nis will not have the right URL or path information. If your view depends on\nthis, you can fake it by setting the relevant keys in the request, e.g.::\n\n request.set('URL', f1.absolute_url() + '/@@folder_contents')\n request.set('ACTUAL_URL', f1.absolute_url() + '/@@folder_contents')\n\nTo inspect the state of the request (e.g. after a view has been invoked)::\n\n self.assertEqual(request.get('disable_border'), True)\n\nTo inspect response headers (e.g. after a view has been invoked)::\n\n response = request.response\n\n self.assertEqual(response.getHeader('content-type'), 'text/plain')\n\nSimulating browser interaction\n------------------------------\n\nEnd-to-end functional tests can use `zope.testbrowser`_ to simulate user\ninteraction. This acts as a web browser, connecting to Zope via a special\nchannel, making requests and obtaining responses.\n\n **Note:** zope.testbrowser runs entirely in Python, and does not simulate\n a JavaScript engine.\n\nNote that to use ``zope.testbrowser``, you need to use one of the functional\ntesting layers, e.g. ``PLONE_FUNCTIONAL_TESTING``, or another layer\ninstantiated with the ``FunctionalTesting`` class.\n\nIf you want to create some initial content, you can do so either in a layer,\nor in the test itself, before invoking the test browser client. In the latter\ncase, you need to commit the transaction before it becomes available, e.g.::\n\n from plone.app.testing import setRoles\n from plone.app.testing import TEST_USER_ID\n\n # Make some changes\n setRoles(portal, TEST_USER_ID, ['Manager'])\n portal.invokeFactory('Folder', 'f1', title=u\"Folder 1\")\n setRoles(portal, TEST_USER_ID, ['Member'])\n\n # Commit so that the test browser sees these changes\n import transaction\n transaction.commit()\n\nTo obtain a new test browser client::\n\n from plone.testing.z2 import Browser\n\n # This is usually self.app (Zope root) or site.portal (test Plone site root)\n browser = Browser(app)\n\nTo open a given URL::\n\n portalURL = portal.absolute_url()\n browser.open(portalURL)\n\nTo inspect the response::\n\n self.assertTrue(u\"Welcome\" in browser.contents)\n\nTo inspect response headers::\n\n self.assertEqual(browser.headers['content-type'], 'text/html; charset=utf-8')\n\nTo follow a link::\n\n browser.getLink('Edit').click()\n\nThis gets a link by its text. To get a link by HTML id::\n\n browser.getLink(id='edit-link').click()\n\nTo verify the current URL::\n\n self.assertEqual(portalURL + '/edit', browser.url)\n\nTo set a form control value::\n\n browser.getControl('Age').value = u\"30\"\n\nThis gets the control by its associated label. To get a control by its form\nvariable name::\n\n browser.getControl(name='age:int').value = u\"30\"\n\nSee the `zope.testbrowser`_ documentation for more details on how to select\nand manipulate various types of controls.\n\nTo submit a form by clicking a button::\n\n browser.getControl('Save').click()\n\nAgain, this uses the label to find the control. To use the form variable\nname::\n\n browser.getControl(name='form.button.Save').click()\n\nTo simulate HTTP BASIC authentication and remain logged in for all\nrequests::\n\n from plone.app.testing import TEST_USER_NAME, TEST_USER_PASSWORD\n\n browser.addHeader('Authorization', 'Basic %s:%s' % (TEST_USER_NAME, TEST_USER_PASSWORD,))\n\nTo simulate logging in via the login form::\n\n browser.open(portalURL + '/login_form')\n browser.getControl(name='__ac_name').value = TEST_USER_NAME\n browser.getControl(name='__ac_password').value = TEST_USER_PASSWORD\n browser.getControl(name='submit').click()\n\nTo simulate logging out::\n\n browser.open(portalURL + '/logout')\n\nDebugging tips\n~~~~~~~~~~~~~~\n\nBy default, only HTTP error codes (e.g. 500 Server Side Error) are shown when\nan error occurs on the server. To see more details, set ``handleErrors`` to\nFalse::\n\n browser.handleErrors = False\n\nTo inspect the error log and obtain a full traceback of the latest entry::\n\n from Products.CMFCore.utils import getToolByName\n\n errorLog = getToolByName(portal, 'error_log')\n print errorLog.getLogEntries()[-1]['tb_text']\n\nTo save the current response to an HTML file::\n\n open('/tmp/testbrowser.html', 'w').write(browser.contents)\n\nYou can now open this file and use tools like Firebug to inspect the structure\nof the page. You should remove the file afterwards.\n\nComparison with ZopeTestCase/PloneTestCase\n==========================================\n\n`plone.testing`_ and ``plone.app.testing`` have in part evolved from\n``ZopeTestCase``, which ships with Zope 2 in the ``Testing`` package, and\n`Products.PloneTestCase`_, which ships with Plone and is used by Plone itself\nas well as numerous add-on products.\n\nIf you are familiar with ``ZopeTestCase`` and ``PloneTestCase``, the concepts\nof these package should be familiar to you. However, there are some important\ndifferences to bear in mind.\n\n* ``plone.testing`` and ``plone.app.testing`` are unburdened by the legacy\n support that ``ZopeTestCase`` and ``PloneTestCase`` have to include. This\n makes them smaller and easier to understand and maintain.\n\n* Conversely, ``plone.testing`` only works with Python 2.6 and Zope 2.12 and\n later. ``plone.app.testing`` only works with Plone 4 and later. If you need\n to write tests that run against older versions of Plone, you'll need to use\n ``PloneTestCase``.\n\n* ``ZopeTestCase``/``PloneTestCase`` were written before layers were available\n as a setup mechanism. ``plone.testing`` is very layer-oriented.\n\n* ``PloneTestCase`` provides a base class, also called ``PloneTestCase``,\n which you must use, as it performs setup and tear-down. ``plone.testing``\n moves shared state to layers and layer resources, and does not impose any\n particular base class for tests. This does sometimes mean a little more\n typing (e.g. ``self.layer['portal']`` vs. ``self.portal``), but it makes\n it much easier to control and reuse test fixtures. It also makes your\n test code simpler and more explicit.\n\n* ``ZopeTestCase`` has an ``installProduct()`` function and a corresponding\n ``installPackage()`` function. `plone.testing`_ has only an\n ``installProduct()``, which can configure any kind of Zope 2 product (i.e.\n packages in the ``Products.*`` namespace, old-style products in a special\n ``Products`` folder, or packages in any namespace that have had their ZCML\n loaded and which include a ``<five:registerPackage />`` directive in their\n configuration). Note that you must pass a full dotted name to this function,\n even for \"old-style\" products in the ``Products.*`` namespace, e.g.\n ``Products.LinguaPlone`` instead of ``LinguaPlone``.\n\n* On setup, ``PloneTestCase`` will load Zope 2's default ``site.zcml``. This\n in turn will load all ZCML for all packages in the ``Products.*`` namespace.\n ``plone.testing`` does not do this (and you are strongly encouraged from\n doing it yourself), because it is easy to accidentally include packages in\n your fixture that you didn't intend to be there (and which can actually\n change the fixture substantially). You should load your package's ZCML\n explicitly. See the `plone.testing`_ documentation for details.\n\n* When using ``PloneTestCase``, any package that has been loaded onto\n ``sys.path`` and which defines the ``z3c.autoinclude.plugin:plone`` entry\n point will be loaded via `z3c.autoinclude`_'s plugin mechanism. This loading\n is explicitly disabled, for the same reasons that the ``Products.*`` auto-\n loading is. You should load your packages' configuration explicitly.\n\n* ``PloneTestCase`` sets up a basic fixture that has member folder enabled,\n and in which the test user's member folder is available as ``self.folder``.\n The ``plone_workflow`` workflow is also installed as the default.\n ``plone.app.testing`` takes a more minimalist approach. To create a test\n folder owned by the test user that is similar to ``self.folder`` in a\n ``PloneTestCase``, you can do::\n\n import unittest2 as unittest\n from plone.app.testing import TEST_USER_ID, setRoles\n from plone.app.testing import PLONE_INTEGRATION_TESTING\n\n class MyTest(unitest.TestCase):\n\n layer = PLONE_INTEGRATION_TESTING\n\n def setUp(self):\n self.portal = self.layer['portal']\n\n setRoles(self.portal, TEST_USER_ID, ['Manager'])\n self.portal.invokeFactory('Folder', 'test-folder')\n setRoles(self.portal, TEST_USER_ID, ['Member'])\n\n self.folder = self.portal['test-folder']\n\n You could of course do this type of setup in your own layer and expose it\n as a resource instead.\n\n* To use `zope.testbrowser`_ with ``PloneTestCase``, you should use its\n ``FunctionalTestCase`` as a base class, and then use the following pattern::\n\n from Products.Five.testbrowser import Browser\n browser = Browser()\n\n The equivalent pattern in ``plone.app.testing`` is to use the\n ``FunctionalTesting`` test lifecycle layer (see example above), and then\n use::\n\n from plone.testing.z2 import Browser\n browser = Browser(self.layer['app'])\n\n Also note that if you have made changes to the fixture prior to calling\n ``browser.open()``, they will *not* be visible until you perform an\n explicit commit. See the ``zope.testbrowser`` examples above for details.\n\n.. _plone.testing: http://pypi.python.org/pypi/plone.testing\n.. _zope.testing: http://pypi.python.org/pypi/zope.testing\n.. _z3c.autoinclude: http://pypi.python.org/pypi/z3c.autoinclude\n.. _zope.testbrowser: http://pypi.python.org/pypi/zope.testbrowser\n.. _Products.PloneTestCase: http://pypi.python.org/pypi/Products.PloneTestCase\n\nChangelog\n=========\n\n.. You should *NOT* be adding new change log entries to this file.\n You should create a file in the news directory instead.\n For helpful instructions, please see:\n https://github.com/plone/plone.releaser/blob/master/ADD-A-NEWS-ITEM.rst\n\n.. towncrier release notes start\n\n7.1.0 (2024-07-31)\n------------------\n\nNew features:\n\n\n- PloneFixture: explicitly install plone.app.contenttypes:default.\n The `addPloneSite` factory in Plone 6.1 no longer installs this by default.\n [maurits] (#3961)\n\n\nBug fixes:\n\n\n- Remove setuptools fossils.\n [maurits] (#72)\n\n\n7.0.2 (2024-01-19)\n------------------\n\nInternal:\n\n\n- Update configuration files.\n [plone devs] (cfffba8c)\n\n\n7.0.1 (2023-04-15)\n------------------\n\nInternal:\n\n\n- Update configuration files.\n [plone devs] (434550cc)\n\n\n7.0.0 (2022-12-02)\n------------------\n\nBug fixes:\n\n\n- Final release for Plone 6.0.0. (#600)\n\n\n7.0.0b2 (2022-10-11)\n--------------------\n\nBug fixes:\n\n\n- Restore the previously used admin password for tests (\"secret\") [davisagli] (#79)\n\n\n7.0.0b1 (2022-09-30)\n--------------------\n\nBug fixes:\n\n\n- Increase the test password length. [davisagli] (#78)\n\n\n7.0.0a3 (2022-04-28)\n--------------------\n\nNew features:\n\n\n- Load CMFFormController in Plone 5, be silent when this fails in Plone 6.\n This restores compatibility with Plone 5.2 for now.\n Note that this ``plone.app.testing`` version is only meant for Python 3.\n [maurits] (#3467)\n\n\n7.0.0a2 (2022-04-04)\n--------------------\n\nBug fixes:\n\n\n- Add savepoint after creating the portal in PloneFixture.\n Fixes `issue 3467 <https://github.com/plone/Products.CMFPlone/issues/3467>`_.\n [pbauer] (#76)\n\n\n7.0.0a1 (2022-03-23)\n--------------------\n\nBreaking changes:\n\n\n- Plone 6 only. (#75)\n\n\nNew features:\n\n\n- remove CMFFormController\n [petschki] (#75)\n\n\n6.1.9 (2021-09-01)\n------------------\n\nBug fixes:\n\n\n- Fixed test that failed for dexterity site root.\n [jaroel, ale-rt] (#60)\n\n\n6.1.8 (2020-11-11)\n------------------\n\nBug fixes:\n\n\n- Before trying to load the zcml of plone.app.folder, double check if it is a real package or an alias provided by plone.app.upgrade (#72)\n\n\n6.1.7 (2020-10-12)\n------------------\n\nNew features:\n\n\n- Removed backwards compatibility code for old quickinstaller.\n Current plone.app.testing is only for Plone 5.2+, so this code was no longer used.\n See also `PLIP 1775 <https://github.com/plone/Products.CMFPlone/issues/1775>`_.\n [maurits] (#1775)\n\n\n6.1.6 (2020-09-26)\n------------------\n\nBug fixes:\n\n\n- Fixed test failure on Python 3 with Products.MailHost 4.10.\n [maurits] (#3178)\n\n\n6.1.5 (2020-04-20)\n------------------\n\nBug fixes:\n\n\n- Minor packaging updates. (#1)\n\n\n6.1.4 (2020-03-09)\n------------------\n\nBug fixes:\n\n\n- Fix a test isolation issue that was preventing the MOCK_MAILHOST_FIXTURE to be used in multiple testcases [ale-rt] (#61)\n- MockMailHostLayer configures the mail sender setting the appropriate registry records (Fixes #62) (#62)\n- Fix tests when using zope.testrunner internals since its version 5.1.\n [jensens] (#68)\n- Do not load Products/ZCML of no longer existing Products.ResourceRegistries.\n [jensens] (#69)\n\n\n6.1.3 (2019-02-16)\n------------------\n\nBug fixes:\n\n\n- Make plone.app.folder a optional product for PloneFixture in py2 (#59)\n\n\n6.1.2 (2019-02-13)\n------------------\n\nBug fixes:\n\n\n- Fixed the travis build checking the Python versions Plone actually supports.\n Also fixed Python versions in setup.py (#57)\n\n\n6.1.1 (2018-11-05)\n------------------\n\nBug fixes:\n\n- Fix the package manifest that was not including some files\n [ale-rt]\n\n\n6.1.0 (2018-11-04)\n------------------\n\nBreaking changes:\n\n- Require `plone.testing >= 7.0`.\n\nNew features:\n\n- Add support for Python 3.5 and 3.6.\n [loechel, ale-rt, icemac, davisagli, pbauer]\n\n\n6.0.0 (2018-10-05)\n------------------\n\nNew features:\n\n- Install and load zcml of CMFQuickInstallerTool only when importable.\n [maurits]\n\n- Load negotiator from plone.i18n (PTS removed).\n [jensens, ksuess]\n\n- Add copy of bbb.PloneTestCase.\n For Plone 5.2 the bbb.PloneTestCase will uses Dexterity instead of Archetypes.\n Adding bbb_at.PloneTestCase for them to use allows to keep the AT tests working.\n See https://github.com/plone/plone.app.testing/pull/51\n [pbauer]\n\nBug fixes:\n\n- Amended the doctests to work with automatically layer port picking from plone.testing.\n [Rotonen]\n\n\n5.0.8 (2017-10-25)\n------------------\n\nBug fixes:\n\n- Load Products.PageTemplates ZCML. [tschorr]\n\n\n5.0.7 (2017-07-03)\n------------------\n\nBug fixes:\n\n- Remove deprecated __of__ calls on BrowserViews\n [MrTango]\n\n- Remove unittest2 dependency\n [kakshay21]\n\n\n5.0.6 (2016-12-19)\n------------------\n\nBug fixes:\n\n- No longer try to load `Products.SecureMailHost` and its zcml.\n This is not shipped with Plone 5.0 or higher. [maurits]\n\n\n5.0.5 (2016-11-19)\n------------------\n\nBug fixes:\n\n- Do not use install Products.PasswordResetTool in the PloneFixture if it isn't available.\n [thet]\n\n\n5.0.4 (2016-09-23)\n------------------\n\nNew features:\n\n- Use get_installer instead of portal_quickinstaller when available, for\n Plone 5.1 and higher. [maurits]\n\n- In PloneSandboxLayer make profile upgrade versions persistent. This\n way installed profile versions get reset in teardown. [maurits]\n\n\n5.0.3 (2016-09-07)\n------------------\n\nBug fixes:\n\n- Load Products.CMFFormController in tests. It is still used by core\n Plone, also without Archetypes. This makes the CMFFormController\n tests pass. [maurits]\n\n\n5.0.2 (2016-06-07)\n------------------\n\nFixes:\n\n- Do not use install Products.SecureMailHost in the PloneFixture if it isn't available\n [vangheem]\n\n\n5.0.1 (2016-02-26)\n------------------\n\nFixes:\n\n- Replace deprecated ``zope.site.hooks`` import with ``zope.component.hooks``.\n [thet]\n\n\n5.0.0 (2016-02-20)\n------------------\n\nNew:\n\n- Add a MOCK_MAILHOST_FIXTURE fixture that integration and functional tests layers can depend on.\n This allows to easily check how mails are sent from Plone.\n [gforcada]\n\nFixes:\n\n- Fix ``layers.rst`` doctest to be compatible with older and newer zope.testrunner layer ordering.\n [thet]\n\n- Depend on ``zope.testrunner`` and fix deprecated usage of ``zope.testing.testrunner``.\n [thet]\n\n- Cleanup code, flake8, sort imports, etc.\n [gforcada]\n\n- Fix RAM cache error with bbb.PloneTestCase.\n [ebrehault]\n\n\n5.0b6 (2015-08-22)\n------------------\n\n- No need for unittest2.\n [gforcada]\n\n\n5.0b5 (2015-07-18)\n------------------\n\n- Do not install CMFDefault.\n [tomgross]\n\n- Document PloneWithPackageLayer.\n [gotcha]\n\n\n5.0b4 (2015-05-04)\n------------------\n\n- Do not install CMFFormController.\n [timo]\n\n- Do not install CMFDefault\n [tomgross]\n\n5.0b3 (2015-03-26)\n------------------\n\n- Remove PloneLanguageTool from PloneFixture.\n [timo]\n\n\n5.0b2 (2015-03-13)\n------------------\n\n- remove test of applying an extension profile, we don't have a good one to\n test now.\n [davidagli]\n\n- fix test, plone.app.theming does not get recorded as installed .\n [davisagli]\n\n- fix: ``Products.CMFPlone`` needs the ``gopip`` index from\n ``plone.app.folder``. So latter has to be initialized before CMFPlones\n profile is applied (which installs the index to catalog). At the moment\n CMFPlone therefore registers the index itself, but plone.app.folder\n registers it too, which resulted in plone/Products.CMFPlone#313\n \"GopipIndex registered twice\" In tests the registration does not succeed,\n because plone.app.folder was never initialized as z2 products. In order to\n remove the misleading regisatration from CMFPlone we must take care that the\n index is available, which is achieved with this change. Also minor pep8\n optimizations in the file touched.\n [jensens]\n\n- create memberfolder, if it is not there for testing.\n [tomgross]\n\n\n5.0b1 (2014-10-23)\n------------------\n\n- Allow applyProfile to skip steps and all other options supported by\n runAllImportStepsFromProfile of portal_setup-tool.\n [pbauer, tomgross]\n\n\n5.0a2 (2014-04-19)\n------------------\n\n- Install Products.DateRecurringIndex for the PLONE_FIXTURE Layer.\n [thet]\n\n\n5.0a1 (2014-02-22)\n------------------\n\n- Add 'ROBOT_TEST_LEVEL' to interfaces, so other packages can import it. This\n makes things easier if we decide to change the value.\n [timo]\n\n- Replace deprecated test assert statements.\n [timo]\n\n- plonetheme.classic no longer ships with Plone, don't use it for\n testing.\n [esteele]\n\n- Clean up the zodbDB and configurationContext resources if there\n is an error during the PloneSandboxLayer setUp.\n [davisagli]\n\n- Make PLONE_FIXTURE not install a content type system.\n Packages that need content types to run their tests should\n pick the appropriate fixture from plone.app.contenttypes\n or Products.ATContentTypes.\n [davisagli]\n\n- Pin [robot] extra to ``robotsuite>=1.4.0``.\n [saily]\n\n- Fix wrong spelling of ``reinstallProducts`` method in quickInstallProduct.\n [saily]\n\n- Sync bbb PloneTestCase class with original one.\n [tomgross]\n\n\n4.2.2 (2013-02-09)\n------------------\n\n- Add [robot] extras for requiring dependnecies for Robot Framework\n tests with Selenium2Library\n [datakurre]\n\n- Install PythonScripts as zope product\n [mikejmets]\n\n\n4.2.1 (2012-12-15)\n------------------\n\n- Allow testing with non standard port. Allows running multiple test suites\n in parallel.\n [do3cc]\n\n- Documentation updates.\n [moo]\n\n\n4.2 (2012-04-15)\n----------------\n\n- Branch as 4.2 as the plone.app.collection addition breaks backwards\n compatibility.\n [esteele]\n\n- Fixed spurious failure in our own tests by using a longer timeout.\n [maurits]\n\n- plone.app.collection added to PloneFixture.\n [timo]\n\n\n4.0.2 (2011-08-31)\n------------------\n\n- Load ZCML before installing Zope products in ``PloneWithPackageLayer``;\n it enables package registration.\n [gotcha]\n\n\n4.0.1 (2011-07-14)\n------------------\n\n- Add ``additional_z2_products`` parameter to ``PloneWithPackageLayer``\n helper class to install additional Zope 2 products.\n [jfroche]\n\n\n4.0 - 2011-05-13\n------------------\n\n- 4.0 Final release.\n [esteele]\n\n- Add MANIFEST.in.\n [WouterVH]\n\n\n4.0a6 - 2011-04-06\n------------------\n\n- Added helper functions for selenium layer. (Copied from SeleniumTestCase\n within Products.CMFPlone/Products/CMFPlone/tests/selenium/base.py)\n [emanlove]\n\n- Rework layer setup of SeleniumLayer so that z2.ZSERVER_FIXTURE is a\n default_base.\n [esteele]\n\n- Convert the passed-in selenium webdriver name to lowercase before doing a\n module lookup.\n [esteele]\n\n- Moved selenium start up and tear down to testSetUp and testTearDown,\n respectively. This was done to help further isolate individual tests.\n For example, logging in under one test would require either logging out\n or shutting down the browser, which is what the selenium_layer will now\n do under testTearDown, in order to have a \"clean\" state within the next\n test.\n [emanlove]\n\n- Corrected module path for the various selenium webdrivers using\n selenium 2.0b2.\n [emanlove]\n\n\n4.0a5 - 2011-03-02\n------------------\n\n- Use the new ``plone.testing.security`` module to ensure isolation of\n security checkers when setting up and tearing down layers based on the\n ``PloneSandboxLayer`` helper base class. This would cause problems when\n running multiple test suites in the same test run, in particular if one of\n those suites were setting up ZCML that used ``five.grok``.\n [optilude]\n\n\n4.0a4 - 2011-01-11\n------------------\n\n- Automatically tear down PAS registrations via snapshotting when using\n ``PloneSandboxLayer``. It's too difficult to do this manually when you\n consider that plugins may be registered in ZCML via transitive dependencies.\n There should be no backwards compatibility concern - using\n ``tearDownMultiPlugin()`` is still supported, and it's generally safe to\n call it once.\n [optilude]\n\n- Try to make sure ``tearDownMultiPlugin()`` and the generic PAS plugin\n cleanup handler do not interfere with the cleanup handler from the PAS\n ZCML directive.\n [optilude]\n\n- Do not install ``Products.kupu`` or ``Products.CMFPlacefulWorkflow``.\n [elro]\n\n- Depend on ``Products.CMFPlone`` instead of ``Plone``.\n [elro]\n\n\n4.0a3 - 2010-12-14\n------------------\n\n- Allow top-level import of PloneTestLifecycle.\n [stefan]\n\n- Added a warning not to use 'default' Firefox profile for selenium tests.\n [zupo]\n\n- Fixed distribution dependency declarations.\n [hannosch]\n\n- Correct license to GPL version 2 only.\n [hannosch]\n\n- Make some module imports helper methods on the already policy-heavy\n helper class per optilude's suggestion.\n [rossp]\n\n- Add a layer and test case for running selenium tests.\n [rossp]\n\n- Give the default test user differing user id and login name. This helps reveal\n problems with userid vs login name errors, an overly common error.\n [wichert]\n\n\n1.0a2 - 2010-09-05\n------------------\n\n- Make sure plone.app.imaging is installed properly during layer setup.\n [optilude]\n\n\n1.0a1 - 2010-08-01\n------------------\n\n- Initial release\n",
"bugtrack_url": null,
"license": "GPL version 2",
"summary": "Testing tools for Plone-the-application, based on plone.testing.",
"version": "7.1.0",
"project_urls": {
"Homepage": "https://pypi.org/project/plone.app.testing"
},
"split_keywords": [
"plone",
"tests"
],
"urls": [
{
"comment_text": "",
"digests": {
"blake2b_256": "ebefec2fca922b00704419a6af32b0764add09bf2686f8dc24656939264a27a3",
"md5": "7c1e7234d9438e60b7f693d4de139ad1",
"sha256": "411dbd6ebf4b7db0fb0a1aa5e9d072f44d60f0620a5d9364e7513e68f66913e1"
},
"downloads": -1,
"filename": "plone.app.testing-7.1.0-py3-none-any.whl",
"has_sig": false,
"md5_digest": "7c1e7234d9438e60b7f693d4de139ad1",
"packagetype": "bdist_wheel",
"python_version": "py3",
"requires_python": ">=3.7",
"size": 49546,
"upload_time": "2024-07-31T10:28:22",
"upload_time_iso_8601": "2024-07-31T10:28:22.254483Z",
"url": "https://files.pythonhosted.org/packages/eb/ef/ec2fca922b00704419a6af32b0764add09bf2686f8dc24656939264a27a3/plone.app.testing-7.1.0-py3-none-any.whl",
"yanked": false,
"yanked_reason": null
},
{
"comment_text": "",
"digests": {
"blake2b_256": "6ca266273ba2a9a5f2a45f7efd8dad2c9460040e3d8fbc94c1f039209d8d2270",
"md5": "2216f3928ac8a1617d1ee5a274e1da2f",
"sha256": "c73f710a0823501bd39dc69615223a7bb593e40ba960ac41038d7e131d937788"
},
"downloads": -1,
"filename": "plone_app_testing-7.1.0.tar.gz",
"has_sig": false,
"md5_digest": "2216f3928ac8a1617d1ee5a274e1da2f",
"packagetype": "sdist",
"python_version": "source",
"requires_python": ">=3.7",
"size": 110905,
"upload_time": "2024-07-31T10:28:25",
"upload_time_iso_8601": "2024-07-31T10:28:25.110487Z",
"url": "https://files.pythonhosted.org/packages/6c/a2/66273ba2a9a5f2a45f7efd8dad2c9460040e3d8fbc94c1f039209d8d2270/plone_app_testing-7.1.0.tar.gz",
"yanked": false,
"yanked_reason": null
}
],
"upload_time": "2024-07-31 10:28:25",
"github": false,
"gitlab": false,
"bitbucket": false,
"codeberg": false,
"lcname": "plone.app.testing"
}