elbformat/ibexa-behat-bundle
A Behat bundle for Ibexa offering a fast, Symfony Kernel-based testing approach with partial database resets, strict behavioral wording, and support for page builder blocks.
1. Install and scope it to the test environment
composer require --dev elbformat/ibexa-behat-bundle
The bundle is only useful for tests, so keep it out of production dependencies:
// config/bundles.php
Elbformat\SymfonyBehatBundle\ElbformatSymfonyBehatBundle::class => ['test' => true],
Elbformat\IbexaBehatBundle\ElbformatIbexaBehatBundle::class => ['test' => true],
The Symfony bundle is a hard dependency of the Ibexa one — it provides the kernel-bootstrapping and base context behaviour, so read its instructions too; the Ibexa README only refers to them.
2. Wire up behat.yml
default:
suites:
default:
contexts:
- Elbformat\IbexaBehatBundle\Context\AdminContext
- Elbformat\IbexaBehatBundle\Context\ContentContext
- Elbformat\IbexaBehatBundle\Context\LandingpageContext
- Elbformat\IbexaBehatBundle\Context\ObjectstateContext
- Elbformat\IbexaBehatBundle\Context\SolrContext
- Elbformat\IbexaBehatBundle\Context\TrashContext
Register only the contexts you actually use — each one adds step definitions and, more importantly, participates in the database reset/dump cycle.
3. Add the file path normalizer before writing any image test
# config/services_behat.yaml
services:
Elbformat\IbexaBehatBundle\IO\TestFilePathNormalizer:
decorates: 'Ibexa\Core\IO\FilePathNormalizer\Flysystem'
arguments: ['@.inner']
Without this, uploaded images get a random hash in the URL and every assertion on an image path becomes flaky. Add it first, not after the first red test.
4. Initialise a dedicated test database once
bin/console -e test do:da:cr
bin/console -e test do:mi:mi -n
bin/console -e test ib:mi:mi -n
bin/console -e test ibexa:reindex
Point .env.test at a throwaway database. The bundle resets content between scenarios and only spares rows below a minimum id, so running it against a real database will destroy real content.
5. Where to look first
behat.yml — which contexts are active; start here when a step is "undefined".vendor/bin/behat -dl — this bundle has no step reference documentation, so the authoritative list of Gherkin is the compiled definition list. Dump it to a file and keep it handy.vendor/elbformat/ibexa-behat-bundle/src/Context/ — the source is the docs..env.test / .env.behat — database DSN and the BEHAT_CONTENT_MIN_ID knob.6. First use case: admin login, then content creation
A minimal first feature proving the stack works — log into the admin UI, create one content item, assert it exists:
Feature: Smoke test
Scenario: Editor can create an article
Given I am logged in as an admin
And there is a "article" content object named "Smoke test"
Then there must be a(n) "article" content object named "Smoke test"
Run vendor/bin/behat and iterate. Get this green before touching page builder, Solr, or trash — those contexts have more environment preconditions (index, schema, permissions).
Treat the contexts as composable layers, not a monolith. Each context owns one concern: AdminContext for admin-UI login, ContentContext for content CRUD and assertions, LandingpageContext for page builder blocks, ObjectstateContext, TrashContext, SolrContext. Split your suites by concern and enable only the contexts each suite needs. A feature tagged @solr shouldn't pay for admin-UI bootstrapping.
Lean on the shared state service instead of re-querying. Recent versions introduced a State service holding a lastContent reference, so a step that creates content and the following assertion step agree on which content they mean without you spelling out the ID. Write scenarios as an ordered narrative rather than repeating identifiers:
Given there is a "landing_page" content object named "Home"
When the page "Home" contains a "hero" block
Then the page "Home" contains a(n) "hero" block
Use fixtures-with-metadata for anything you'll assert later. Content creation steps accept sort metadata, defaults from content-type definitions, and meta properties. Push repetitive field data into content-type defaults in the test config rather than repeating it in every scenario, and use _contentHidden when a scenario needs content invisible in the front end.
Prefer the assertion steps over raw PHP contexts. The bundle's value proposition is "strict wordings in a behavioural manner" — meaning assertions are opinionated and produce useful failure output, including per-field postCheck hooks. When a field type has a postCheck (e.g. image asset), the assertion verifies the underlying relation, not just the stored string. Let it.
Use LandingpageContext for page builder work specifically. Page builder fields are the hardest thing to set up by hand — blocks, their configuration, custom value mapping, scheduling. Fixture-based block creation with custom value mapping keeps that setup in Gherkin instead of a PHP hook.
Keep ObjectstateContext and TrashContext out of the happy path. These are assertion/setup contexts for edge cases: content in the trash still being findable, object state transitions affecting visibility. Use them to set up a state then verify through your normal front-end tests, rather than testing state transitions in isolation.
Add a new content object version step instead of reseeding. Since v2.2.7 there's a step for creating a new version of existing content. This is the right tool for testing editorial workflows (draft → publish → new draft) — far cheaper than tearing down and recreating the content item with a second name.
Bake the four init commands into CI as a cached step. The DB schema + Ibexa migrations + reindex sequence is stable per build; cache the resulting database where your CI allows it, and always run ibexa:reindex after the migration steps.
Extension points. Subclass or decorate the shipped contexts (ContentContext has been explicitly extensible since v1.0.1) to add project-specific steps; register the subclass in behat.yml in place of the parent. For repeated field assertions, add postChecks rather than duplicating Then the field ... lines. For page builder, custom value mapping is the intended hook for non-standard block attributes.
The database reset is destructive and id-scoped. Content and locations with IDs above the minimum survive the reset; below it they're collateral. Default minimum is 1000. If you ship fixed internal content or locations with IDs above that (common when fixtures are imported with high auto-increment values), raise the floor in .env.behat:
BEHAT_CONTENT_MIN_ID=10000
Symptom of getting this wrong: your hand-made landing page structure disappears mid-suite, or the admin preview can't find a siteaccess.
.env.behat is a real file the bundle reads. It is not .env.test.behat and not a Symfony bundle config key. Env vars set only in .env.test won't be seen by the reset logic.
Pin the version against your Ibexa version. v2.0.0 dropped Netgen tags support for Ibexa 4.6 compatibility; v2.2.0 restored Ibexa 4.6 support. If you're on 4.6 and using Netgen tags, you are on a version that cannot serve both — know which one you're giving up.
Field-type assertion regressions are the recurring bug class. Patch releases have repeatedly fixed assertions for ibexa_seo, ezmatrix/ibexa_matrix (indexed arrays, valid in Ibexa 4), and ezbinaryfile validation. If a Then step fails with an "invalid assertion" style error, check the release notes before debugging your fixture — it may be a known fixed bug in a later patch.
Image IDs shift on content edits. v1.1.1 changed the normalizer to increment the attribute id by 100 per content creation, so appending a field to previously created content doesn't renumber existing images. If you add an image field to an earlier step, expect already-asserted image URLs to remain stable — but only if the decorator is actually registered. Verify with one print of the URL before trusting it across a suite.
Undefined step is almost always a missing context. Because definition discovery is per-registered-context, a step that "should exist" and returns undefined means the context isn't in that suite. Check behat.yml suites and tags first, --definitions second.
Stale index looks like a broken assertion. SolrContext and any search-backed assertion will fail on freshly created content if ibexa:reindex wasn't run or the Solr core holds data from a previous run. When a content-exists assertion fails but the content is visibly there in the admin UI, reindex before you touch the fixture.
Don't run the suite against the default env. There's no guard in the bundle stopping you from initialising dev. The only protection is discipline plus a clearly separated DATABASE_URL in .env.test. Consider a CI check that greps the test DSN for the word test.
Focus mode can be disabled for tests. Newer releases allow turning it off; useful when assertions traverse the content tree and focus state changes what's rendered in the admin UI.
Debugging workflow: run a single scenario with vendor/bin/behat features/foo.feature:12, add a context step that dumps the State service's lastContent id, and assert on that id before asserting on fields. Most "wrong field value" failures are actually "wrong content object" failures caused by the state pointer lagging one step behind.
Assume documentation is thin and read the source. With this project's community size, there is no cookbook and few examples in the wild. The context classes and their docblocks are the contract; treat a minor-version bump as potentially changing step phrasing and check the release notes before upgrading in a busy branch.
How can I help you explore Laravel packages today?