Spent a morning chasing a slow CI on a Plone 6.1 project and tracked it down to something that probably bites more people, so here it is.
Symptom: test suite crawling. pytest --durations=0 showed the costliest entries all in the setup phase, ~2.7s each, not in call. It is layer thrashing, not slow tests.
Root cause: with plain function-style tests (def test_x(portal): ..., using the pytest-plone fixtures) the full layer setUp - including applyProfile - runs once per test. zope.pytestlayer's keep-optimization only kicks in for items that carry a .layer attribute, i.e. unittest.TestCase classes the old zope.testrunner way. Function tests have no .layer, so the class-scoped layer fixture is torn down and set up again for every single test. Counted it: 15 tests -> 15 full layer setups.
Fix is one fixture in conftest.py:
@pytest.fixture(autouse=True, scope="session")
def _keep_layers_for_session(integration_session):
"""Keep the expensive layer set up for the whole session."""
integration_session is the session-scoped fixture fixtures_factory already generates (<prefix>_session). Requesting it once parks the layer in keep_for_whole_session so it survives the run. Per-test isolation is untouched — IntegrationTesting still rolls back the transaction per test.
Result on my suite: ~275s -> ~14s, 253 tests, ~20x
Question for the maintainers / the room: shouldn't pytest-plone keep the layers session-wide by default? With everyone moving from TestCase to function-style tests this is an easy, silent performance cliff to fall off. Happy to open a PR if there's agreement on the approach.