Canvas AI: add layout fixture support so page builder and template builder agent tests can run against a realistic layout
<h3 id="overview">Overview</h3>
🎉 [#3582390] has landed with automated tests for the orchestrator agent.
This is the second part of #3591457
Both the page builder agent and the template builder agent depend on the current layout JSON, which is sent by the Canvas UI and stored in <code>Drupal\canvas_ai\CanvasAiTempStore</code> under the key <code>CanvasAiTempStore::CURRENT_LAYOUT_KEY</code>. The layout is loaded from tempstore by the <code>Drupal\canvas_ai\Plugin\AiFunctionCall\GetCurrentLayout</code> tool plugin, and by <code>Drupal\canvas_ai\CanvasAiPageBuilderHelper::hasChildComponents()</code>, <code>Drupal\canvas_ai\CanvasAiPageBuilderHelper::processSetTemplateDataToolInput()</code>, and <code>Drupal\canvas_ai\Controller\CanvasBuilder::render()</code>.
Because the layout is populated from the browser at runtime, agent tests in the <code>canvas_ai_agents_test</code> module have no layout in tempstore by default. This makes it impossible to write meaningful tests for the page builder and template builder agents against realistic page states (such as an empty content region, a region that already contains a hero component, or a page with multiple components).
<h3 id="proposed-resolution">Proposed resolution</h3>
<h4>Layout fixture files</h4>
Add JSON layout fixture files to <code>modules/canvas_ai/tests/modules/canvas_ai_agents_test/fixtures/page_layout/</code>. Each file represents a different starting layout that a test might need (for example, <code>page-with-hero.json</code> for a content region that already holds a hero component, or an empty-region fixture for placement tests).
<h4>LayoutFixtureSubscriber</h4>
Add a new event subscriber, <code>Drupal\canvas_ai_agents_test\EventSubscriber\LayoutFixtureSubscriber</code>, to the <code>canvas_ai_agents_test</code> module. It subscribes to <code>KernelEvents::REQUEST</code> and <code>KernelEvents::RESPONSE</code>.
On request, it checks whether the current route is one of the agent-test routes (<code>ai_agents_test.group_ajax</code> or <code>ai_agents_test.run_test</code>). If so, it loads the <code>ai_agents_test</code> entity for the running test, reads a <code>canvas_layout_fixture</code> key from the entity's tokens YAML, resolves the named JSON file from <code>fixtures/page_layout/</code>, and writes its contents into <code>CanvasAiTempStore</code> under <code>CanvasAiTempStore::CURRENT_LAYOUT_KEY</code>.
On response, it clears the same key from tempstore so successive tests in a group run do not bleed into each other.
To opt a test in, the test author adds one line to the test entity's tokens YAML:
<code>canvas_layout_fixture: page-with-hero</code>
<h4>SubAgentResponseMockSubscriber update</h4>
The existing <code>Drupal\canvas_ai_agents_test\EventSubscriber\SubAgentResponseMockSubscriber</code> currently intercepts every AI call to a Canvas sub-agent and returns a canned success response. This blanket approach would prevent page builder and template builder tests from executing their real tool chains.
The subscriber must be updated so that mocking is opt-in. It should subscribe to <code>KernelEvents::REQUEST</code>, load the test entity, and enable sub-agent mocking only when the test entity's <code>ai_agent</code> field targets <code>canvas_ai_orchestrator</code>. When the test targets a sub-agent directly (page builder, template builder, etc.), mocking stays disabled and the sub-agent executes normally so tool outputs can be asserted.
<h4>Service registration</h4>
Both subscribers are registered in <code>canvas_ai_agents_test.services.yml</code> with autowiring and autoconfiguration enabled. No manual tag declarations are needed.
<h3 id="testing-steps">Testing steps</h3>
<ol>
<li>Install the canvas_ai_agents_test module in the Standard installation profile.</li>
<li>Go to <code>/admin/config/ai/agents-test</code>.</li>
<li>Click <strong>Import test</strong> and import the <code>test_page_builder_agent_test.yaml</code> file attached to this issue.</li>
<li>Run the imported test.</li>
<li>Open <strong>Agent Data</strong> in the result.</li>
<li>Find the <em>Tool processed: <code>get_current_layout</code> / Values: Current layout</em> section in the system prompt. It should contain the contents of <code>modules/canvas_ai/tests/modules/canvas_ai_agents_test/fixtures/page_layout/page-with-hero.json</code>, because the test carries the token <code>canvas_layout_fixture: page-with-hero</code>.</li>
</ol>
Once this issue is merged, a follow-up issue should be created to build an agentic skill that generates page builder agent test YAML files automatically.[test_page_builder_agent_test.yaml](/uploads/f0928bbae044287de2da3a29b7cc1c08/test_page_builder_agent_test.yaml)
<em>Issue description generated with AI assistance.</em>
issue