Skip to content

[#677] Added shared entity registry with automatic scenario cleanup.#686

Open
AlexSkrypnyk wants to merge 3 commits into
mainfrom
feature/677-entity-cleanup
Open

[#677] Added shared entity registry with automatic scenario cleanup.#686
AlexSkrypnyk wants to merge 3 commits into
mainfrom
feature/677-entity-cleanup

Conversation

@AlexSkrypnyk

@AlexSkrypnyk AlexSkrypnyk commented Jul 7, 2026

Copy link
Copy Markdown
Member

Closes #677

Summary

Adds a shared per-scenario entity registry to HelperTrait (Drupal) that centralises cleanup of Drupal entities created during Behat scenarios. Instead of each direct-API creation trait maintaining its own registry property, its own #[AfterScenario] hook, and its own skip tag, entities are registered into one array and deleted in reverse creation order by a single hook.

Changes

  • src/Drupal/HelperTrait.php: adds entityRegister(EntityInterface) and entityRegisterId(string $type, int|string $id) to record [type, id] pairs in a new $entityRegistry array, and a single #[AfterScenario('@api')] entityCleanupAfterScenario() hook that reloads each registered entity by id and deletes it in reverse creation order, tolerating entities already deleted elsewhere in the scenario.
  • A hard-coded ENTITY_CLEANUP_EXCLUDED_TYPES list (node, user, taxonomy_term, user_role, language, configurable_language) stops the hook from double-deleting entities the base Drupal Extension already cleans up.
  • Two bypass tags: @behat-steps-skip:entityCleanupAfterScenario skips cleanup entirely for a scenario; @behat-steps-entity-cleanup-skip:ENTITY_TYPE_ID skips one entity type and can be repeated for several types.
  • Retrofits 9 creation traits to call entityRegister() / entityRegisterId() and removes their individual registry property and #[AfterScenario] hook: MediaTrait, FileTrait (managed files only - its fileAfterScenario hook still handles unmanaged files), ParagraphsTrait, ContentBlockTrait, EckTrait, MenuTrait (menus and menu links), RedirectTrait, BlockTrait, WebformTrait.
  • RedirectTrait and BlockTrait also drop manual registry-pruning from their delete steps, since the shared hook's reload-and-tolerate approach already handles entities deleted mid-scenario.
  • Node, user, taxonomy term, and role creation is untouched - it remains the responsibility of the base Drupal Extension's own cleanup.
  • docs.php: registers the new behat-steps-entity-cleanup-skip parametrized tag.
  • tests/behat/features/drupal_entity_cleanup.feature (new): paired scenarios that prove auto-cleanup, the global skip tag, and the per-type skip tag, relying on Behat's file-order execution - one scenario creates an entity, the next asserts whether it survived teardown.
  • tests/phpunit/src/Drupal/HelperTraitTest.php: unit tests for entityRegisterId() and entityCleanupSkippedTypes().
  • drupal_block.feature, drupal_content_block.feature, drupal_eck.feature: migrated from the removed per-trait skip tags to the new per-type tag.
  • MIGRATION.md (new): maps each removed skip tag to its replacement.
  • README.md: documents the cleanup mechanism and its two bypass tags.
  • STEPS.md: regenerated.

Breaking change

The per-trait cleanup skip tags are removed: @behat-steps-skip:mediaAfterScenario, :contentBlockAfterScenario, :eckAfterScenario, :menuAfterScenario, :redirectAfterScenario, :blockAfterScenario, :webformAfterScenario, :paragraphsAfterScenario. See MIGRATION.md for the replacement tag for each.

Before / After

BEFORE: one registry, one hook and one skip tag per trait

┌────────────────────────────────────────────┐
│ XTrait                                     │
├────────────────────────────────────────────┤
│ $xEntities (own array property)            │
│ xAfterScenario() #[AfterScenario('@api')]  │
│  - deletes everything in $xEntities        │
│ skip tag: @behat-steps-skip:xAfterScenario │
└────────────────────────────────────────────┘

  same shape repeated independently in: MediaTrait, FileTrait,
  ParagraphsTrait, ContentBlockTrait, EckTrait, MenuTrait, RedirectTrait,
  BlockTrait, WebformTrait


AFTER: one shared registry and one hook, fed by every creation trait

┌────────────────────────────────────────────────────────┐
│ HelperTrait (DrevOps\BehatSteps\Drupal)                │
├────────────────────────────────────────────────────────┤
│ $entityRegistry (one shared array property)            │
├────────────────────────────────────────────────────────┤
│ entityRegister($entity)                                │
│ entityRegisterId($type, $id)                           │
├────────────────────────────────────────────────────────┤
│ entityCleanupAfterScenario() #[AfterScenario('@api')]  │
│  - deletes $entityRegistry in reverse creation order   │
│  - reloads each entity by id, tolerates pre-deletion   │
│  - excludes node, user, taxonomy_term, user_role,      │
│    language, configurable_language                     │
├────────────────────────────────────────────────────────┤
│ skip all: @behat-steps-skip:entityCleanupAfterScenario │
│ skip type: @behat-steps-entity-cleanup-skip:TYPE       │
└────────────────────────────────────────────────────────┘
               ▲
               │ entityRegister() / entityRegisterId()
               │
  MediaTrait, FileTrait, ParagraphsTrait, ContentBlockTrait, EckTrait,
  MenuTrait, RedirectTrait, BlockTrait, WebformTrait
  (no own registry, no own hook, no own skip tag)

Summary

This PR introduces a shared, per-scenario Drupal entity registry used by Behat step traits to ensure scenario-created entities are automatically cleaned up at teardown in reverse creation order. Cleanup is implemented once in src/Drupal/HelperTrait.php via a #[AfterScenario('@api')] hook that:

  • records created entities centrally during the scenario,
  • reloads entities by type/id before deletion, and
  • tolerates entities that may have been deleted earlier in the scenario.

Key behavior changes

  • Centralized cleanup: Entity-creating traits now register created entities via HelperTrait::entityRegister() instead of maintaining their own static registries and trait-level *AfterScenario hooks.
  • Reverse-order deletion: Entities recorded in the shared registry are deleted in reverse creation order.
  • Compatibility with base Drupal extension: Cleanup excludes entity types already handled by the base Drupal Extension via ENTITY_CLEANUP_EXCLUDED_TYPES.
  • Bypass/skip tags:
    • Global skip: behat-steps-skip:entityCleanupAfterScenario
    • Per-entity-type skip (repeatable): @behat-steps-entity-cleanup-skip:<entity_type_id>

Traits updated to use the shared registry

Removed per-trait teardown logic and switched to registering entities for the shared cleanup:

  • MediaTrait
  • FileTrait (managed files only via registry; unmanaged files still cleaned up via unlinking tracked URIs)
  • ParagraphsTrait
  • ContentBlockTrait
  • EckTrait
  • MenuTrait
  • RedirectTrait
  • BlockTrait
  • WebformTrait

Documentation and tag validation updates

  • Added MIGRATION.md documenting the new shared cleanup mechanism, removal/replacement of old per-trait cleanup skip tags, and the FileTrait managed vs unmanaged behavior.
  • Updated README.md with an “Automatic entity cleanup” section and the new tag-based controls.
  • Updated STEPS.md to reflect the new lifecycle wording and remove references to old per-trait skip tags.
  • Extended docs.php tag validation to recognize the parametrized tag @behat-steps-entity-cleanup-skip:<value>.

Tests

  • Added tests/behat/features/drupal_entity_cleanup.feature to verify:
    • entities created in one scenario are removed after scenario teardown,
    • the global cleanup skip tag preserves all registered entities (requiring manual removal),
    • the per-type skip tag preserves only specified entity types (requiring manual removal).
  • Updated existing feature files to use the new per-entity-type skip tags:
    • tests/behat/features/drupal_block.feature
    • tests/behat/features/drupal_content_block.feature
    • tests/behat/features/drupal_eck.feature
  • Expanded tests/phpunit/src/Drupal/HelperTraitTest.php with coverage for entity registration and scenario skip-tag parsing.

Critical: Step format guideline violations (per CONTRIBUTING.md)

The following feature steps use Given I ... and/or Then I ..., which conflicts with the documented steps format guidance (“Avoid using Given I” / “Avoid using Then I”):

  • tests/behat/features/drupal_content_block.feature
    • Given I am logged in ...
  • tests/behat/features/drupal_block.feature
    • Then I should see ...
    • Then I should not see ...
  • tests/behat/features/drupal_eck.feature
    • Given I am logged in ...

@coderabbitai

coderabbitai Bot commented Jul 7, 2026

Copy link
Copy Markdown

Review Change Stack

No actionable comments were generated in the recent review. 🎉

ℹ️ Recent review info
⚙️ Run configuration

Configuration used: Repository UI

Review profile: ASSERTIVE

Plan: Pro Plus

Run ID: 4e506ede-f967-411e-9a72-881ce8607035

📥 Commits

Reviewing files that changed from the base of the PR and between db57546 and 8449a93.

📒 Files selected for processing (2)
  • src/Drupal/HelperTrait.php
  • tests/phpunit/src/Drupal/HelperTraitTest.php

Walkthrough

Adds shared scenario-scoped entity cleanup in HelperTrait, migrates Drupal traits to register created entities there, updates skip-tag validation and documentation, and adds Behat coverage for teardown and skip behavior.

Changes

Unified entity cleanup

Layer / File(s) Summary
HelperTrait registry and cleanup
src/Drupal/HelperTrait.php, tests/phpunit/src/Drupal/HelperTraitTest.php
Adds the entity registry, registration helpers, excluded types, cleanup hook, skip-tag parsing, and unit tests for registration and tag parsing.
Tag validation registry
docs.php
Adds behat-steps-entity-cleanup-skip to the Behat tag registry as a parametrized tag.
Block, ContentBlock, Eck, and File traits
src/Drupal/BlockTrait.php, src/Drupal/ContentBlockTrait.php, src/Drupal/EckTrait.php, src/Drupal/FileTrait.php, tests/behat/features/drupal_block.feature, tests/behat/features/drupal_content_block.feature, tests/behat/features/drupal_eck.feature
Removes per-trait cleanup arrays and hooks, registers created entities through HelperTrait, and updates the matching feature tags and file cleanup behavior.
Media, Menu, Paragraphs, Redirect, and Webform traits
src/Drupal/MediaTrait.php, src/Drupal/MenuTrait.php, src/Drupal/ParagraphsTrait.php, src/Drupal/RedirectTrait.php, src/Drupal/WebformTrait.php
Removes per-trait teardown hooks and static registries, registers created entities with HelperTrait, and removes redirect deletion tracking.
End-to-end entity cleanup feature
tests/behat/features/drupal_entity_cleanup.feature
Adds Behat scenarios covering teardown cleanup, global skip behavior, and per-type skip behavior for registered entities.
Documentation updates
MIGRATION.md, README.md, STEPS.md
Adds the migration guide, README cleanup section, and updates STEPS.md wording and skip-tag references for the affected traits.

Estimated code review effort: 4 (Complex) | ~60 minutes

Possibly related PRs

Poem

Hoppity-hop, the registry grew,
Old cleanup hooks were split in two.
Reverse they go at scenario end,
With skip tags here to help amend.
🐇 tidy trails and no stray crumbs,
The burrow hums, the cleanup comes.

🚥 Pre-merge checks | ✅ 5
✅ Passed checks (5 passed)
Check name Status Explanation
Description Check ✅ Passed Check skipped - CodeRabbit’s high-level summary is enabled.
Title check ✅ Passed The title clearly summarizes the main change: shared entity registration with automatic scenario cleanup.
Linked Issues check ✅ Passed The PR implements shared reverse-order cleanup, bypass tags, base-extension exclusions, BDD coverage, and updated docs as requested.
Out of Scope Changes check ✅ Passed The added migration/docs, tag validation, trait refactors, and tests all support the cleanup-tracking objective.
Docstring Coverage ✅ Passed No functions found in the changed files to evaluate docstring coverage. Skipping docstring coverage check.
✨ Finishing Touches
📝 Generate docstrings
  • Create stacked PR
  • Commit on current branch
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Commit unit tests in branch feature/677-entity-cleanup

Comment @coderabbitai help to get the list of available commands.

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 3

Caution

Some comments are outside the diff and can’t be posted inline due to platform limitations.

⚠️ Outside diff range comments (1)
src/Drupal/ContentBlockTrait.php (1)

168-169: 📐 Maintainability & Code Quality | 🟡 Minor | ⚡ Quick win

Update stale docblock referencing removed $blockContentEntities array.

This comment still describes the old per-trait static tracking array, which was removed in favor of entityRegister(). Update it to reflect the shared registry cleanup mechanism.

📝 Proposed doc fix
-   * This internal helper method creates and saves a single content block
-   * entity.
-   * Created entities are stored in the static $blockContentEntities array for
-   * automatic cleanup after the scenario.
+   * This internal helper method creates and saves a single content block
+   * entity.
+   * Created entities are registered via entityRegister() for automatic
+   * cleanup after the scenario.
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@src/Drupal/ContentBlockTrait.php` around lines 168 - 169, Update the stale
docblock in ContentBlockTrait so it no longer references the removed
$blockContentEntities array; revise the comment to describe the current shared
cleanup flow via entityRegister() and related entity registry tracking. Keep the
wording aligned with the current behavior in the trait so the documentation
matches the implementation.
🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

Inline comments:
In `@README.md`:
- Around line 171-177: Update the cleanup documentation paragraph so it matches
HelperTrait::ENTITY_CLEANUP_EXCLUDED_TYPES by explicitly adding the skipped
language entity types, language and configurable_language, alongside the
existing exclusion list. Keep the wording in README consistent with the actual
cleanup behavior described by HelperTrait and the shared registry logic.

In `@src/Drupal/HelperTrait.php`:
- Around line 25-27: The class docblock for HelperTrait has an excluded-types
list that no longer matches ENTITY_CLEANUP_EXCLUDED_TYPES. Update the
documentation near the trait/class comment to include every currently excluded
type, including language and configurable_language, so it accurately reflects
the double-deletion safeguard and stays in sync with the constant.

In `@tests/phpunit/src/Drupal/HelperTraitTest.php`:
- Around line 54-59: Add test coverage for
HelperTrait::entityRegister(EntityInterface) instead of only calling
entityRegisterId() directly. Create a stubbed EntityInterface that returns a
valid id and another that returns NULL, then verify entityRegister() records the
non-null entity via entityRegisterId() and skips the null-id case by leaving the
registry unchanged. Use the existing test helper object in HelperTraitTest to
assert the behavior around the null-id guard.

---

Outside diff comments:
In `@src/Drupal/ContentBlockTrait.php`:
- Around line 168-169: Update the stale docblock in ContentBlockTrait so it no
longer references the removed $blockContentEntities array; revise the comment to
describe the current shared cleanup flow via entityRegister() and related entity
registry tracking. Keep the wording aligned with the current behavior in the
trait so the documentation matches the implementation.
🪄 Autofix (Beta)

Fix all unresolved CodeRabbit comments on this PR:

  • Push a commit to this branch (recommended)
  • Create a new PR with the fixes

ℹ️ Review info
⚙️ Run configuration

Configuration used: Repository UI

Review profile: ASSERTIVE

Plan: Pro Plus

Run ID: e4449e4f-4cab-42bb-ab57-2f87b18e4dca

📥 Commits

Reviewing files that changed from the base of the PR and between a2d7b9f and 5176404.

📒 Files selected for processing (19)
  • MIGRATION.md
  • README.md
  • STEPS.md
  • docs.php
  • src/Drupal/BlockTrait.php
  • src/Drupal/ContentBlockTrait.php
  • src/Drupal/EckTrait.php
  • src/Drupal/FileTrait.php
  • src/Drupal/HelperTrait.php
  • src/Drupal/MediaTrait.php
  • src/Drupal/MenuTrait.php
  • src/Drupal/ParagraphsTrait.php
  • src/Drupal/RedirectTrait.php
  • src/Drupal/WebformTrait.php
  • tests/behat/features/drupal_block.feature
  • tests/behat/features/drupal_content_block.feature
  • tests/behat/features/drupal_eck.feature
  • tests/behat/features/drupal_entity_cleanup.feature
  • tests/phpunit/src/Drupal/HelperTraitTest.php

Comment thread README.md Outdated
Comment thread src/Drupal/HelperTrait.php Outdated
Comment thread tests/phpunit/src/Drupal/HelperTraitTest.php
@codecov

codecov Bot commented Jul 7, 2026

Copy link
Copy Markdown

Codecov Report

✅ All modified and coverable lines are covered by tests.
✅ Project coverage is 97.13%. Comparing base (a2d7b9f) to head (8449a93).

Additional details and impacted files
@@            Coverage Diff             @@
##             main     #686      +/-   ##
==========================================
- Coverage   97.15%   97.13%   -0.02%     
==========================================
  Files          51       51              
  Lines        4215     4192      -23     
==========================================
- Hits         4095     4072      -23     
  Misses        120      120              

☔ View full report in Codecov by Harness.
📢 Have feedback on the report? Share it here.

🚀 New features to boost your workflow:
  • ❄️ Test Analytics: Detect flaky tests, report on failures, and find test suite problems.

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Caution

Some comments are outside the diff and can’t be posted inline due to platform limitations.

⚠️ Outside diff range comments (1)
src/Drupal/HelperTrait.php (1)

104-111: 🩺 Stability & Availability | 🔵 Trivial | ⚡ Quick win

Isolate each entity cleanup failure
getStorage() can throw PluginNotFoundException or InvalidPluginDefinitionException, and delete() can throw EntityStorageException; one failed entity currently aborts the rest of the teardown and leaves entityRegistry uncleared. Wrap each iteration so cleanup keeps going.

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@src/Drupal/HelperTrait.php` around lines 104 - 111, Wrap each iteration in
the entity cleanup loop in HelperTrait::cleanup so a failure in one entity does
not stop the remaining teardown work. Specifically, catch exceptions from both
\Drupal::entityTypeManager()->getStorage(...)->load(...) and the subsequent
delete call around the existing $entity_type_id / $entity_id processing, log or
otherwise handle the failure per entity, and continue to the next entry. Make
sure entityRegistry is still cleared after the loop even when some entities
fail.
🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

Outside diff comments:
In `@src/Drupal/HelperTrait.php`:
- Around line 104-111: Wrap each iteration in the entity cleanup loop in
HelperTrait::cleanup so a failure in one entity does not stop the remaining
teardown work. Specifically, catch exceptions from both
\Drupal::entityTypeManager()->getStorage(...)->load(...) and the subsequent
delete call around the existing $entity_type_id / $entity_id processing, log or
otherwise handle the failure per entity, and continue to the next entry. Make
sure entityRegistry is still cleared after the loop even when some entities
fail.

ℹ️ Review info
⚙️ Run configuration

Configuration used: Repository UI

Review profile: ASSERTIVE

Plan: Pro Plus

Run ID: 6e79c0fb-6bcf-4ad5-a970-fee69aa41252

📥 Commits

Reviewing files that changed from the base of the PR and between 5176404 and db57546.

📒 Files selected for processing (2)
  • README.md
  • src/Drupal/HelperTrait.php

@AlexSkrypnyk AlexSkrypnyk added the Needs review Pull request needs a review from assigned developers label Jul 7, 2026
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

Needs review Pull request needs a review from assigned developers

Projects

None yet

Development

Successfully merging this pull request may close these issues.

Add automatic entity cleanup tracking for scenarios

1 participant