From 81e51d9f27abf58da5be431b0a4590342aa1880c Mon Sep 17 00:00:00 2001
From: Alex Pott <alex.a.pott@googlemail.com>
Date: Fri, 29 Jul 2016 13:17:46 +0100
Subject: [PATCH] Issue #2712935 by Wim Leers, Fabianx, catch, alexpott:
 Messages are not rendered last, meaning those messages set within
 placeholders only appear on the next request

---
 core/lib/Drupal/Core/Render/Renderer.php      | 29 +++++-
 core/modules/big_pipe/src/Render/BigPipe.php  | 42 ++++++--
 .../big_pipe/src/Tests/BigPipeTest.php        | 18 +++-
 .../BigPipeRegressionTest.php                 | 57 +++++++++--
 .../render_placeholder_message_test.info.yml  |  6 ++
 ...ender_placeholder_message_test.routing.yml | 24 +++++
 ...RenderPlaceholderMessageTestController.php | 99 +++++++++++++++++++
 .../Render/PlaceholderMessageTest.php         | 58 +++++++++++
 8 files changed, 314 insertions(+), 19 deletions(-)
 create mode 100644 core/modules/system/tests/modules/render_placeholder_message_test/render_placeholder_message_test.info.yml
 create mode 100644 core/modules/system/tests/modules/render_placeholder_message_test/render_placeholder_message_test.routing.yml
 create mode 100644 core/modules/system/tests/modules/render_placeholder_message_test/src/RenderPlaceholderMessageTestController.php
 create mode 100644 core/modules/system/tests/src/Functional/Render/PlaceholderMessageTest.php

diff --git a/core/lib/Drupal/Core/Render/Renderer.php b/core/lib/Drupal/Core/Render/Renderer.php
index 09a1bf103224..0bf1d631958e 100644
--- a/core/lib/Drupal/Core/Render/Renderer.php
+++ b/core/lib/Drupal/Core/Render/Renderer.php
@@ -636,8 +636,33 @@ protected function replacePlaceholders(array &$elements) {
       return FALSE;
     }
 
-    foreach (array_keys($elements['#attached']['placeholders']) as $placeholder) {
-      $elements = $this->renderPlaceholder($placeholder, $elements);
+    // The 'status messages' placeholder needs to be special cased, because it
+    // depends on global state that can be modified when other placeholders are
+    // being rendered: any code can add messages to render.
+    // This violates the principle that each lazy builder must be able to render
+    // itself in isolation, and therefore in any order. However, we cannot
+    // change the way drupal_set_message() works in the Drupal 8 cycle. So we
+    // have to accommodate its special needs.
+    // Allowing placeholders to be rendered in a particular order (in this case:
+    // last) would violate this isolation principle. Thus a monopoly is granted
+    // to this one special case, with this hard-coded solution.
+    // @see \Drupal\Core\Render\Element\StatusMessages
+    // @see https://www.drupal.org/node/2712935#comment-11368923
+
+    // First render all placeholders except 'status messages' placeholders.
+    $message_placeholders = [];
+    foreach ($elements['#attached']['placeholders'] as $placeholder => $placeholder_element) {
+      if (isset($placeholder_element['#lazy_builder']) && $placeholder_element['#lazy_builder'][0] === 'Drupal\Core\Render\Element\StatusMessages::renderMessages') {
+        $message_placeholders[] = $placeholder;
+      }
+      else {
+        $elements = $this->renderPlaceholder($placeholder, $elements);
+      }
+    }
+
+    // Then render 'status messages' placeholders.
+    foreach ($message_placeholders as $message_placeholder) {
+      $elements = $this->renderPlaceholder($message_placeholder, $elements);
     }
 
     return TRUE;
diff --git a/core/modules/big_pipe/src/Render/BigPipe.php b/core/modules/big_pipe/src/Render/BigPipe.php
index 91452085b004..1894aa96aa6e 100644
--- a/core/modules/big_pipe/src/Render/BigPipe.php
+++ b/core/modules/big_pipe/src/Render/BigPipe.php
@@ -134,7 +134,7 @@ public function sendContent($content, array $attachments) {
     $pre_body = implode('', $parts);
 
     $this->sendPreBody($pre_body, $nojs_placeholders, $cumulative_assets);
-    $this->sendPlaceholders($placeholders, $this->getPlaceholderOrder($pre_body), $cumulative_assets);
+    $this->sendPlaceholders($placeholders, $this->getPlaceholderOrder($pre_body, $placeholders), $cumulative_assets);
     $this->sendPostBody($post_body);
 
     // Close the session again.
@@ -534,6 +534,9 @@ protected function renderPlaceholder($placeholder, array $placeholder_render_arr
    *
    * @param string $html
    *   HTML markup.
+   * @param array $placeholders
+   *   Associative array; the BigPipe placeholders. Keys are the BigPipe
+   *   placeholder IDs.
    *
    * @return array
    *   Indexed array; the order in which the BigPipe placeholders must be sent.
@@ -541,18 +544,45 @@ protected function renderPlaceholder($placeholder, array $placeholder_render_arr
    *   placeholders are kept: if the same placeholder occurs multiple times, we
    *   only keep the first occurrence.
    */
-  protected function getPlaceholderOrder($html) {
+  protected function getPlaceholderOrder($html, $placeholders) {
     $fragments = explode('<div data-big-pipe-placeholder-id="', $html);
     array_shift($fragments);
-    $order = [];
+    $placeholder_ids = [];
 
     foreach ($fragments as $fragment) {
       $t = explode('"></div>', $fragment, 2);
-      $placeholder = $t[0];
-      $order[] = $placeholder;
+      $placeholder_id = $t[0];
+      $placeholder_ids[] = $placeholder_id;
+    }
+    $placeholder_ids = array_unique($placeholder_ids);
+
+    // The 'status messages' placeholder needs to be special cased, because it
+    // depends on global state that can be modified when other placeholders are
+    // being rendered: any code can add messages to render.
+    // This violates the principle that each lazy builder must be able to render
+    // itself in isolation, and therefore in any order. However, we cannot
+    // change the way drupal_set_message() works in the Drupal 8 cycle. So we
+    // have to accommodate its special needs.
+    // Allowing placeholders to be rendered in a particular order (in this case:
+    // last) would violate this isolation principle. Thus a monopoly is granted
+    // to this one special case, with this hard-coded solution.
+    // @see \Drupal\Core\Render\Element\StatusMessages
+    // @see \Drupal\Core\Render\Renderer::replacePlaceholders()
+    // @see https://www.drupal.org/node/2712935#comment-11368923
+    $message_placeholder_ids = [];
+    foreach ($placeholders as $placeholder_id => $placeholder_element) {
+      if (isset($placeholder_element['#lazy_builder']) && $placeholder_element['#lazy_builder'][0] === 'Drupal\Core\Render\Element\StatusMessages::renderMessages') {
+        $message_placeholder_ids[] = $placeholder_id;
+      }
     }
 
-    return array_unique($order);
+    // Return placeholder IDs in DOM order, but with the 'status messages'
+    // placeholders at the end, if they are present.
+    $ordered_placeholder_ids = array_merge(
+      array_diff($placeholder_ids, $message_placeholder_ids),
+      array_intersect($placeholder_ids, $message_placeholder_ids)
+    );
+    return $ordered_placeholder_ids;
   }
 
 }
diff --git a/core/modules/big_pipe/src/Tests/BigPipeTest.php b/core/modules/big_pipe/src/Tests/BigPipeTest.php
index 370b815dfbe4..1a1bc3d3c2c7 100644
--- a/core/modules/big_pipe/src/Tests/BigPipeTest.php
+++ b/core/modules/big_pipe/src/Tests/BigPipeTest.php
@@ -170,6 +170,11 @@ public function testBigPipe() {
       $cases['edge_case__html_non_lazy_builder']->bigPipePlaceholderId => Json::encode($cases['edge_case__html_non_lazy_builder']->embeddedAjaxResponseCommands),
       $cases['exception__lazy_builder']->bigPipePlaceholderId          => NULL,
       $cases['exception__embedded_response']->bigPipePlaceholderId     => NULL,
+    ], [
+      0 => $cases['edge_case__html_non_lazy_builder']->bigPipePlaceholderId,
+      // The 'html' case contains the 'status messages' placeholder, which is
+      // always rendered last.
+      1 => $cases['html']->bigPipePlaceholderId,
     ]);
 
     $this->assertRaw('</body>', 'Closing body tag present.');
@@ -336,8 +341,11 @@ protected function assertBigPipeNoJsPlaceholders(array $expected_big_pipe_nojs_p
    *
    * @param array $expected_big_pipe_placeholders
    *   Keys: BigPipe placeholder IDs. Values: expected AJAX response.
+   * @param array $expected_big_pipe_placeholder_stream_order
+   *   Keys: BigPipe placeholder IDs. Values: expected AJAX response. Keys are
+   *   defined in the order that they are expected to be rendered & streamed.
    */
-  protected function assertBigPipePlaceholders(array $expected_big_pipe_placeholders) {
+  protected function assertBigPipePlaceholders(array $expected_big_pipe_placeholders, array $expected_big_pipe_placeholder_stream_order) {
     $this->pass('Verifying BigPipe placeholders & replacements…', 'Debug');
     $this->assertSetsEqual(array_keys($expected_big_pipe_placeholders), explode(' ', $this->drupalGetHeader('BigPipe-Test-Placeholders')));
     $placeholder_positions = [];
@@ -365,8 +373,12 @@ protected function assertBigPipePlaceholders(array $expected_big_pipe_placeholde
     ksort($placeholder_positions, SORT_NUMERIC);
     $this->assertEqual(array_keys($expected_big_pipe_placeholders), array_values($placeholder_positions));
     $this->assertEqual(count($expected_big_pipe_placeholders), preg_match_all('/' . preg_quote('<div data-big-pipe-placeholder-id="', '/') . '/', $this->getRawContent()));
-    $expected_big_pipe_placeholders_with_replacements = array_filter($expected_big_pipe_placeholders);
-    $this->assertEqual(array_keys($expected_big_pipe_placeholders_with_replacements), array_values($placeholder_replacement_positions));
+    $expected_big_pipe_placeholders_with_replacements = [];
+    foreach ($expected_big_pipe_placeholder_stream_order as $big_pipe_placeholder_id) {
+      $expected_big_pipe_placeholders_with_replacements[$big_pipe_placeholder_id] = $expected_big_pipe_placeholders[$big_pipe_placeholder_id];
+    }
+    $this->assertEqual($expected_big_pipe_placeholders_with_replacements, array_filter($expected_big_pipe_placeholders));
+    $this->assertSetsEqual(array_keys($expected_big_pipe_placeholders_with_replacements), array_values($placeholder_replacement_positions));
     $this->assertEqual(count($expected_big_pipe_placeholders_with_replacements), preg_match_all('/' . preg_quote('<script type="application/vnd.drupal-ajax" data-big-pipe-replacement-for-placeholder-with-id="', '/') . '/', $this->getRawContent()));
 
     $this->pass('Verifying BigPipe start/stop signals…', 'Debug');
diff --git a/core/modules/big_pipe/tests/src/FunctionalJavascript/BigPipeRegressionTest.php b/core/modules/big_pipe/tests/src/FunctionalJavascript/BigPipeRegressionTest.php
index 3ce990797f18..a99d5fccec80 100644
--- a/core/modules/big_pipe/tests/src/FunctionalJavascript/BigPipeRegressionTest.php
+++ b/core/modules/big_pipe/tests/src/FunctionalJavascript/BigPipeRegressionTest.php
@@ -30,14 +30,8 @@ class BigPipeRegressionTest extends JavascriptTestBase {
    * {@inheritdoc}
    */
   public static $modules = [
-    'node',
-    'comment',
     'big_pipe',
     'big_pipe_regression_test',
-    'history',
-    'editor',
-    'ckeditor',
-    'filter',
   ];
 
   /**
@@ -57,6 +51,8 @@ public function setUp() {
    * @see https://www.drupal.org/node/2698811
    */
   public function testCommentForm_2698811() {
+    $this->assertTrue($this->container->get('module_installer')->install(['comment', 'history', 'ckeditor'], TRUE), 'Installed modules.');
+
     // Ensure an `article` node type exists.
     $this->createContentType(['type' => 'article']);
     $this->addDefaultCommentField('node', 'article');
@@ -124,6 +120,8 @@ public function testCommentForm_2698811() {
    * @see https://www.drupal.org/node/2678662
    */
   public function testMultipleClosingBodies_2678662() {
+    $this->assertTrue($this->container->get('module_installer')->install(['render_placeholder_message_test'], TRUE), 'Installed modules.');
+
     $this->drupalLogin($this->drupalCreateUser());
     $this->drupalGet(Url::fromRoute('big_pipe_regression_test.2678662'));
 
@@ -138,9 +136,52 @@ public function testMultipleClosingBodies_2678662() {
 
     // Besides verifying there is no JavaScript syntax error, also verify the
     // HTML structure.
-    $this->assertSession()->responseContains(BigPipe::STOP_SIGNAL . "\n\n\n</body></html>", 'The BigPipe stop signal is present just before the closing </body> and </html> tags.');
+    $this->assertSession()
+      ->responseContains(BigPipe::STOP_SIGNAL . "\n\n\n</body></html>", 'The BigPipe stop signal is present just before the closing </body> and </html> tags.');
     $js_code_until_closing_body_tag = substr(BigPipeRegressionTestController::MARKER_2678662, 0, strpos(BigPipeRegressionTestController::MARKER_2678662, '</body>'));
-    $this->assertSession()->responseNotContains($js_code_until_closing_body_tag . "\n" . BigPipe::START_SIGNAL, 'The BigPipe start signal does NOT start at the closing </body> tag string in an inline script.');
+    $this->assertSession()
+      ->responseNotContains($js_code_until_closing_body_tag . "\n" . BigPipe::START_SIGNAL, 'The BigPipe start signal does NOT start at the closing </body> tag string in an inline script.');
+  }
+
+  /**
+   * Ensure messages set in placeholders always appear.
+   *
+   * @see https://www.drupal.org/node/2712935
+   */
+  public function testMessages_2712935() {
+    $this->assertTrue($this->container->get('module_installer')->install(['render_placeholder_message_test'], TRUE), 'Installed modules.');
+
+    $this->drupalLogin($this->drupalCreateUser());
+    $messages_markup = '<div role="contentinfo" aria-label="Status message"';
+
+    $test_routes = [
+      // Messages placeholder rendered first.
+      'render_placeholder_message_test.first',
+      // Messages placeholder rendered after one, before another.
+      'render_placeholder_message_test.middle',
+      // Messages placeholder rendered last.
+      'render_placeholder_message_test.last',
+    ];
+
+    $assert = $this->assertSession();
+    foreach ($test_routes as $route) {
+      // Verify that we start off with zero messages queued.
+      $this->drupalGet(Url::fromRoute('render_placeholder_message_test.queued'));
+      $assert->responseNotContains($messages_markup);
+
+      // Verify the test case at this route behaves as expected.
+      $this->drupalGet(Url::fromRoute($route));
+      $assert->elementContains('css', 'p.logged-message:nth-of-type(1)', 'Message: P1');
+      $assert->elementContains('css', 'p.logged-message:nth-of-type(2)', 'Message: P2');
+      $assert->responseContains($messages_markup);
+      $assert->elementExists('css', 'div[aria-label="Status message"] ul');
+      $assert->elementContains('css', 'div[aria-label="Status message"] ul li:nth-of-type(1)', 'P1');
+      $assert->elementContains('css', 'div[aria-label="Status message"] ul li:nth-of-type(2)', 'P2');
+
+      // Verify that we end with all messages printed, hence again zero queued.
+      $this->drupalGet(Url::fromRoute('render_placeholder_message_test.queued'));
+      $assert->responseNotContains($messages_markup);
+    }
   }
 
 }
diff --git a/core/modules/system/tests/modules/render_placeholder_message_test/render_placeholder_message_test.info.yml b/core/modules/system/tests/modules/render_placeholder_message_test/render_placeholder_message_test.info.yml
new file mode 100644
index 000000000000..532695bc0a11
--- /dev/null
+++ b/core/modules/system/tests/modules/render_placeholder_message_test/render_placeholder_message_test.info.yml
@@ -0,0 +1,6 @@
+name: 'Placeholder setting a message test'
+type: module
+description: 'Support module for PlaceholderMessageTest.'
+package: Testing
+version: VERSION
+core: 8.x
diff --git a/core/modules/system/tests/modules/render_placeholder_message_test/render_placeholder_message_test.routing.yml b/core/modules/system/tests/modules/render_placeholder_message_test/render_placeholder_message_test.routing.yml
new file mode 100644
index 000000000000..6734cf33e955
--- /dev/null
+++ b/core/modules/system/tests/modules/render_placeholder_message_test/render_placeholder_message_test.routing.yml
@@ -0,0 +1,24 @@
+render_placeholder_message_test.first:
+  path: '/render_placeholder_message_test/first'
+  defaults:
+    _controller: '\Drupal\render_placeholder_message_test\RenderPlaceholderMessageTestController::messagesPlaceholderFirst'
+  requirements:
+    _access: 'TRUE'
+render_placeholder_message_test.middle:
+  path: '/render_placeholder_message_test/middle'
+  defaults:
+    _controller: '\Drupal\render_placeholder_message_test\RenderPlaceholderMessageTestController::messagesPlaceholderMiddle'
+  requirements:
+    _access: 'TRUE'
+render_placeholder_message_test.last:
+  path: '/render_placeholder_message_test/last'
+  defaults:
+    _controller: '\Drupal\render_placeholder_message_test\RenderPlaceholderMessageTestController::messagesPlaceholderLast'
+  requirements:
+    _access: 'TRUE'
+render_placeholder_message_test.queued:
+  path: '/render_placeholder_message_test/queued'
+  defaults:
+    _controller: '\Drupal\render_placeholder_message_test\RenderPlaceholderMessageTestController::queuedMessages'
+  requirements:
+    _access: 'TRUE'
diff --git a/core/modules/system/tests/modules/render_placeholder_message_test/src/RenderPlaceholderMessageTestController.php b/core/modules/system/tests/modules/render_placeholder_message_test/src/RenderPlaceholderMessageTestController.php
new file mode 100644
index 000000000000..f02f0b63f4d6
--- /dev/null
+++ b/core/modules/system/tests/modules/render_placeholder_message_test/src/RenderPlaceholderMessageTestController.php
@@ -0,0 +1,99 @@
+<?php
+
+namespace Drupal\render_placeholder_message_test;
+use Drupal\Core\Render\RenderContext;
+use Symfony\Component\DependencyInjection\ContainerAwareInterface;
+use Symfony\Component\DependencyInjection\ContainerAwareTrait;
+
+class RenderPlaceholderMessageTestController implements ContainerAwareInterface {
+
+  use ContainerAwareTrait;
+
+  /**
+   * @return array
+   */
+  public function messagesPlaceholderFirst() {
+    return $this->build([
+      '<drupal-render-placeholder callback="Drupal\Core\Render\Element\StatusMessages::renderMessages" arguments="0" token="a8c34b5e"></drupal-render-placeholder>',
+      '<drupal-render-placeholder callback="\Drupal\render_placeholder_message_test\RenderPlaceholderMessageTestController::setAndLogMessage" arguments="0=P1" token="0546ada3"></drupal-render-placeholder>',
+      '<drupal-render-placeholder callback="\Drupal\render_placeholder_message_test\RenderPlaceholderMessageTestController::setAndLogMessage" arguments="0=P2" token="83d2df0d"></drupal-render-placeholder>',
+    ]);
+  }
+
+  /**
+   * @return array
+   */
+  public function messagesPlaceholderMiddle() {
+    return $this->build([
+      '<drupal-render-placeholder callback="\Drupal\render_placeholder_message_test\RenderPlaceholderMessageTestController::setAndLogMessage" arguments="0=P1" token="0546ada3"></drupal-render-placeholder>',
+      '<drupal-render-placeholder callback="Drupal\Core\Render\Element\StatusMessages::renderMessages" arguments="0" token="a8c34b5e"></drupal-render-placeholder>',
+      '<drupal-render-placeholder callback="\Drupal\render_placeholder_message_test\RenderPlaceholderMessageTestController::setAndLogMessage" arguments="0=P2" token="83d2df0d"></drupal-render-placeholder>',
+    ]);
+  }
+
+  /**
+   * @return array
+   */
+  public function messagesPlaceholderLast() {
+    return $this->build([
+      '<drupal-render-placeholder callback="\Drupal\render_placeholder_message_test\RenderPlaceholderMessageTestController::setAndLogMessage" arguments="0=P1" token="0546ada3"></drupal-render-placeholder>',
+      '<drupal-render-placeholder callback="\Drupal\render_placeholder_message_test\RenderPlaceholderMessageTestController::setAndLogMessage" arguments="0=P2" token="83d2df0d"></drupal-render-placeholder>',
+      '<drupal-render-placeholder callback="Drupal\Core\Render\Element\StatusMessages::renderMessages" arguments="0" token="a8c34b5e"></drupal-render-placeholder>',
+    ]);
+  }
+
+  /**
+   * @return array
+   */
+  public function queuedMessages() {
+    return ['#type' => 'status_messages'];
+  }
+
+  /**
+   * @return array
+   */
+  protected function build(array $placeholder_order) {
+    $build = [];
+    $build['messages'] = ['#type' => 'status_messages'];
+    $build['p1'] = [
+      '#lazy_builder' => ['\Drupal\render_placeholder_message_test\RenderPlaceholderMessageTestController::setAndLogMessage', ['P1']],
+      '#create_placeholder' => TRUE,
+    ];
+    $build['p2'] = [
+      '#lazy_builder' => ['\Drupal\render_placeholder_message_test\RenderPlaceholderMessageTestController::setAndLogMessage', ['P2']],
+      '#create_placeholder' => TRUE,
+    ];
+
+    /** @var \Drupal\Core\Render\RendererInterface $renderer */
+    $renderer = $this->container->get('renderer');
+    $renderer->executeInRenderContext(new RenderContext(), function () use (&$build, $renderer) {
+      return $renderer->render($build, FALSE);
+    });
+
+    $reordered = [];
+    foreach ($placeholder_order as $placeholder) {
+      $reordered[$placeholder] = $build['#attached']['placeholders'][$placeholder];
+    }
+    $build['#attached']['placeholders'] = $reordered;
+
+    return $build;
+  }
+
+  /**
+   * #lazy_builder callback; sets and prints a message.
+   *
+   * @param string $message
+   *   The message to send.
+   *
+   * @return array
+   *   A renderable array containing the message.
+   */
+  public static function setAndLogMessage($message) {
+    // Set message.
+    drupal_set_message($message);
+
+    // Print which message is expected.
+    return ['#markup' => '<p class="logged-message">Message: ' . $message . '</p>'];
+  }
+
+}
diff --git a/core/modules/system/tests/src/Functional/Render/PlaceholderMessageTest.php b/core/modules/system/tests/src/Functional/Render/PlaceholderMessageTest.php
new file mode 100644
index 000000000000..185ca4985a0c
--- /dev/null
+++ b/core/modules/system/tests/src/Functional/Render/PlaceholderMessageTest.php
@@ -0,0 +1,58 @@
+<?php
+
+namespace Drupal\Tests\system\Functional\Render;
+
+use Drupal\Core\Url;
+use Drupal\Tests\BrowserTestBase;
+
+/**
+ * Functional test verifying that messages set in placeholders always appear.
+ *
+ * @group Render
+ */
+class PlaceholderMessageTest extends BrowserTestBase {
+
+  /**
+   * Modules to enable.
+   *
+   * @var array
+   */
+  public static $modules = ['render_placeholder_message_test'];
+
+  /**
+   * Test rendering of message placeholder.
+   */
+  public function testMessagePlaceholder() {
+    $messages_markup = '<div role="contentinfo" aria-label="Status message"';
+
+    $test_routes = [
+      // Messages placeholder rendered first.
+      'render_placeholder_message_test.first',
+      // Messages placeholder rendered after one, before another.
+      'render_placeholder_message_test.middle',
+      // Messages placeholder rendered last.
+      'render_placeholder_message_test.last',
+    ];
+
+    $assert = $this->assertSession();
+    foreach ($test_routes as $route) {
+      // Verify that we start off with zero messages queued.
+      $this->drupalGet(Url::fromRoute('render_placeholder_message_test.queued'));
+      $assert->responseNotContains($messages_markup);
+
+      // Verify the test case at this route behaves as expected.
+      $this->drupalGet(Url::fromRoute($route));
+      $assert->elementContains('css', 'p.logged-message:nth-of-type(1)', 'Message: P1');
+      $assert->elementContains('css', 'p.logged-message:nth-of-type(2)', 'Message: P2');
+      $assert->responseContains($messages_markup);
+      $assert->elementExists('css', 'div[aria-label="Status message"] ul');
+      $assert->elementContains('css', 'div[aria-label="Status message"] ul li:nth-of-type(1)', 'P1');
+      $assert->elementContains('css', 'div[aria-label="Status message"] ul li:nth-of-type(2)', 'P2');
+
+      // Verify that we end with all messages printed, hence again zero queued.
+      $this->drupalGet(Url::fromRoute('render_placeholder_message_test.queued'));
+      $assert->responseNotContains($messages_markup);
+    }
+  }
+
+}
-- 
GitLab