Fixtures#

Every fixture pytest-plone registers, grouped by what it gives you.

The integration and functional fixtures these depend on are generated from your testing layers by fixtures_factory. For what the two layers mean and when to choose which, see About testing layers, scopes, and isolation.

Overview#

Fixture

Scope

Requires

app

Function

integration

portal

Function

integration

app_class

Class

integration_class

portal_class

Class

integration_class

http_request

Function

integration

functional_app

Function

functional

functional_portal

Function

functional

functional_app_class

Class

functional_class

functional_portal_class

Class

functional_class

functional_http_request

Function

functional

request_factory

Function

functional_portal

manager_request

Function

request_factory

anon_request

Function

request_factory

installer

Function

portal

uninstalled

Function

installer, package_name

browser_layers

Function

portal

controlpanel_actions

Function

portal

setup_tool

Function

portal

profile_last_version

Function

setup_tool

apply_profiles

Session

get_fti

Function

portal

get_behaviors

Function

get_fti

create_content

Session

grant_roles

Session

get_vocabulary

Session

create_site

Session

distribution_name, site_owner_name

distribution_name

Session

answers

Session

site_logo

site_logo

Session

site_owner_name

Session

site_owner_password

Session

generate_mo

Session

Portal and app#

The Plone site and the Zope root, on either testing layer.

The functional_ variants are bound to the functional layer, which uses a real transaction and a running server rather than the integration layer’s stacked DemoStorage. Use them for REST API and browser tests.

The class-scoped variants share one portal across every test method in a class. They honor @pytest.mark.portal only when it is applied to the class; a class-scoped fixture cannot see method-level markers.

The app_class and functional_app_class fixtures return the Zope root at class scope. Each one drives the single per-class setup and teardown that its portal counterpart depends on, so a class can ask for both the app and the portal without setting the layer up twice.

pytest_plone.fixtures.base.app(integration: plone.testing.layer.Layer) OFS.Application.Application#

Returns the root of a Zope application for an integration Layer.

Example usage:

def test_app(app):
    assert app.title == "Zope"
pytest_plone.fixtures.base.portal(integration: plone.testing.layer.Layer, request: pytest.FixtureRequest) Products.CMFPlone.Portal.PloneSite#

Returns the default Plone Site for an integration Layer.

Supports @pytest.mark.portal to apply GenericSetup profiles, create content, and grant roles before the test runs.

Example usage:

def test_portal(portal):
    assert portal.title == "Plone site"

@pytest.mark.portal(
    profiles=["my.addon:testing"],
    content=[{"type": "Document", "id": "doc1", "title": "A document"}],
    roles=["Manager"],
)
def test_portal_with_marker(portal):
    assert "doc1" in portal
pytest_plone.fixtures.base.app_class(integration_class: plone.testing.layer.Layer) collections.abc.Generator[OFS.Application.Application, None, None]#

Returns the root of a Zope application for an integration Layer, class-scoped.

Class-scoped counterpart to :func:app. zope.pytestlayer only invokes testSetUp for function-scoped fixtures, so this fixture drives the per-class testSetUp/testTearDown lifecycle itself. :func:portal_class builds on it, so a test class can request both app_class and portal_class without setting the layer up twice.

Example usage:

class TestApp:
    def test_app(self, app_class):
        assert app_class.title == "Zope"
pytest_plone.fixtures.base.portal_class(app_class: OFS.Application.Application, integration_class: plone.testing.layer.Layer, request: pytest.FixtureRequest) Products.CMFPlone.Portal.PloneSite#

Returns the default Plone Site for an integration Layer, class-scoped.

Class-scoped counterpart to :func:portal. The same portal instance is shared across every test method in the class, so setup runs once per class instead of once per test. The per-class testSetUp/testTearDown lifecycle is driven by :func:app_class, on which this fixture depends.

Honors @pytest.mark.portal applied at the class level — method-level markers are not visible to a class-scoped fixture and are ignored.

Example usage:

@pytest.mark.portal(
    content=[{"type": "Document", "id": "doc1", "title": "Doc"}],
    roles=["Manager"],
)
class TestSomething:
    def test_one(self, portal_class):
        assert "doc1" in portal_class

    def test_two(self, portal_class):
        assert "doc1" in portal_class
pytest_plone.fixtures.base.functional_app(functional: plone.testing.layer.Layer) OFS.Application.Application#

Returns the root of a Zope application for a functional Layer.

Mirrors :func:app but bound to the functional layer. Use this in REST API, browser, or other tests that require transaction-level isolation instead of the integration-layer stacked-DemoStorage.

Example usage:

def test_functional_app(functional_app):
    assert functional_app.title == "Zope"
pytest_plone.fixtures.base.functional_portal(functional: plone.testing.layer.Layer, request: pytest.FixtureRequest) Products.CMFPlone.Portal.PloneSite#

Returns the default Plone Site for a functional Layer.

Mirrors :func:portal but bound to the functional layer and also honors @pytest.mark.portal for GenericSetup profiles, pre-created content, and test-user roles.

Example usage:

def test_functional_portal(functional_portal):
    assert functional_portal.title == "Plone site"
pytest_plone.fixtures.base.functional_app_class(functional_class: plone.testing.layer.Layer) collections.abc.Generator[OFS.Application.Application, None, None]#

Returns the root of a Zope application for a functional Layer, class-scoped.

Class-scoped counterpart to :func:functional_app. zope.pytestlayer only invokes testSetUp for function-scoped fixtures, so this fixture drives the per-class testSetUp/testTearDown lifecycle itself.

Func:

functional_portal_class builds on it, so a test class can request both without setting the layer up twice.

Example usage:

class TestFunctionalApp:
    def test_app(self, functional_app_class):
        assert functional_app_class.title == "Zope"
pytest_plone.fixtures.base.functional_portal_class(functional_app_class: OFS.Application.Application, functional_class: plone.testing.layer.Layer, request: pytest.FixtureRequest) Products.CMFPlone.Portal.PloneSite#

Returns the default Plone Site for a functional Layer, class-scoped.

Class-scoped counterpart to :func:functional_portal. The same portal instance is shared across every test method in the class — the typical pattern for REST API / service test suites that need a persistent portal. The per-class testSetUp/testTearDown lifecycle is driven by

Func:

functional_app_class, on which this fixture depends.

Honors @pytest.mark.portal applied at the class level — method-level markers are not visible to a class-scoped fixture and are ignored.

Example usage:

@pytest.mark.portal(roles=["Manager"])
class TestRESTService:
    def test_one(self, functional_portal_class):
        assert functional_portal_class.title == "Plone site"

    def test_two(self, functional_portal_class):
        assert "plone" in functional_portal_class.absolute_url()

Requests#

http_request and functional_http_request return the request object bound to the layer.

request_factory is different in kind: it builds authenticated HTTP sessions that talk to a running Plone over the network, which is what REST API tests need. manager_request and anon_request are shorthands for the two common identities.

pytest_plone.fixtures.base.http_request(integration: plone.testing.layer.Layer) ZPublisher.HTTPRequest.HTTPRequest#

Returns the current request object.

Example usage:

def test_http_request(http_request):
    assert http_request.method == "GET"
pytest_plone.fixtures.base.functional_http_request(functional: plone.testing.layer.Layer) ZPublisher.HTTPRequest.HTTPRequest#

Returns the current request object for a functional Layer.

Mirrors :func:http_request but bound to the functional layer.

Example usage:

def test_functional_http_request(functional_http_request):
    assert functional_http_request.method == "GET"
pytest_plone.fixtures.requests.request_factory(functional_portal: Products.CMFPlone.Portal.PloneSite, request: pytest.FixtureRequest) pytest_plone._types.RequestFactory#

Builder fixture for HTTP request sessions against the functional portal.

Returns a callable that produces a :class:RelativeSession bound to the portal URL. The session is closed automatically at the end of the test.

Parameters accepted by the returned callable:

  • role"Manager" or "Anonymous" (default). Maps to predefined test credentials. Unknown roles raise ValueError — use basic_auth for other identities.

  • basic_auth(username, password) tuple; takes precedence over role when provided.

  • api — when True (default), the base URL is suffixed with ++api++ so relative requests hit the REST API traverser.

Example usage:

def test_list_content(request_factory):
    session = request_factory(role="Manager")
    response = session.get("/")
    assert response.status_code == 200
pytest_plone.fixtures.requests.manager_request(request_factory: pytest_plone._types.RequestFactory) pytest_plone.fixtures.requests.RelativeSession#

A RelativeSession authenticated as the portal owner (Manager).

Example usage:

def test_admin_endpoint(manager_request):
    response = manager_request.get("/@controlpanels")
    assert response.status_code == 200
pytest_plone.fixtures.requests.anon_request(request_factory: pytest_plone._types.RequestFactory) pytest_plone.fixtures.requests.RelativeSession#

A RelativeSession with no authentication (Anonymous).

Example usage:

def test_public_endpoint(anon_request):
    response = anon_request.get("/")
    assert response.status_code == 200
class RelativeSession(base_url: str)#

Bases: requests.Session

requests.Session that resolves relative URLs against a base URL.

Minimal standalone equivalent of plone.restapi.testing.RelativeSession — avoids pulling the full plone.restapi[test] import chain into pytest-plone’s runtime.

Initialization

request(method: str, url: str, **kwargs)#

Add-ons#

Fixtures for the canonical add-on test suite: is the product installed, are its browser layers registered, is its control panel there, is its profile at the expected version.

pytest_plone.fixtures.addons.installer(portal: Products.CMFPlone.Portal.PloneSite) Products.CMFPlone.controlpanel.browser.quickinstaller.InstallerView#

Portal helper for managing add-ons using GenericSetup.

Example usage:

PACKAGE_NAME = "collective.person"

class TestSetupUninstall:
    @pytest.fixture(autouse=True)
    def uninstalled(self, installer):
        installer.uninstall_product(PACKAGE_NAME)
pytest_plone.fixtures.addons.uninstalled(installer: Products.CMFPlone.controlpanel.browser.quickinstaller.InstallerView, package_name: str) None#

Uninstall the add-on under test from the current portal.

Requires a package_name fixture defined in your conftest.py that returns the distribution name of the add-on being tested.

Example usage:

# conftest.py
import pytest


@pytest.fixture
def package_name() -> str:
    return "collective.person"


# tests/test_setup.py
class TestSetupUninstall:
    @pytest.fixture(autouse=True)
    def uninstalled(self, uninstalled):
        # add-on is now uninstalled for every test in this class
        pass

    def test_product_uninstalled(self, installer, package_name):
        assert installer.is_product_installed(package_name) is False
pytest_plone.fixtures.addons.browser_layers(portal: Products.CMFPlone.Portal.PloneSite) list[zope.interface.interface.InterfaceClass]#

List of browser layers registered in a portal.

Example usage:

def test_browserlayer(browser_layers):
    from collective.person.interfaces import IBrowserLayer

    assert IBrowserLayer in browser_layers
pytest_plone.fixtures.addons.controlpanel_actions(portal: Products.CMFPlone.Portal.PloneSite) list[str]#

List of identifiers (id) of control panel actions.

Example usage:

def test_controlpanel_installed(controlpanel_actions):
    assert "MyControlPanel" in controlpanel_actions
pytest_plone.fixtures.addons.setup_tool(portal: Products.CMFPlone.Portal.PloneSite) Products.GenericSetup.tool.SetupTool#

Return the portal_setup for the current portal.

Example usage:

def test_profile_version(setup_tool):
    name = "profile-collective.person:default"
    version = setup_tool.getLastVersionForProfile(name)
    assert version[0] == "1000"
pytest_plone.fixtures.addons.profile_last_version(setup_tool: Products.GenericSetup.tool.SetupTool) pytest_plone._types.ProfileVersionGetter#

Provides a method to return the last version for a profile.

Example usage:

PACKAGE_NAME = "collective.person"

def test_latest_version(profile_last_version):
    assert profile_last_version(f"{PACKAGE_NAME}:default") == "1000"
pytest_plone.fixtures.addons.apply_profiles() pytest_plone._types.ProfilesApplier#

Apply GenericSetup profiles to a Plone site.

Example usage:

def test_with_profile(portal, apply_profiles):
    apply_profiles(portal, ["my.addon:testing"])

Content#

Inspect content types and create content items.

create_content is session-scoped and takes the container explicitly, so you can call it against any portal or folder.

pytest_plone.fixtures.content.get_fti(portal: Products.CMFPlone.Portal.PloneSite) pytest_plone._types.FTIGetter#

Provides a method to get the Factory Type Information for a type by name.

Example usage:

def test_fti(get_fti):
    fti = get_fti("Person")
    assert IDexterityFTI.providedBy(fti)
    assert fti.title == "Person"
pytest_plone.fixtures.content.get_behaviors(get_fti: pytest_plone._types.FTIGetter) pytest_plone._types.BehaviorsGetter#

Provides a method to get the list of behaviors for a type.

Example usage:

def test_behaviors(get_behaviors):
    behaviors = get_behaviors("Person")
    assert "plone.namefromtitle" in behaviors
pytest_plone.fixtures.content.create_content() pytest_plone._types.ContentCreator#

Create content items in a Plone site as the site owner.

Example usage:

def test_with_content(portal, create_content):
    create_content(portal, [
        {"type": "Document", "id": "doc1", "title": "A Document"},
    ])
    assert "doc1" in portal

Security#

pytest_plone.fixtures.security.grant_roles() pytest_plone._types.RolesGranter#

Grant local roles to the test user on the portal.

Example usage:

def test_manager_action(portal, grant_roles):
    grant_roles(portal, ["Manager"])
    # test user now has Manager role on portal

Vocabularies#

pytest_plone.fixtures.vocabularies.get_vocabulary() pytest_plone._types.VocabularyGetter#

Provides a method to get a named vocabulary in a given context.

Example usage:

def test_vocabulary(portal, get_vocabulary):
    voc = get_vocabulary("plone.app.vocabularies.SupportedContentLanguages", portal)
    assert "en" in voc
    term = voc.getTerm("en")
    assert term.title == "English"

Distribution#

Create a Plone site from a plone.distribution distribution, the way a real deployment does.

create_site returns a callable that builds a new site in a Zope app from the distribution named by distribution_name, using the answers from answers, and sets it as the current site. It first deletes any existing site with the same id, so each call starts from a clean state. The distribution named by distribution_name must be registered, for example by loading the ZCML of a package that declares it with a plone:distribution directive.

The distribution_name, answers, and site_logo fixtures are meant to be overridden in your own conftest.py to point at your distribution and customize the site. site_owner_name and site_owner_password expose the site owner credentials, so tests and fixtures can depend on them instead of importing the constants.

pytest_plone.fixtures.distribution.create_site(distribution_name: str, site_owner_name: str) pytest_plone._types.SiteCreator#

Return a callable that creates a Plone site from a distribution.

The returned callable creates a new site in app from the distribution_name distribution, deleting any existing site with the same site_id first to guarantee a clean state, and sets it as the current local site. The created site coexists with the site provided by the testing layer (a second site, by design).

Parameters:
  • distribution_name – distribution the site is created from.

  • site_owner_name – login of the site owner the site is created as.

Returns:

a callable func(app, answers) -> PloneSite.

pytest_plone.fixtures.distribution.distribution_name() str#

Return the name of the distribution to create sites from.

Override this fixture on your tests to change the distribution used for creating Plone sites with the fixture create_site.

Returns:

the distribution name.

pytest_plone.fixtures.distribution.answers(site_logo: str) dict#

Return the default answers for creating a distribution site.

Override this fixture on your tests to change the answers for creating a Plone site from a distribution.

Parameters:

site_logo – data-URI logo injected as the site_logo answer.

Returns:

a mapping of answers passed to the distribution site handler.

Return a data-URI logo usable as the site_logo answer.

Returns:

an SVG image encoded as a data: URI.

pytest_plone.fixtures.base.site_owner_name() str#

Return the login name of the site owner (Manager) test user.

Returns:

the SITE_OWNER_NAME from :mod:plone.app.testing.

pytest_plone.fixtures.base.site_owner_password() str#

Return the password of the site owner (Manager) test user.

Returns:

the SITE_OWNER_PASSWORD from :mod:plone.app.testing.

Environment#

pytest_plone.fixtures.env.generate_mo() collections.abc.Generator[None]#

Compile available .po files and generate .mo files.

This is a session fixture, as it needs to compile files just once.

generate_mo does nothing unless a test requests it. To compile translations once for the whole suite, pull it in from an autouse session fixture in your conftest.py:

import pytest


@pytest.fixture(scope="session", autouse=True)
def session_initialization(generate_mo):
    """Force translation files to be compiled."""