diff --git a/packages/e2e-test-utils/README.md b/packages/e2e-test-utils/README.md
index d8b3263c2e6e9..3b11399259294 100644
--- a/packages/e2e-test-utils/README.md
+++ b/packages/e2e-test-utils/README.md
@@ -181,6 +181,18 @@ _Returns_
- `Promise`: Promise resolving once the edit post sidebar is opened.
+# **externalWrapperHasFocus**
+
+Asserts that the element with keyboard focus is a block's external wrapper
+
+_Parameters_
+
+- _blockType_ `string`: The expected value of the data-type attribute of the block's external wrapper
+
+_Returns_
+
+- `Promise`: A promise that's resolved when the active element is evaluated and asserted against the expected result.
+
# **findSidebarPanelToggleButtonWithTitle**
Finds a sidebar panel with the provided title.
@@ -259,6 +271,19 @@ _Returns_
- `Promise`: Promise resolving with a boolean.
+# **insertAndPopulateBlock**
+
+Inserts a content block and then, if it has text content areas, fills them with text
+
+_Parameters_
+
+- _blockName_ `string`: The type of block to insert
+- _content_ `string`: The text to enter into each contenteditable area
+
+_Returns_
+
+- `Promise`: A promise that resolves when all the blocks are inserted and filled with content.
+
# **insertBlock**
Opens the inserter, searches for the given term, then selects the first
@@ -327,6 +352,14 @@ _Returns_
- `Promise`: Promise that uses `mockCheck` to see if a request should be mocked with `mock`, and optionally transforms the response with `responseObjectTransform`.
+# **navigateToContentEditorTop**
+
+Navigates to the top of the content editor using the keyboard.
+
+_Returns_
+
+- `Promise`: A promise that's resolved when the browser has finished emulating the keyboard shortcut for focusing the top of the editor, and tabbed to the next focusable element.
+
# **observeFocusLoss**
Binds to the document on page load which throws an error if a `focusout`
@@ -477,6 +510,91 @@ running the test is not already the admin user).
Switches the current user to whichever user we should be
running the tests as (if we're not already that user).
+# **tabThroughBlockControls**
+
+Tabs through a content block and asserts that the external wrapper, inserter toggle, mover controls, and toolbar buttons all receive keyboard focus.
+
+_Parameters_
+
+- _blockType_ `string`: The expected value of the data-type attribute of the block's external wrapper
+
+_Returns_
+
+- `Promise`: A promise that's resolved when the browser has finished tabbing through the major components of a common block.
+
+# **tabThroughBlockMovers**
+
+Navigates through the block mover control using the keyboard. Asserts that the 'move up' and 'move down' controls receive focus.
+
+_Returns_
+
+- `Promise`: A promise that's resolved when the browser has finished tabbing throught the block mover controls.
+
+# **tabThroughBlockToolbar**
+
+Navigate through a block's toolbar using the keyboard. Asserts that each button receives focus.
+
+_Returns_
+
+- `Promise`: A promise that resolves when it's finished tabbing through the buttons in a block's toolbar, asserting that each one received focus.
+
+# **tabThroughFileBlock**
+
+Tabs through a content block with file upload buttons, such as an Image, Gallery, Audio, or Cover block
+
+_Parameters_
+
+- _blockType_ `string`: The expected value of the data-type attribute of the block's external wrapper
+
+_Returns_
+
+- `Promise`: A promise that resolves when the browser has completed tabbing through the common block components, and the placeholder buttons that are unique to blocks with file-upload features.
+
+# **tabThroughPlaceholderButtons**
+
+Tabs through the file upload buttons that appear in a file content block's placeholder area
+
+_Returns_
+
+- `Promise`: A promise that resolves when the browser has completed tabbing through the placeholder buttons that are unique to blocks with file-upload features.
+
+# **tabThroughTextBlock**
+
+Tabs through a content block with text content areas, such as a Heading, Quote, or Paragraph block. Asserts that the text content areas all receive focus.
+
+_Parameters_
+
+- _blockType_ `string`: The expected value of the data-type attribute of the block's external wrapper
+- _content_ `string`: The expected title of the block
+
+_Returns_
+
+- `Promise`: A promise that resolves when the browser has completed tabbing through the focusable elements of a common block, and through the contenteditbable areas unique to text blocks.
+
+# **tabThroughTextContentAreas**
+
+Tabs through the text content areas of a block and asserts the expected values
+
+_Parameters_
+
+- _content_ `string`: The expected value of the block's contenteditable elements
+
+_Returns_
+
+- `Promise`: A promise that's resolved when the browser has finished tabbing throught the contenteditable areas of a block, and asserting they have keyboard focus and the expected content.
+
+# **textContentAreas**
+
+Returns a list of a block's contenteditable elements.
+
+_Parameters_
+
+- _empty_ `boolean`: When true, restricts the list to contenteditable elements with no value
+
+_Returns_
+
+- `Promise`: A promise that resolves when it's returned an array of classes representing the contenteditable areas of a block with keyboard focus.
+
# **toggleMoreMenu**
Toggles the More Menu.
diff --git a/packages/e2e-test-utils/src/external-wrapper-has-focus.js b/packages/e2e-test-utils/src/external-wrapper-has-focus.js
new file mode 100644
index 0000000000000..9660b3affc3f9
--- /dev/null
+++ b/packages/e2e-test-utils/src/external-wrapper-has-focus.js
@@ -0,0 +1,11 @@
+/**
+ * Asserts that the element with keyboard focus is a block's external wrapper
+ *
+ * @param {string} blockType The expected value of the data-type attribute of the block's external wrapper
+ * @return {Promise} A promise that's resolved when the active element is evaluated and asserted against the expected result.
+ */
+
+export async function externalWrapperHasFocus( blockType ) {
+ const activeElementDataType = await page.evaluate( () => document.activeElement.dataset.type );
+ expect( activeElementDataType ).toEqual( blockType );
+}
diff --git a/packages/e2e-test-utils/src/index.js b/packages/e2e-test-utils/src/index.js
index 8fcf2b400c585..51c7dd88e02dc 100644
--- a/packages/e2e-test-utils/src/index.js
+++ b/packages/e2e-test-utils/src/index.js
@@ -15,28 +15,39 @@ export { enableExperimentalFeatures } from './enable-experimental-features';
export { enablePageDialogAccept } from './enable-page-dialog-accept';
export { enablePrePublishChecks } from './enable-pre-publish-checks';
export { ensureSidebarOpened } from './ensure-sidebar-opened';
-export { findSidebarPanelToggleButtonWithTitle } from './find-sidebar-panel-toggle-button-with-title';
+export { externalWrapperHasFocus } from './external-wrapper-has-focus';
+export {
+ findSidebarPanelToggleButtonWithTitle,
+} from './find-sidebar-panel-toggle-button-with-title';
export { findSidebarPanelWithTitle } from './find-sidebar-panel-with-title';
-export { getAllBlockInserterItemTitles } from './get-all-block-inserter-item-titles';
+export {
+ getAllBlockInserterItemTitles,
+} from './get-all-block-inserter-item-titles';
export { getAllBlocks } from './get-all-blocks';
export { getAvailableBlockTransforms } from './get-available-block-transforms';
export { getBlockSetting } from './get-block-setting';
export { getEditedPostContent } from './get-edited-post-content';
export { hasBlockSwitcher } from './has-block-switcher';
+export { insertAndPopulateBlock } from './insert-and-populate-block';
export { insertBlock } from './insert-block';
export { installPlugin } from './install-plugin';
export { isCurrentURL } from './is-current-url';
export { isInDefaultBlock } from './is-in-default-block';
export { loginUser } from './login-user';
+export { navigateToContentEditorTop } from './navigate-to-content-editor-top';
export { observeFocusLoss } from './observe-focus-loss';
-export { openAllBlockInserterCategories } from './open-all-block-inserter-categories';
+export {
+ openAllBlockInserterCategories,
+} from './open-all-block-inserter-categories';
export { openDocumentSettingsSidebar } from './open-document-settings-sidebar';
export { openGlobalBlockInserter } from './open-global-block-inserter';
export { openPublishPanel } from './open-publish-panel';
export { pressKeyTimes } from './press-key-times';
export { pressKeyWithModifier } from './press-key-with-modifier';
export { publishPost } from './publish-post';
-export { publishPostWithPrePublishChecksDisabled } from './publish-post-with-pre-publish-checks-disabled';
+export {
+ publishPostWithPrePublishChecksDisabled,
+} from './publish-post-with-pre-publish-checks-disabled';
export { saveDraft } from './save-draft';
export { searchForBlock } from './search-for-block';
export { selectBlockByClientId } from './select-block-by-client-id';
@@ -46,6 +57,16 @@ export { switchEditorModeTo } from './switch-editor-mode-to';
export { disableNavigationMode } from './keyboard-mode';
export { switchUserToAdmin } from './switch-user-to-admin';
export { switchUserToTest } from './switch-user-to-test';
+export { tabThroughBlockControls } from './tab-through-block-controls';
+export { tabThroughBlockMovers } from './tab-through-block-movers';
+export { tabThroughBlockToolbar } from './tab-through-block-toolbar';
+export { tabThroughFileBlock } from './tab-through-file-block';
+export {
+ tabThroughPlaceholderButtons,
+} from './tab-through-placeholder-buttons';
+export { tabThroughTextBlock } from './tab-through-text-block';
+export { tabThroughTextContentAreas } from './tab-through-text-content-areas';
+export { textContentAreas } from './text-content-areas';
export { toggleMoreMenu } from './toggle-more-menu';
export { toggleOfflineMode, isOfflineMode } from './offline-mode';
export { toggleScreenOption } from './toggle-screen-option';
diff --git a/packages/e2e-test-utils/src/insert-and-populate-block.js b/packages/e2e-test-utils/src/insert-and-populate-block.js
new file mode 100644
index 0000000000000..606544c4fb8d1
--- /dev/null
+++ b/packages/e2e-test-utils/src/insert-and-populate-block.js
@@ -0,0 +1,27 @@
+/**
+ * Internal dependencies
+ */
+import { insertBlock } from './insert-block';
+import { textContentAreas } from './text-content-areas';
+
+/**
+ * Inserts a content block and then, if it has text content areas, fills them with text
+ *
+ * @param {string} blockName The type of block to insert
+ * @param {string} content The text to enter into each contenteditable area
+ * @return {Promise} A promise that resolves when all the blocks are inserted and filled with content.
+ */
+export async function insertAndPopulateBlock( blockName, content ) {
+ await insertBlock( blockName );
+ // typing populates the first content area
+ await page.keyboard.type( content );
+
+ // if there are more contenteditable elements, select and populate them too:
+ const blockContentAreas = await textContentAreas( { empty: true } );
+
+ for ( let i = 0; i < blockContentAreas.length; i++ ) {
+ await page.keyboard.press( 'Tab' );
+ await page.keyboard.type( content );
+ }
+ await page.keyboard.press( 'Enter' );
+}
diff --git a/packages/e2e-test-utils/src/navigate-to-content-editor-top.js b/packages/e2e-test-utils/src/navigate-to-content-editor-top.js
new file mode 100644
index 0000000000000..e5c94707c9c16
--- /dev/null
+++ b/packages/e2e-test-utils/src/navigate-to-content-editor-top.js
@@ -0,0 +1,18 @@
+/**
+ * Internal dependencies
+ */
+import { pressKeyWithModifier } from './press-key-with-modifier';
+
+/**
+ * Navigates to the top of the content editor using the keyboard.
+ *
+ * @return {Promise} A promise that's resolved when the browser has finished emulating the keyboard shortcut for focusing the top of the editor, and tabbed to the next focusable element.
+ */
+export async function navigateToContentEditorTop() {
+ // Use 'Ctrl+`' to return to the top of the editor
+ await pressKeyWithModifier( 'ctrl', '`' );
+ await pressKeyWithModifier( 'ctrl', '`' );
+
+ // Tab into the Title block
+ await page.keyboard.press( 'Tab' );
+}
diff --git a/packages/e2e-test-utils/src/tab-through-block-controls.js b/packages/e2e-test-utils/src/tab-through-block-controls.js
new file mode 100644
index 0000000000000..6117c764e7f4d
--- /dev/null
+++ b/packages/e2e-test-utils/src/tab-through-block-controls.js
@@ -0,0 +1,37 @@
+/**
+ * Internal dependencies
+ */
+import { externalWrapperHasFocus } from './external-wrapper-has-focus';
+import { tabThroughBlockMovers } from './tab-through-block-movers';
+import { tabThroughBlockToolbar } from './tab-through-block-toolbar';
+
+/**
+ * Asserts that a content block's inserter toggle has keyboard focus
+ *
+ * @return {Promise} A promise that's resolved when the active element is evaluated and asserted to have the inserter toggle's classname.
+ */
+
+const inserterToggleHasFocus = async function() {
+ const isFocusedInserterToggle = await page.evaluate( () => document.activeElement.classList.contains( 'block-editor-inserter__toggle' ) );
+ expect( isFocusedInserterToggle ).toBe( true );
+};
+
+/**
+ * Tabs through a content block and asserts that the external wrapper, inserter toggle, mover controls, and toolbar buttons all receive keyboard focus.
+ *
+ * @param {string} blockType The expected value of the data-type attribute of the block's external wrapper
+ * @return {Promise} A promise that's resolved when the browser has finished tabbing through the major components of a common block.
+ */
+
+export async function tabThroughBlockControls( blockType ) {
+ // Tab to the next block
+ await page.keyboard.press( 'Tab' );
+ await externalWrapperHasFocus( blockType );
+
+ // Tab causes 'add block' button to receive focus
+ await page.keyboard.press( 'Tab' );
+ await inserterToggleHasFocus();
+
+ await tabThroughBlockMovers();
+ await tabThroughBlockToolbar();
+}
diff --git a/packages/e2e-test-utils/src/tab-through-block-movers.js b/packages/e2e-test-utils/src/tab-through-block-movers.js
new file mode 100644
index 0000000000000..1000dc372a94b
--- /dev/null
+++ b/packages/e2e-test-utils/src/tab-through-block-movers.js
@@ -0,0 +1,21 @@
+/**
+ * Navigates through the block mover control using the keyboard. Asserts that the 'move up' and 'move down' controls receive focus.
+ *
+ * @return {Promise} A promise that's resolved when the browser has finished tabbing throught the block mover controls.
+ */
+
+export async function tabThroughBlockMovers() {
+ // Tab to focus on the 'move up' control
+ await page.keyboard.press( 'Tab' );
+ const isFocusedMoveUpControl = await page.evaluate( () =>
+ document.activeElement.classList.contains( 'block-editor-block-mover__control' )
+ );
+ expect( isFocusedMoveUpControl ).toBe( true );
+
+ // Tab to focus on the 'move down' control
+ await page.keyboard.press( 'Tab' );
+ const isFocusedMoveDownControl = await page.evaluate( () =>
+ document.activeElement.classList.contains( 'block-editor-block-mover__control' )
+ );
+ expect( isFocusedMoveDownControl ).toBe( true );
+}
diff --git a/packages/e2e-test-utils/src/tab-through-block-toolbar.js b/packages/e2e-test-utils/src/tab-through-block-toolbar.js
new file mode 100644
index 0000000000000..85a811745a5ff
--- /dev/null
+++ b/packages/e2e-test-utils/src/tab-through-block-toolbar.js
@@ -0,0 +1,18 @@
+/**
+ * Navigate through a block's toolbar using the keyboard. Asserts that each button receives focus.
+ *
+ * @return {Promise} A promise that resolves when it's finished tabbing through the buttons in a block's toolbar, asserting that each one received focus.
+ */
+
+export async function tabThroughBlockToolbar() {
+ const blockToolbarButtons = await page.$$eval( '.block-editor-block-contextual-toolbar button:not([disabled])',
+ ( elements ) => elements.map( ( elem ) => elem.className ) );
+
+ for ( const buttonClassName of blockToolbarButtons ) {
+ await page.keyboard.press( 'Tab' );
+ const focusedBlockToolBarButton = await page.evaluate( () =>
+ document.activeElement.className
+ );
+ expect( focusedBlockToolBarButton ).toEqual( buttonClassName );
+ }
+}
diff --git a/packages/e2e-test-utils/src/tab-through-file-block.js b/packages/e2e-test-utils/src/tab-through-file-block.js
new file mode 100644
index 0000000000000..1f309e019643d
--- /dev/null
+++ b/packages/e2e-test-utils/src/tab-through-file-block.js
@@ -0,0 +1,18 @@
+
+/**
+ * Internal dependencies
+ */
+
+import { tabThroughBlockControls } from './tab-through-block-controls';
+import { tabThroughPlaceholderButtons } from './tab-through-placeholder-buttons';
+
+/**
+ * Tabs through a content block with file upload buttons, such as an Image, Gallery, Audio, or Cover block
+ *
+ * @param {string} blockType The expected value of the data-type attribute of the block's external wrapper
+ * @return {Promise} A promise that resolves when the browser has completed tabbing through the common block components, and the placeholder buttons that are unique to blocks with file-upload features.
+ */
+export async function tabThroughFileBlock( blockType ) {
+ await tabThroughBlockControls( blockType );
+ await tabThroughPlaceholderButtons();
+}
diff --git a/packages/e2e-test-utils/src/tab-through-placeholder-buttons.js b/packages/e2e-test-utils/src/tab-through-placeholder-buttons.js
new file mode 100644
index 0000000000000..59894cd3a7523
--- /dev/null
+++ b/packages/e2e-test-utils/src/tab-through-placeholder-buttons.js
@@ -0,0 +1,17 @@
+/**
+ * Tabs through the file upload buttons that appear in a file content block's placeholder area
+ *
+ * @return {Promise} A promise that resolves when the browser has completed tabbing through the placeholder buttons that are unique to blocks with file-upload features.
+ */
+export const tabThroughPlaceholderButtons = async () => {
+ const placeholderButtons = await page.$$eval( '.wp-block.is-selected .block-editor-media-placeholder button:not([disabled])',
+ ( elements ) => elements.map( ( elem ) => elem.className ) );
+
+ for ( const buttonClassName of placeholderButtons ) {
+ await page.keyboard.press( 'Tab' );
+ const focusedPlaceholderButton = await page.evaluate( () =>
+ document.activeElement.className
+ );
+ expect( focusedPlaceholderButton ).toEqual( buttonClassName );
+ }
+};
diff --git a/packages/e2e-test-utils/src/tab-through-text-block.js b/packages/e2e-test-utils/src/tab-through-text-block.js
new file mode 100644
index 0000000000000..97f7ab84bc584
--- /dev/null
+++ b/packages/e2e-test-utils/src/tab-through-text-block.js
@@ -0,0 +1,21 @@
+/**
+ * Internal dependencies
+ */
+import { tabThroughBlockControls } from './tab-through-block-controls';
+import { tabThroughTextContentAreas } from './tab-through-text-content-areas';
+
+/**
+ * Tabs through a content block with text content areas, such as a Heading, Quote, or Paragraph block. Asserts that the text content areas all receive focus.
+ *
+ * @param {string} blockType The expected value of the data-type attribute of the block's external wrapper
+ * @param {string} content The expected title of the block
+ * @return {Promise} A promise that resolves when the browser has completed tabbing through the focusable elements of a common block, and through the contenteditbable areas unique to text blocks.
+ */
+
+export async function tabThroughTextBlock( blockType, content ) {
+ await tabThroughBlockControls( blockType );
+
+ // Tab causes the block text content to receive focus
+ await page.keyboard.press( 'Tab' );
+ await tabThroughTextContentAreas( content );
+}
diff --git a/packages/e2e-test-utils/src/tab-through-text-content-areas.js b/packages/e2e-test-utils/src/tab-through-text-content-areas.js
new file mode 100644
index 0000000000000..b7c59a8ea6e3a
--- /dev/null
+++ b/packages/e2e-test-utils/src/tab-through-text-content-areas.js
@@ -0,0 +1,27 @@
+/**
+ * Internal dependencies
+ */
+import { textContentAreas } from './text-content-areas';
+
+/**
+ * Tabs through the text content areas of a block and asserts the expected values
+ *
+ * @param {string} content The expected value of the block's contenteditable elements
+ * @return {Promise} A promise that's resolved when the browser has finished tabbing throught the contenteditable areas of a block, and asserting they have keyboard focus and the expected content.
+ */
+
+export async function tabThroughTextContentAreas( content ) {
+ const blocks = await textContentAreas( { empty: false } );
+
+ for ( let i = 0; i < blocks.length; i++ ) {
+ if ( i > 0 ) {
+ await page.keyboard.press( 'Tab' );
+ }
+ const isFocusedTextContentArea = await page.evaluate( () => document.activeElement.contentEditable );
+ const textContentAreaContent = await page.evaluate( () => document.activeElement.innerHTML );
+
+ // The value of 'contentEditable' should be the string 'true'
+ expect( isFocusedTextContentArea ).toBe( 'true' );
+ expect( textContentAreaContent ).toContain( content );
+ }
+}
diff --git a/packages/e2e-test-utils/src/text-content-areas.js b/packages/e2e-test-utils/src/text-content-areas.js
new file mode 100644
index 0000000000000..0be764ca3226f
--- /dev/null
+++ b/packages/e2e-test-utils/src/text-content-areas.js
@@ -0,0 +1,17 @@
+/**
+ * Returns a list of a block's contenteditable elements.
+ *
+ * @param {boolean} empty When true, restricts the list to contenteditable elements with no value
+ * @return {Promise} A promise that resolves when it's returned an array of classes representing the contenteditable areas of a block with keyboard focus.
+ */
+export async function textContentAreas( { empty = false } ) {
+ const selectors = [
+ '.wp-block.is-selected [contenteditable]',
+ '.wp-block.is-typing [contenteditable]',
+ ].map( ( selector ) => {
+ return empty ? selector + '[data-is-placeholder-visible="true"]' : selector;
+ }, empty ).join( ',' );
+
+ return await page.$$eval( selectors,
+ ( elements ) => elements.map( ( elem ) => elem.className ) );
+}
diff --git a/packages/e2e-tests/specs/editor/various/keyboard-navigable-blocks.test.js b/packages/e2e-tests/specs/editor/various/keyboard-navigable-blocks.test.js
index a8ebe96c34fcd..01fb5f19a0cd3 100644
--- a/packages/e2e-tests/specs/editor/various/keyboard-navigable-blocks.test.js
+++ b/packages/e2e-tests/specs/editor/various/keyboard-navigable-blocks.test.js
@@ -3,121 +3,102 @@
*/
import {
createNewPost,
- insertBlock,
- pressKeyWithModifier,
+ insertAndPopulateBlock,
+ navigateToContentEditorTop,
+ tabThroughTextBlock,
+ tabThroughFileBlock,
} from '@wordpress/e2e-test-utils';
-const navigateToContentEditorTop = async () => {
- // Use 'Ctrl+`' to return to the top of the editor
- await pressKeyWithModifier( 'ctrl', '`' );
- await pressKeyWithModifier( 'ctrl', '`' );
-
- // Tab into the Title block
- await page.keyboard.press( 'Tab' );
-};
-
-const tabThroughParagraphBlock = async ( paragraphText ) => {
- // Tab to the next paragraph block
- await page.keyboard.press( 'Tab' );
-
- // The block external focusable wrapper has focus
- const isFocusedParagraphBlock = await page.evaluate(
- () => document.activeElement.dataset.type
- );
- await expect( isFocusedParagraphBlock ).toEqual( 'core/paragraph' );
-
- // Tab causes 'add block' button to receive focus
- await page.keyboard.press( 'Tab' );
- const isFocusedParagraphInserterToggle = await page.evaluate( () =>
- document.activeElement.classList.contains( 'block-editor-inserter__toggle' )
- );
- await expect( isFocusedParagraphInserterToggle ).toBe( true );
-
- await tabThroughBlockMoverControl();
- await tabThroughBlockToolbar();
-
- // Tab causes the paragraph content to receive focus
- await page.keyboard.press( 'Tab' );
- const isFocusedParagraphContent = await page.evaluate(
- () => document.activeElement.contentEditable
- );
- // The value of 'contentEditable' should be the string 'true'
- await expect( isFocusedParagraphContent ).toBe( 'true' );
-
- const paragraphEditableContent = await page.evaluate(
- () => document.activeElement.innerHTML
- );
- await expect( paragraphEditableContent ).toBe( paragraphText );
-};
-
-const tabThroughBlockMoverControl = async () => {
- // Tab to focus on the 'move up' control
- await page.keyboard.press( 'Tab' );
- const isFocusedMoveUpControl = await page.evaluate( () =>
- document.activeElement.classList.contains( 'block-editor-block-mover__control' )
- );
- await expect( isFocusedMoveUpControl ).toBe( true );
-
- // Tab to focus on the 'move down' control
- await page.keyboard.press( 'Tab' );
- const isFocusedMoveDownControl = await page.evaluate( () =>
- document.activeElement.classList.contains( 'block-editor-block-mover__control' )
- );
- await expect( isFocusedMoveDownControl ).toBe( true );
-};
-
-const tabThroughBlockToolbar = async () => {
- // Tab to focus on the 'block switcher' control
- await page.keyboard.press( 'Tab' );
- const isFocusedBlockSwitcherControl = await page.evaluate( () =>
- document.activeElement.classList.contains(
- 'block-editor-block-switcher__toggle'
- )
- );
- await expect( isFocusedBlockSwitcherControl ).toBe( true );
-
- // Tab to focus on the 'Change text alignment' dropdown control
- await page.keyboard.press( 'Tab' );
- const isFocusedChangeTextAlignmentControl = await page.evaluate( () =>
- document.activeElement.classList.contains( 'components-dropdown-menu__toggle' )
- );
- await expect( isFocusedChangeTextAlignmentControl ).toBe( true );
-
- // Tab to focus on the 'More formatting' dropdown toggle
- await page.keyboard.press( 'Tab' );
- const isFocusedBlockSettingsDropdown = await page.evaluate( () =>
- document.activeElement.classList.contains( 'components-dropdown-menu__toggle' )
- );
- await expect( isFocusedBlockSettingsDropdown ).toBe( true );
-};
-
describe( 'Order of block keyboard navigation', () => {
beforeEach( async () => {
await createNewPost();
} );
+ it( 'permits tabbing through heading blocks in the expected order', async () => {
+ await insertAndPopulateBlock( 'Heading', 'Heading Block Content' );
+
+ await navigateToContentEditorTop();
+
+ await tabThroughTextBlock( 'core/heading', 'Heading Block Content' );
+ } );
+
+ it( 'permits tabbing through quote blocks in the expected order', async () => {
+ await insertAndPopulateBlock( 'Quote', 'Quote Block Content' );
+ // in order to see the block mover controls, we must insert more than one of these blocks:
+ await insertAndPopulateBlock( 'Quote', 'Quote Block 2 Content' );
+
+ await navigateToContentEditorTop();
+
+ await tabThroughTextBlock( 'core/quote', 'Quote Block Content' );
+ } );
+
+ it( 'permits tabbing through list blocks in the expected order', async () => {
+ await insertAndPopulateBlock( 'List', 'List Block Content' );
+ // in order to see the block mover controls, we must insert more than one of these blocks:
+ await insertAndPopulateBlock( 'List', 'List Block 2 Content' );
+
+ await navigateToContentEditorTop();
+
+ await tabThroughTextBlock( 'core/list', 'List Block Content' );
+ } );
+
it( 'permits tabbing through paragraph blocks in the expected order', async () => {
- const paragraphBlocks = [ 'Paragraph 0', 'Paragraph 1', 'Paragraph 2' ];
+ await insertAndPopulateBlock( 'Paragraph', 'Paragraph Block Content' );
+ // in order to see the block mover controls, we must insert more than one of these blocks:
+ await insertAndPopulateBlock( 'Paragraph', 'Paragraph Block 2 Content' );
+
+ await navigateToContentEditorTop();
- // create 3 paragraphs blocks with some content
- for ( const paragraphBlock of paragraphBlocks ) {
- await insertBlock( 'Paragraph' );
- await page.keyboard.type( paragraphBlock );
- await page.keyboard.press( 'Enter' );
- }
+ await tabThroughTextBlock( 'core/paragraph', 'Paragraph Block Content' );
+ } );
+
+ it( 'permits tabbing through image blocks in the expected order', async () => {
+ await insertAndPopulateBlock( 'Image', 'Image Block Content' );
+ // in order to see the block mover controls, we must insert more than one of these blocks:
+ await insertAndPopulateBlock( 'Image', 'Image Block 2 Content' );
+
+ await navigateToContentEditorTop();
+
+ await tabThroughFileBlock( 'core/image' );
+ } );
+
+ it( 'permits tabbing through gallery blocks in the expected order', async () => {
+ await insertAndPopulateBlock( 'Gallery', 'Gallery Block Content' );
+ // in order to see the block mover controls, we must insert more than one of these blocks:
+ await insertAndPopulateBlock( 'Gallery', 'Gallery Block 2 Content' );
+
+ await navigateToContentEditorTop();
+
+ await tabThroughFileBlock( 'core/gallery' );
+ } );
+
+ it( 'permits tabbing through audio blocks in the expected order', async () => {
+ await insertAndPopulateBlock( 'Audio', 'Audio Block Content' );
+ // in order to see the block mover controls, we must insert more than one of these blocks:
+ await insertAndPopulateBlock( 'Audio', 'Audio Block 2 Content' );
await navigateToContentEditorTop();
- for ( const paragraphBlock of paragraphBlocks ) {
- await tabThroughParagraphBlock( paragraphBlock );
- }
+ await tabThroughFileBlock( 'core/audio' );
+ } );
+
+ it( 'permits tabbing through cover blocks in the expected order', async () => {
+ await insertAndPopulateBlock( 'Cover', 'Cover Block 2 Content' );
+ // in order to see the block mover controls, we must insert more than one of these blocks:
+ await insertAndPopulateBlock( 'Cover', 'Cover Block Content' );
+
+ await navigateToContentEditorTop();
+
+ await tabThroughFileBlock( 'core/cover' );
+ } );
+
+ it( 'permits tabbing through file blocks in the expected order', async () => {
+ await insertAndPopulateBlock( 'File', 'File Block Content' );
+ // in order to see the block mover controls, we must insert more than one of these blocks:
+ await insertAndPopulateBlock( 'File', 'File Block 2 Content' );
- // Repeat the same steps to ensure that there is no change introduced in how the focus is handled.
- // This prevents the previous regression explained in: https://github.com/WordPress/gutenberg/issues/11773.
await navigateToContentEditorTop();
- for ( const paragraphBlock of paragraphBlocks ) {
- await tabThroughParagraphBlock( paragraphBlock );
- }
+ await tabThroughFileBlock( 'core/file' );
} );
} );
diff --git a/packages/e2e-tests/specs/keyboard-navigable-content-editor.test.js b/packages/e2e-tests/specs/keyboard-navigable-content-editor.test.js
new file mode 100644
index 0000000000000..d1003634417b0
--- /dev/null
+++ b/packages/e2e-tests/specs/keyboard-navigable-content-editor.test.js
@@ -0,0 +1,42 @@
+/**
+ * WordPress dependencies
+ */
+import {
+ createNewPost,
+ insertAndPopulateBlock,
+ navigateToContentEditorTop,
+ tabThroughTextBlock,
+} from '@wordpress/e2e-test-utils';
+
+/**
+ * External dependencies
+ */
+
+describe( 'Order of the block editor keyboard navigation', () => {
+ beforeEach( async () => {
+ await createNewPost();
+ } );
+
+ it( 'permits tabbing through paragraph blocks and the top of the content in the expected order', async () => {
+ const paragraphBlocks = [ 'Paragraph 0', 'Paragraph 1', 'Paragraph 2' ];
+
+ // create 3 paragraphs blocks with some content
+ for ( const paragraphBlock of paragraphBlocks ) {
+ await insertAndPopulateBlock( 'Paragraph', paragraphBlock );
+ }
+
+ await navigateToContentEditorTop();
+
+ for ( const paragraphBlock of paragraphBlocks ) {
+ await tabThroughTextBlock( 'core/paragraph', paragraphBlock );
+ }
+
+ // Repeat the same steps to ensure that there is no change introduced in how the focus is handled.
+ // This prevents the previous regression explained in: https://github.com/WordPress/gutenberg/issues/11773.
+ await navigateToContentEditorTop();
+
+ for ( const paragraphBlock of paragraphBlocks ) {
+ await tabThroughTextBlock( 'core/paragraph', paragraphBlock );
+ }
+ } );
+} );