Index: /trunk/src/js/_enqueues/admin/common.js
===================================================================
--- /trunk/src/js/_enqueues/admin/common.js	(revision 64281)
+++ /trunk/src/js/_enqueues/admin/common.js	(revision 64282)
@@ -1076,15 +1076,4 @@
 	}
 
-	/*
-	 * The `.below-h2` class is here just for backward compatibility with plugins
-	 * that are (incorrectly) using it. Do not use. Use `.inline` instead. See #34570.
-	 * If '.wp-header-end' is found, append the notices after it otherwise
-	 * after the first h1 or h2 heading found within the main content.
-	 */
-	if ( ! $headerEnd.length ) {
-		$headerEnd = $( '.wrap h1, .wrap h2' ).first();
-	}
-	$( 'div.updated, div.error, div.notice' ).not( '.inline, .below-h2' ).insertAfter( $headerEnd );
-
 	/**
 	 * Makes notices dismissible.
@@ -1525,4 +1514,30 @@
 		input.on('change', toggleUploadButton);
 	})();
+
+	/**
+	 * Moves the admin notices into an `aside` element after the first `h1`.
+	 * Prior to 7.2.0, admin notices were only moved after the first `h1`;
+	 * The `aside` wrapper was added in 7.2.0.
+	 *
+	 * @since 7.2.0
+	 *
+	 * @return {void}
+	 */
+	function moveAdminNotices() {
+		/*
+		 * The `.below-h2` class is here just for backward compatibility with plugins
+		 * that are (incorrectly) using it. Do not use. Use `.inline` instead. See #34570.
+		 * If '.wp-header-end' is found, append the notices after it otherwise
+		 * after the first h1 or h2 heading found within the main content.
+		 */
+		if ( ! $headerEnd.length ) {
+			$headerEnd = $( '.wrap h1, .wrap h2' ).first();
+		}
+		const $notices = $( 'div.updated, div.error, div.notice' ).not( '.inline, .below-h2' );
+		if ( $notices.length ) {
+			const $wpAdminNotices = $( '<aside id="wp-admin-notices"></aside>' ).attr( 'aria-label', __( 'Admin Notices' ) ).insertAfter( $headerEnd );
+			$notices.appendTo( $wpAdminNotices );
+		}
+	}
 
 	/**
@@ -2128,4 +2143,5 @@
 
 	window.wpResponsive.init();
+	moveAdminNotices();
 	setPinMenu();
 	setMenuState();
Index: /trunk/src/wp-admin/admin.php
===================================================================
--- /trunk/src/wp-admin/admin.php	(revision 64281)
+++ /trunk/src/wp-admin/admin.php	(revision 64282)
@@ -101,4 +101,6 @@
 
 require_once ABSPATH . 'wp-admin/includes/admin.php';
+
+WP_Admin_Notice_Collector::init();
 
 auth_redirect();
Index: /trunk/src/wp-includes/class-wp-admin-notice-collector.php
===================================================================
--- /trunk/src/wp-includes/class-wp-admin-notice-collector.php	(revision 64282)
+++ /trunk/src/wp-includes/class-wp-admin-notice-collector.php	(revision 64282)
@@ -0,0 +1,165 @@
+<?php
+/**
+ * Administration API: WP_Admin_Notice_Collector class
+ *
+ * @package WordPress
+ * @subpackage Administration
+ * @since x.x.x
+ */
+
+/**
+ * Capture admin notices generated in PHP during a request.
+ *
+ * Notices are captured via the {@see 'wp_admin_notice_markup'} filter.
+ * a title placeholder is inserted into the admin page's `<title>` tag,
+ * the admin page title is modified via the {@see 'admin_title'}
+ * filter, the admin page is wrapped in an output buffer, and an
+ * `ob_start()` callback replaces the placeholder with the final count once
+ * the buffer is flushed.
+ *
+ * @since 7.2.0
+ */
+class WP_Admin_Notice_Collector {
+
+	/**
+	 * Placeholder inserted into the admin title, replaced with the
+	 * actual notice count once it is known.
+	 *
+	 * @since 7.2.0
+	 * @var string
+	 */
+	const TITLE_PLACEHOLDER = '%%wp_admin_notice_count%%';
+
+	/**
+	 * Notices captured during the current request.
+	 *
+	 * @since 7.2.0
+	 * @var array[]
+	 */
+	protected static $notices = array();
+
+	/**
+	 * Whether the collector's hooks have already been registered.
+	 *
+	 * @since 7.2.0
+	 * @var bool
+	 */
+	protected static $hooked = false;
+
+	/**
+	 * Registers the hooks used to collect notices and to annotate the admin title.
+	 *
+	 * @since 7.2.0
+	 */
+	public static function init() {
+		if ( self::$hooked ) {
+			return;
+		}
+
+		self::$hooked = true;
+
+		add_filter( 'wp_admin_notice_markup', array( __CLASS__, 'capture_notice' ), 10, 3 );
+
+		// Ajax/REST admin requests don't render a <title>, so skip the buffer there.
+		if ( is_admin() && ! wp_doing_ajax() ) {
+			add_filter( 'admin_title', array( __CLASS__, 'add_title_placeholder' ), 10, 2 );
+			ob_start( array( __CLASS__, 'inject_notice_count' ) );
+		}
+	}
+
+	/**
+	 * Captures a single admin notice without altering its markup.
+	 *
+	 * Hooked to the {@see 'wp_admin_notice_markup'} filter.
+	 *
+	 * @since 7.2.0
+	 *
+	 * @param string $markup  The HTML markup for the admin notice.
+	 * @param string $message The message for the admin notice.
+	 * @param array  $args    The arguments for the admin notice.
+	 * @return string The unmodified markup, so the filter remains transparent.
+	 */
+	public static function capture_notice( $markup, $message, $args ) {
+		if ( '' !== trim( wp_strip_all_tags( $message ) ) ) {
+			self::$notices[ $args['notice_id'] ] = array(
+				'markup'  => $markup,
+				'message' => $message,
+				'args'    => $args,
+			);
+		}
+
+		return $markup;
+	}
+
+	/**
+	 * Returns all notices captured during the current request.
+	 *
+	 * @since 7.2.0
+	 *
+	 * @return array[] Array of captured notices. Each entry contains the
+	 *                 notice's 'markup', 'message', and 'args'.
+	 */
+	public static function get_notices() {
+		return self::$notices;
+	}
+
+	/**
+	 * Returns the number of notices captured during the current request.
+	 *
+	 * @since 7.2.0
+	 *
+	 * @return int Notice count.
+	 */
+	public static function get_notice_count() {
+		return count( self::$notices );
+	}
+
+	/**
+	 * Clears all previously captured notices.
+	 *
+	 * @since 7.2.0
+	 */
+	public static function reset() {
+		self::$notices = array();
+	}
+
+	/**
+	 * Prepends a title placeholder via the {@see 'admin_title'} filter.
+	 *
+	 * The real count isn't known yet at this point, since notices are usually
+	 * produced later in the page. inject_notice_count() resolves it.
+	 *
+	 * @since 7.2.0
+	 *
+	 * @param string $admin_title The page title, with extra context added.
+	 * @param string $title       The original page title.
+	 * @return string The admin title with the placeholder prepended.
+	 */
+	public static function add_title_placeholder( $admin_title, $title ) {
+		return self::TITLE_PLACEHOLDER . $admin_title;
+	}
+
+	/**
+	 * Replaces the title placeholder with the final notice count.
+	 *
+	 * Used as an `ob_start()` callback so it runs once the full page - including
+	 * any notices printed after `<title>` - has been buffered.
+	 *
+	 * @since 7.2.0
+	 *
+	 * @param string $buffer The full buffered page output.
+	 * @return string The page output with the placeholder resolved.
+	 */
+	public static function inject_notice_count( $buffer ) {
+		$count = self::get_notice_count();
+
+		if ( 0 === $count ) {
+			return str_replace( self::TITLE_PLACEHOLDER, '', $buffer );
+		}
+
+		/* translators: %d: Number of admin notices on the current page. */
+		$prefix = sprintf( _n( '(%d notice) ', '(%d notices) ', $count ), $count );
+
+		return str_replace( self::TITLE_PLACEHOLDER, $prefix, $buffer );
+	}
+}
Index: /trunk/src/wp-includes/functions.php
===================================================================
--- /trunk/src/wp-includes/functions.php	(revision 64281)
+++ /trunk/src/wp-includes/functions.php	(revision 64282)
@@ -9343,4 +9343,8 @@
 	$args = apply_filters( 'wp_admin_notice_args', $args, $message );
 
+	// Unique ID is intended for internal use, and is not a filterable argument.
+	$unique_id         = wp_unique_prefixed_id( 'wp-admin-notice-' );
+	$args['notice_id'] = $unique_id;
+
 	$wrap_with_p  = false !== $args['paragraph_wrap'];
 	$wrap_opener  = $wrap_with_p ? '<p>' : '';
@@ -9352,7 +9356,13 @@
 		$trimmed_id = trim( $args['id'] );
 
+		$html_builder->set_attribute( 'data-id', $unique_id );
 		if ( '' !== $trimmed_id ) {
 			$html_builder->set_attribute( 'id', $trimmed_id );
-		}
+		} else {
+			$html_builder->set_attribute( 'id', $unique_id );
+		}
+	} else {
+		$html_builder->set_attribute( 'data-id', $unique_id );
+		$html_builder->set_attribute( 'id', $unique_id );
 	}
 
Index: /trunk/src/wp-settings.php
===================================================================
--- /trunk/src/wp-settings.php	(revision 64281)
+++ /trunk/src/wp-settings.php	(revision 64282)
@@ -116,4 +116,5 @@
 require ABSPATH . WPINC . '/meta.php';
 require ABSPATH . WPINC . '/functions.php';
+require ABSPATH . WPINC . '/class-wp-admin-notice-collector.php';
 require ABSPATH . WPINC . '/class-wp-meta-query.php';
 require ABSPATH . WPINC . '/class-wp-matchesmapregex.php';
Index: /trunk/tests/phpunit/tests/functions/wpAdminNotice.php
===================================================================
--- /trunk/tests/phpunit/tests/functions/wpAdminNotice.php	(revision 64281)
+++ /trunk/tests/phpunit/tests/functions/wpAdminNotice.php	(revision 64282)
@@ -22,4 +22,9 @@
 	 */
 	public function test_should_output_admin_notice( $message, $args, $expected ) {
+		// Replace placeholder %%ID%% in expected string, required for test isolation.
+		$next_id = array_last( explode( '-', wp_unique_prefixed_id( 'wp-admin-notice-' ) ) );
+		++$next_id;
+		$expected = str_replace( '%%ID%%', $next_id, $expected );
+
 		ob_start();
 		wp_admin_notice( $message, $args );
@@ -39,5 +44,5 @@
 				'message'  => 'A notice with defaults.',
 				'args'     => array(),
-				'expected' => '<div class="notice"><p>A notice with defaults.</p></div>',
+				'expected' => '<div data-id="wp-admin-notice-%%ID%%" id="wp-admin-notice-%%ID%%" class="notice"><p>A notice with defaults.</p></div>',
 			),
 			'an empty message (used for templates)'     => array(
@@ -49,5 +54,5 @@
 					'additional_classes' => array( 'inline', 'hidden' ),
 				),
-				'expected' => '<div id="message" class="notice notice-error is-dismissible inline hidden"><p></p></div>',
+				'expected' => '<div data-id="wp-admin-notice-%%ID%%" id="message" class="notice notice-error is-dismissible inline hidden"><p></p></div>',
 			),
 			'an empty message (used for templates) without paragraph wrapping' => array(
@@ -60,5 +65,5 @@
 					'paragraph_wrap'     => false,
 				),
-				'expected' => '<div id="message" class="notice notice-error is-dismissible inline hidden"></div>',
+				'expected' => '<div data-id="wp-admin-notice-%%ID%%" id="message" class="notice notice-error is-dismissible inline hidden"></div>',
 			),
 			'an "error" notice'                         => array(
@@ -67,5 +72,5 @@
 					'type' => 'error',
 				),
-				'expected' => '<div class="notice notice-error"><p>An "error" notice.</p></div>',
+				'expected' => '<div data-id="wp-admin-notice-%%ID%%" id="wp-admin-notice-%%ID%%" class="notice notice-error"><p>An "error" notice.</p></div>',
 			),
 			'a "success" notice'                        => array(
@@ -74,5 +79,5 @@
 					'type' => 'success',
 				),
-				'expected' => '<div class="notice notice-success"><p>A "success" notice.</p></div>',
+				'expected' => '<div data-id="wp-admin-notice-%%ID%%" id="wp-admin-notice-%%ID%%" class="notice notice-success"><p>A "success" notice.</p></div>',
 			),
 			'a "warning" notice'                        => array(
@@ -81,5 +86,5 @@
 					'type' => 'warning',
 				),
-				'expected' => '<div class="notice notice-warning"><p>A "warning" notice.</p></div>',
+				'expected' => '<div data-id="wp-admin-notice-%%ID%%" id="wp-admin-notice-%%ID%%" class="notice notice-warning"><p>A "warning" notice.</p></div>',
 			),
 			'an "info" notice'                          => array(
@@ -88,5 +93,5 @@
 					'type' => 'info',
 				),
-				'expected' => '<div class="notice notice-info"><p>An "info" notice.</p></div>',
+				'expected' => '<div data-id="wp-admin-notice-%%ID%%" id="wp-admin-notice-%%ID%%" class="notice notice-info"><p>An "info" notice.</p></div>',
 			),
 			'a type that already starts with "notice-"' => array(
@@ -95,5 +100,5 @@
 					'type' => 'notice-info',
 				),
-				'expected' => '<div class="notice notice-notice-info"><p>A type that already starts with "notice-".</p></div>',
+				'expected' => '<div data-id="wp-admin-notice-%%ID%%" id="wp-admin-notice-%%ID%%" class="notice notice-notice-info"><p>A type that already starts with "notice-".</p></div>',
 			),
 			'a dismissible notice'                      => array(
@@ -102,5 +107,5 @@
 					'dismissible' => true,
 				),
-				'expected' => '<div class="notice is-dismissible"><p>A dismissible notice.</p></div>',
+				'expected' => '<div data-id="wp-admin-notice-%%ID%%" id="wp-admin-notice-%%ID%%" class="notice is-dismissible"><p>A dismissible notice.</p></div>',
 			),
 			'no type and an ID'                         => array(
@@ -109,5 +114,5 @@
 					'id' => 'message',
 				),
-				'expected' => '<div id="message" class="notice"><p>A notice with an ID.</p></div>',
+				'expected' => '<div data-id="wp-admin-notice-%%ID%%" id="message" class="notice"><p>A notice with an ID.</p></div>',
 			),
 			'a type and an ID'                          => array(
@@ -117,5 +122,5 @@
 					'id'   => 'message',
 				),
-				'expected' => '<div id="message" class="notice notice-warning"><p>A warning notice with an ID.</p></div>',
+				'expected' => '<div data-id="wp-admin-notice-%%ID%%" id="message" class="notice notice-warning"><p>A warning notice with an ID.</p></div>',
 			),
 			'no type and additional classes'            => array(
@@ -124,5 +129,5 @@
 					'additional_classes' => array( 'error', 'notice-alt' ),
 				),
-				'expected' => '<div class="notice error notice-alt"><p>A notice with additional classes.</p></div>',
+				'expected' => '<div data-id="wp-admin-notice-%%ID%%" id="wp-admin-notice-%%ID%%" class="notice error notice-alt"><p>A notice with additional classes.</p></div>',
 			),
 			'a type and additional classes'             => array(
@@ -132,5 +137,5 @@
 					'additional_classes' => array( 'error', 'notice-alt' ),
 				),
-				'expected' => '<div class="notice notice-warning error notice-alt"><p>A warning notice with additional classes.</p></div>',
+				'expected' => '<div data-id="wp-admin-notice-%%ID%%" id="wp-admin-notice-%%ID%%" class="notice notice-warning error notice-alt"><p>A warning notice with additional classes.</p></div>',
 			),
 			'a dismissible notice with a type and additional classes' => array(
@@ -141,5 +146,5 @@
 					'additional_classes' => array( 'error', 'notice-alt' ),
 				),
-				'expected' => '<div class="notice notice-warning is-dismissible error notice-alt"><p>A dismissible warning notice with a type and additional classes.</p></div>',
+				'expected' => '<div data-id="wp-admin-notice-%%ID%%" id="wp-admin-notice-%%ID%%" class="notice notice-warning is-dismissible error notice-alt"><p>A dismissible warning notice with a type and additional classes.</p></div>',
 			),
 			'a notice without paragraph wrapping'       => array(
@@ -148,5 +153,5 @@
 					'paragraph_wrap' => false,
 				),
-				'expected' => '<div class="notice"><span>A notice without paragraph wrapping.</span></div>',
+				'expected' => '<div data-id="wp-admin-notice-%%ID%%" id="wp-admin-notice-%%ID%%" class="notice"><span>A notice without paragraph wrapping.</span></div>',
 			),
 			'an unsafe type'                            => array(
@@ -155,5 +160,5 @@
 					'type' => '"><script>alert("Howdy,admin!");</script>',
 				),
-				'expected' => '<div class="notice notice-&quot;><script>alert(&quot;Howdy,admin!&quot;);</script>"><p>A notice with an unsafe type.</p></div>',
+				'expected' => '<div data-id="wp-admin-notice-%%ID%%" id="wp-admin-notice-%%ID%%" class="notice notice-&quot;><script>alert(&quot;Howdy,admin!&quot;);</script>"><p>A notice with an unsafe type.</p></div>',
 			),
 			'an unsafe ID'                              => array(
@@ -162,5 +167,5 @@
 					'id' => '"><script>alert( "Howdy, admin!" );</script> <div class="notice',
 				),
-				'expected' => '<div id="&quot;><script>alert( &quot;Howdy, admin!&quot; );</script> <div class=&quot;notice" class="notice"><p>A notice with an unsafe ID.</p></div>',
+				'expected' => '<div data-id="wp-admin-notice-%%ID%%" id="&quot;><script>alert( &quot;Howdy, admin!&quot; );</script> <div class=&quot;notice" class="notice"><p>A notice with an unsafe ID.</p></div>',
 			),
 			'unsafe additional classes'                 => array(
@@ -169,5 +174,5 @@
 					'additional_classes' => array( '"><script>alert( "Howdy, admin!" );</script> <div class="notice' ),
 				),
-				'expected' => '<div class="notice &quot;><script>alert( &quot;Howdy, admin!&quot; );</script> <div class=&quot;notice"><p>A notice with unsafe additional classes.</p></div>',
+				'expected' => '<div data-id="wp-admin-notice-%%ID%%" id="wp-admin-notice-%%ID%%" class="notice &quot;><script>alert( &quot;Howdy, admin!&quot; );</script> <div class=&quot;notice"><p>A notice with unsafe additional classes.</p></div>',
 			),
 			'a type that is not a string'               => array(
@@ -176,5 +181,5 @@
 					'type' => array(),
 				),
-				'expected' => '<div class="notice"><p>A notice with a type that is not a string.</p></div>',
+				'expected' => '<div data-id="wp-admin-notice-%%ID%%" id="wp-admin-notice-%%ID%%" class="notice"><p>A notice with a type that is not a string.</p></div>',
 			),
 			'a type with only empty space'              => array(
@@ -183,5 +188,5 @@
 					'type' => " \t\r\n",
 				),
-				'expected' => '<div class="notice"><p>A notice with a type with only empty space.</p></div>',
+				'expected' => '<div data-id="wp-admin-notice-%%ID%%" id="wp-admin-notice-%%ID%%" class="notice"><p>A notice with a type with only empty space.</p></div>',
 			),
 			'an ID that is not a string'                => array(
@@ -190,5 +195,5 @@
 					'id' => array( 'message' ),
 				),
-				'expected' => '<div class="notice"><p>A notice with an ID that is not a string.</p></div>',
+				'expected' => '<div data-id="wp-admin-notice-%%ID%%" id="wp-admin-notice-%%ID%%" class="notice"><p>A notice with an ID that is not a string.</p></div>',
 			),
 			'an ID with only empty space'               => array(
@@ -197,5 +202,5 @@
 					'id' => " \t\r\n",
 				),
-				'expected' => '<div class="notice"><p>A notice with an ID with only empty space.</p></div>',
+				'expected' => '<div data-id="wp-admin-notice-%%ID%%" id="wp-admin-notice-%%ID%%" class="notice"><p>A notice with an ID with only empty space.</p></div>',
 			),
 			'dismissible as a truthy value rather than (bool) true' => array(
@@ -204,5 +209,5 @@
 					'dismissible' => 1,
 				),
-				'expected' => '<div class="notice"><p>A notice with dismissible as a truthy value rather than (bool) true.</p></div>',
+				'expected' => '<div data-id="wp-admin-notice-%%ID%%" id="wp-admin-notice-%%ID%%" class="notice"><p>A notice with dismissible as a truthy value rather than (bool) true.</p></div>',
 			),
 			'additional classes that are not an array'  => array(
@@ -211,5 +216,5 @@
 					'additional_classes' => 'class-1 class-2 class-3',
 				),
-				'expected' => '<div class="notice"><p>A notice with additional classes that are not an array.</p></div>',
+				'expected' => '<div data-id="wp-admin-notice-%%ID%%" id="wp-admin-notice-%%ID%%" class="notice"><p>A notice with additional classes that are not an array.</p></div>',
 			),
 			'additional attribute with a value'         => array(
@@ -218,5 +223,5 @@
 					'attributes' => array( 'aria-live' => 'assertive' ),
 				),
-				'expected' => '<div class="notice" aria-live="assertive"><p>A notice with an additional attribute with a value.</p></div>',
+				'expected' => '<div data-id="wp-admin-notice-%%ID%%" id="wp-admin-notice-%%ID%%" class="notice" aria-live="assertive"><p>A notice with an additional attribute with a value.</p></div>',
 			),
 			'additional hidden attribute'               => array(
@@ -225,5 +230,5 @@
 					'attributes' => array( 'hidden' => true ),
 				),
-				'expected' => '<div class="notice" hidden><p>A notice with the hidden attribute.</p></div>',
+				'expected' => '<div data-id="wp-admin-notice-%%ID%%" id="wp-admin-notice-%%ID%%" class="notice" hidden><p>A notice with the hidden attribute.</p></div>',
 			),
 			'additional attribute no associative keys'  => array(
@@ -232,5 +237,5 @@
 					'attributes' => array( 'hidden' ),
 				),
-				'expected' => '<div class="notice" hidden><p>A notice with a boolean attribute without an associative key.</p></div>',
+				'expected' => '<div data-id="wp-admin-notice-%%ID%%" id="wp-admin-notice-%%ID%%" class="notice" hidden><p>A notice with a boolean attribute without an associative key.</p></div>',
 			),
 			'additional attribute with role'            => array(
@@ -239,5 +244,5 @@
 					'attributes' => array( 'role' => 'alert' ),
 				),
-				'expected' => '<div class="notice" role="alert"><p>A notice with an additional attribute role.</p></div>',
+				'expected' => '<div data-id="wp-admin-notice-%%ID%%" id="wp-admin-notice-%%ID%%" class="notice" role="alert"><p>A notice with an additional attribute role.</p></div>',
 			),
 			'multiple additional attributes'            => array(
@@ -249,5 +254,5 @@
 					),
 				),
-				'expected' => '<div class="notice" role="alert" data-test="-1"><p>A notice with multiple additional attributes.</p></div>',
+				'expected' => '<div data-id="wp-admin-notice-%%ID%%" id="wp-admin-notice-%%ID%%" class="notice" role="alert" data-test="-1"><p>A notice with multiple additional attributes.</p></div>',
 			),
 			'data attribute with unsafe value'          => array(
@@ -256,5 +261,5 @@
 					'attributes' => array( 'data-unsafe' => '<script>alert( "Howdy, admin!" );</script>' ),
 				),
-				'expected' => '<div class="notice" data-unsafe="&lt;script&gt;alert( &quot;Howdy, admin!&quot; );&lt;/script&gt;"><p>A notice with an additional attribute with an unsafe value.</p></div>',
+				'expected' => '<div data-id="wp-admin-notice-%%ID%%" id="wp-admin-notice-%%ID%%" class="notice" data-unsafe="&lt;script&gt;alert( &quot;Howdy, admin!&quot; );&lt;/script&gt;"><p>A notice with an additional attribute with an unsafe value.</p></div>',
 			),
 			'additional invalid attribute'              => array(
@@ -263,5 +268,5 @@
 					'attributes' => array( 'not-valid' => 'not-valid' ),
 				),
-				'expected' => '<div class="notice"><p>A notice with an additional attribute that is invalid.</p></div>',
+				'expected' => '<div data-id="wp-admin-notice-%%ID%%" id="wp-admin-notice-%%ID%%" class="notice"><p>A notice with an additional attribute that is invalid.</p></div>',
 			),
 			'multiple attributes with "role", invalid, data-*, numeric, and boolean' => array(
@@ -276,5 +281,5 @@
 					),
 				),
-				'expected' => '<div class="notice" role="alert" data-name="my-name" data-id="1" hidden><p>A notice with multiple attributes with "role", invalid, "data-*", numeric, and boolean.</p></div>',
+				'expected' => '<div id="wp-admin-notice-%%ID%%" class="notice" role="alert" data-name="my-name" data-id="1" hidden><p>A notice with multiple attributes with "role", invalid, "data-*", numeric, and boolean.</p></div>',
 			),
 			'paragraph wrapping as a falsy value rather than (bool) false' => array(
@@ -283,5 +288,5 @@
 					'paragraph_wrap' => 0,
 				),
-				'expected' => '<div class="notice"><p>A notice with paragraph wrapping as a falsy value rather than (bool) false.</p></div>',
+				'expected' => '<div data-id="wp-admin-notice-%%ID%%" id="wp-admin-notice-%%ID%%" class="notice"><p>A notice with paragraph wrapping as a falsy value rather than (bool) false.</p></div>',
 			),
 		);
@@ -296,4 +301,7 @@
 	 */
 	public function test_should_throw_doing_it_wrong_with_a_type_containing_spaces() {
+		$next_id = array_last( explode( '-', wp_unique_prefixed_id( 'wp-admin-notice-' ) ) );
+		++$next_id;
+
 		ob_start();
 		wp_admin_notice(
@@ -304,5 +312,5 @@
 
 		$this->assertSame(
-			'<div class="notice notice-first second third fourth"><p>A type containing spaces.</p></div>',
+			'<div class="notice notice-first second third fourth" data-id="wp-admin-notice-' . $next_id . '" id="wp-admin-notice-' . $next_id . '"><p>A type containing spaces.</p></div>',
 			$actual
 		);
Index: /trunk/tests/phpunit/tests/functions/wpGetAdminNotice.php
===================================================================
--- /trunk/tests/phpunit/tests/functions/wpGetAdminNotice.php	(revision 64281)
+++ /trunk/tests/phpunit/tests/functions/wpGetAdminNotice.php	(revision 64282)
@@ -22,4 +22,9 @@
 	 */
 	public function test_should_return_admin_notice( $message, $args, $expected ) {
+		// Replace placeholder %%ID%% in expected string, required for test isolation.
+		$next_id = array_last( explode( '-', wp_unique_prefixed_id( 'wp-admin-notice-' ) ) );
+		++$next_id;
+		$expected = str_replace( '%%ID%%', $next_id, $expected );
+
 		$this->assertEqualHTML( $expected, wp_get_admin_notice( $message, $args ) );
 	}
@@ -35,5 +40,5 @@
 				'message'  => 'A notice with defaults.',
 				'args'     => array(),
-				'expected' => '<div class="notice"><p>A notice with defaults.</p></div>',
+				'expected' => '<div data-id="wp-admin-notice-%%ID%%" id="wp-admin-notice-%%ID%%" class="notice"><p>A notice with defaults.</p></div>',
 			),
 			'an empty message (used for templates)'     => array(
@@ -45,5 +50,5 @@
 					'additional_classes' => array( 'inline', 'hidden' ),
 				),
-				'expected' => '<div id="message" class="notice notice-error is-dismissible inline hidden"><p></p></div>',
+				'expected' => '<div data-id="wp-admin-notice-%%ID%%" id="message" class="notice notice-error is-dismissible inline hidden"><p></p></div>',
 			),
 			'an empty message (used for templates) without paragraph wrapping' => array(
@@ -56,5 +61,5 @@
 					'paragraph_wrap'     => false,
 				),
-				'expected' => '<div id="message" class="notice notice-error is-dismissible inline hidden"></div>',
+				'expected' => '<div data-id="wp-admin-notice-%%ID%%" id="message" class="notice notice-error is-dismissible inline hidden"></div>',
 			),
 			'an "error" notice'                         => array(
@@ -63,5 +68,5 @@
 					'type' => 'error',
 				),
-				'expected' => '<div class="notice notice-error"><p>An "error" notice.</p></div>',
+				'expected' => '<div data-id="wp-admin-notice-%%ID%%" id="wp-admin-notice-%%ID%%" class="notice notice-error"><p>An "error" notice.</p></div>',
 			),
 			'a "success" notice'                        => array(
@@ -70,5 +75,5 @@
 					'type' => 'success',
 				),
-				'expected' => '<div class="notice notice-success"><p>A "success" notice.</p></div>',
+				'expected' => '<div data-id="wp-admin-notice-%%ID%%" id="wp-admin-notice-%%ID%%" class="notice notice-success"><p>A "success" notice.</p></div>',
 			),
 			'a "warning" notice'                        => array(
@@ -77,5 +82,5 @@
 					'type' => 'warning',
 				),
-				'expected' => '<div class="notice notice-warning"><p>A "warning" notice.</p></div>',
+				'expected' => '<div data-id="wp-admin-notice-%%ID%%" id="wp-admin-notice-%%ID%%" class="notice notice-warning"><p>A "warning" notice.</p></div>',
 			),
 			'an "info" notice'                          => array(
@@ -84,5 +89,5 @@
 					'type' => 'info',
 				),
-				'expected' => '<div class="notice notice-info"><p>An "info" notice.</p></div>',
+				'expected' => '<div data-id="wp-admin-notice-%%ID%%" id="wp-admin-notice-%%ID%%" class="notice notice-info"><p>An "info" notice.</p></div>',
 			),
 			'a type that already starts with "notice-"' => array(
@@ -91,5 +96,5 @@
 					'type' => 'notice-info',
 				),
-				'expected' => '<div class="notice notice-notice-info"><p>A type that already starts with "notice-".</p></div>',
+				'expected' => '<div data-id="wp-admin-notice-%%ID%%" id="wp-admin-notice-%%ID%%" class="notice notice-notice-info"><p>A type that already starts with "notice-".</p></div>',
 			),
 			'a dismissible notice'                      => array(
@@ -98,5 +103,5 @@
 					'dismissible' => true,
 				),
-				'expected' => '<div class="notice is-dismissible"><p>A dismissible notice.</p></div>',
+				'expected' => '<div data-id="wp-admin-notice-%%ID%%" id="wp-admin-notice-%%ID%%" class="notice is-dismissible"><p>A dismissible notice.</p></div>',
 			),
 			'no type and an ID'                         => array(
@@ -105,5 +110,5 @@
 					'id' => 'message',
 				),
-				'expected' => '<div id="message" class="notice"><p>A notice with an ID.</p></div>',
+				'expected' => '<div data-id="wp-admin-notice-%%ID%%" id="message" class="notice"><p>A notice with an ID.</p></div>',
 			),
 			'a type and an ID'                          => array(
@@ -113,5 +118,5 @@
 					'id'   => 'message',
 				),
-				'expected' => '<div id="message" class="notice notice-warning"><p>A warning notice with an ID.</p></div>',
+				'expected' => '<div data-id="wp-admin-notice-%%ID%%" id="message" class="notice notice-warning"><p>A warning notice with an ID.</p></div>',
 			),
 			'no type and additional classes'            => array(
@@ -120,5 +125,5 @@
 					'additional_classes' => array( 'error', 'notice-alt' ),
 				),
-				'expected' => '<div class="notice error notice-alt"><p>A notice with additional classes.</p></div>',
+				'expected' => '<div data-id="wp-admin-notice-%%ID%%" id="wp-admin-notice-%%ID%%" class="notice error notice-alt"><p>A notice with additional classes.</p></div>',
 			),
 			'a type and additional classes'             => array(
@@ -128,5 +133,5 @@
 					'additional_classes' => array( 'error', 'notice-alt' ),
 				),
-				'expected' => '<div class="notice notice-warning error notice-alt"><p>A warning notice with additional classes.</p></div>',
+				'expected' => '<div data-id="wp-admin-notice-%%ID%%" id="wp-admin-notice-%%ID%%" class="notice notice-warning error notice-alt"><p>A warning notice with additional classes.</p></div>',
 			),
 			'a dismissible notice with a type and additional classes' => array(
@@ -137,5 +142,5 @@
 					'additional_classes' => array( 'error', 'notice-alt' ),
 				),
-				'expected' => '<div class="notice notice-warning is-dismissible error notice-alt"><p>A dismissible warning notice with a type and additional classes.</p></div>',
+				'expected' => '<div data-id="wp-admin-notice-%%ID%%" id="wp-admin-notice-%%ID%%" class="notice notice-warning is-dismissible error notice-alt"><p>A dismissible warning notice with a type and additional classes.</p></div>',
 			),
 			'a notice without paragraph wrapping'       => array(
@@ -144,5 +149,5 @@
 					'paragraph_wrap' => false,
 				),
-				'expected' => '<div class="notice"><span>A notice without paragraph wrapping.</span></div>',
+				'expected' => '<div data-id="wp-admin-notice-%%ID%%" id="wp-admin-notice-%%ID%%" class="notice"><span>A notice without paragraph wrapping.</span></div>',
 			),
 			'an unsafe type'                            => array(
@@ -151,5 +156,5 @@
 					'type' => '"><script>alert("Howdy,admin!");</script>',
 				),
-				'expected' => '<div class="notice notice-&quot;><script>alert(&quot;Howdy,admin!&quot;);</script>"><p>A notice with an unsafe type.</p></div>',
+				'expected' => '<div data-id="wp-admin-notice-%%ID%%" id="wp-admin-notice-%%ID%%" class="notice notice-&quot;><script>alert(&quot;Howdy,admin!&quot;);</script>"><p>A notice with an unsafe type.</p></div>',
 			),
 			'an unsafe ID'                              => array(
@@ -158,5 +163,5 @@
 					'id' => '"><script>alert( "Howdy, admin!" );</script> <div class="notice',
 				),
-				'expected' => '<div id="&quot;><script>alert( &quot;Howdy, admin!&quot; );</script> <div class=&quot;notice" class="notice"><p>A notice with an unsafe ID.</p></div>',
+				'expected' => '<div data-id="wp-admin-notice-%%ID%%" id="&quot;><script>alert( &quot;Howdy, admin!&quot; );</script> <div class=&quot;notice" class="notice"><p>A notice with an unsafe ID.</p></div>',
 			),
 			'unsafe additional classes'                 => array(
@@ -165,5 +170,5 @@
 					'additional_classes' => array( '"><script>alert( "Howdy, admin!" );</script> <div class="notice' ),
 				),
-				'expected' => '<div class="notice &quot;><script>alert( &quot;Howdy, admin!&quot; );</script> <div class=&quot;notice"><p>A notice with unsafe additional classes.</p></div>',
+				'expected' => '<div data-id="wp-admin-notice-%%ID%%" id="wp-admin-notice-%%ID%%" class="notice &quot;><script>alert( &quot;Howdy, admin!&quot; );</script> <div class=&quot;notice"><p>A notice with unsafe additional classes.</p></div>',
 			),
 			'a type that is not a string'               => array(
@@ -172,5 +177,5 @@
 					'type' => array(),
 				),
-				'expected' => '<div class="notice"><p>A notice with a type that is not a string.</p></div>',
+				'expected' => '<div data-id="wp-admin-notice-%%ID%%" id="wp-admin-notice-%%ID%%" class="notice"><p>A notice with a type that is not a string.</p></div>',
 			),
 			'a type with only empty space'              => array(
@@ -179,5 +184,5 @@
 					'type' => " \t\r\n",
 				),
-				'expected' => '<div class="notice"><p>A notice with a type with only empty space.</p></div>',
+				'expected' => '<div data-id="wp-admin-notice-%%ID%%" id="wp-admin-notice-%%ID%%" class="notice"><p>A notice with a type with only empty space.</p></div>',
 			),
 			'an ID that is not a string'                => array(
@@ -186,5 +191,5 @@
 					'id' => array( 'message' ),
 				),
-				'expected' => '<div class="notice"><p>A notice with an ID that is not a string.</p></div>',
+				'expected' => '<div data-id="wp-admin-notice-%%ID%%" id="wp-admin-notice-%%ID%%" class="notice"><p>A notice with an ID that is not a string.</p></div>',
 			),
 			'an ID with only empty space'               => array(
@@ -193,5 +198,5 @@
 					'id' => " \t\r\n",
 				),
-				'expected' => '<div class="notice"><p>A notice with an ID with only empty space.</p></div>',
+				'expected' => '<div data-id="wp-admin-notice-%%ID%%" id="wp-admin-notice-%%ID%%" class="notice"><p>A notice with an ID with only empty space.</p></div>',
 			),
 			'dismissible as a truthy value rather than (bool) true' => array(
@@ -200,5 +205,5 @@
 					'dismissible' => 1,
 				),
-				'expected' => '<div class="notice"><p>A notice with dismissible as a truthy value rather than (bool) true.</p></div>',
+				'expected' => '<div data-id="wp-admin-notice-%%ID%%" id="wp-admin-notice-%%ID%%" class="notice"><p>A notice with dismissible as a truthy value rather than (bool) true.</p></div>',
 			),
 			'additional classes that are not an array'  => array(
@@ -207,5 +212,5 @@
 					'additional_classes' => 'class-1 class-2 class-3',
 				),
-				'expected' => '<div class="notice"><p>A notice with additional classes that are not an array.</p></div>',
+				'expected' => '<div data-id="wp-admin-notice-%%ID%%" id="wp-admin-notice-%%ID%%" class="notice"><p>A notice with additional classes that are not an array.</p></div>',
 			),
 			'additional attribute with a value'         => array(
@@ -214,5 +219,5 @@
 					'attributes' => array( 'aria-live' => 'assertive' ),
 				),
-				'expected' => '<div class="notice" aria-live="assertive"><p>A notice with an additional attribute with a value.</p></div>',
+				'expected' => '<div data-id="wp-admin-notice-%%ID%%" id="wp-admin-notice-%%ID%%" class="notice" aria-live="assertive"><p>A notice with an additional attribute with a value.</p></div>',
 			),
 			'additional hidden attribute'               => array(
@@ -221,5 +226,5 @@
 					'attributes' => array( 'hidden' => true ),
 				),
-				'expected' => '<div class="notice" hidden><p>A notice with the hidden attribute.</p></div>',
+				'expected' => '<div data-id="wp-admin-notice-%%ID%%" id="wp-admin-notice-%%ID%%" class="notice" hidden><p>A notice with the hidden attribute.</p></div>',
 			),
 			'additional attribute no associative keys'  => array(
@@ -228,5 +233,5 @@
 					'attributes' => array( 'hidden' ),
 				),
-				'expected' => '<div class="notice" hidden><p>A notice with a boolean attribute without an associative key.</p></div>',
+				'expected' => '<div data-id="wp-admin-notice-%%ID%%" id="wp-admin-notice-%%ID%%" class="notice" hidden><p>A notice with a boolean attribute without an associative key.</p></div>',
 			),
 			'additional attribute with role'            => array(
@@ -235,5 +240,5 @@
 					'attributes' => array( 'role' => 'alert' ),
 				),
-				'expected' => '<div class="notice" role="alert"><p>A notice with an additional attribute role.</p></div>',
+				'expected' => '<div data-id="wp-admin-notice-%%ID%%" id="wp-admin-notice-%%ID%%" class="notice" role="alert"><p>A notice with an additional attribute role.</p></div>',
 			),
 			'multiple additional attributes'            => array(
@@ -245,5 +250,5 @@
 					),
 				),
-				'expected' => '<div class="notice" role="alert" data-test="-1"><p>A notice with multiple additional attributes.</p></div>',
+				'expected' => '<div data-id="wp-admin-notice-%%ID%%" id="wp-admin-notice-%%ID%%" class="notice" role="alert" data-test="-1"><p>A notice with multiple additional attributes.</p></div>',
 			),
 			'data attribute with unsafe value'          => array(
@@ -252,5 +257,5 @@
 					'attributes' => array( 'data-unsafe' => '<script>alert( "Howdy, admin!" );</script>' ),
 				),
-				'expected' => '<div class="notice" data-unsafe="&lt;script&gt;alert( &quot;Howdy, admin!&quot; );&lt;/script&gt;"><p>A notice with an additional attribute with an unsafe value.</p></div>',
+				'expected' => '<div data-id="wp-admin-notice-%%ID%%" id="wp-admin-notice-%%ID%%" class="notice" data-unsafe="&lt;script&gt;alert( &quot;Howdy, admin!&quot; );&lt;/script&gt;"><p>A notice with an additional attribute with an unsafe value.</p></div>',
 			),
 			'multiple attributes with "role", invalid, data-*, numeric, and boolean' => array(
@@ -265,5 +270,5 @@
 					),
 				),
-				'expected' => '<div class="notice" role="alert" disabled="disabled" data-name="my-name" data-id="1" hidden><p>A notice with multiple attributes with "role", invalid, "data-*", numeric, and boolean.</p></div>',
+				'expected' => '<div id="wp-admin-notice-%%ID%%" class="notice" role="alert" disabled="disabled" data-name="my-name" data-id="1" hidden><p>A notice with multiple attributes with "role", invalid, "data-*", numeric, and boolean.</p></div>',
 			),
 			'paragraph wrapping as a falsy value rather than (bool) false' => array(
@@ -272,5 +277,5 @@
 					'paragraph_wrap' => 0,
 				),
-				'expected' => '<div class="notice"><p>A notice with paragraph wrapping as a falsy value rather than (bool) false.</p></div>',
+				'expected' => '<div data-id="wp-admin-notice-%%ID%%" id="wp-admin-notice-%%ID%%" class="notice"><p>A notice with paragraph wrapping as a falsy value rather than (bool) false.</p></div>',
 			),
 		);
@@ -286,6 +291,12 @@
 	 */
 	public function test_should_throw_doing_it_wrong_with_a_type_containing_spaces() {
-		$this->assertSame(
-			'<div class="notice notice-first second third fourth"><p>A type containing spaces.</p></div>',
+		// Replace placeholder %%ID%% in expected string, required for test isolation.
+		$expected = '<div class="notice notice-first second third fourth" id="wp-admin-notice-%%ID%%" data-id="wp-admin-notice-%%ID%%"><p>A type containing spaces.</p></div>';
+		$next_id  = array_last( explode( '-', wp_unique_prefixed_id( 'wp-admin-notice-' ) ) );
+		++$next_id;
+		$expected = str_replace( '%%ID%%', $next_id, $expected );
+
+		$this->assertEqualHTML(
+			$expected,
 			wp_get_admin_notice(
 				'A type containing spaces.',
