@wordpress/e2e-tests 2.4.1-next.253d9b6e21.0 → 2.5.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (49) hide show
  1. package/CHANGELOG.md +6 -0
  2. package/README.md +62 -0
  3. package/config/flaky-tests-reporter.js +94 -0
  4. package/config/setup-test-framework.js +10 -0
  5. package/jest.config.js +11 -1
  6. package/package.json +8 -7
  7. package/plugins/class-test-widget.php +5 -2
  8. package/plugins/iframed-block/block.json +16 -0
  9. package/plugins/iframed-block/editor.css +6 -0
  10. package/plugins/iframed-block/editor.js +18 -0
  11. package/plugins/iframed-block/jquery.test.js +7 -0
  12. package/plugins/iframed-block/script.js +7 -0
  13. package/plugins/iframed-block/style.css +9 -0
  14. package/plugins/iframed-block.php +46 -0
  15. package/specs/editor/blocks/buttons.test.js +30 -0
  16. package/specs/editor/blocks/post-title.test.js +1 -1
  17. package/specs/editor/plugins/__snapshots__/iframed-block.test.js.snap +7 -0
  18. package/specs/editor/plugins/align-hook.test.js +116 -105
  19. package/specs/editor/plugins/annotations.test.js +3 -3
  20. package/specs/editor/plugins/iframed-block.test.js +58 -0
  21. package/specs/editor/various/__snapshots__/copy-cut-paste-whole-blocks.test.js.snap +28 -0
  22. package/specs/editor/various/__snapshots__/rich-text.test.js.snap +15 -3
  23. package/specs/editor/various/a11y.test.js +1 -1
  24. package/specs/editor/various/block-deletion.test.js +2 -2
  25. package/specs/editor/various/block-grouping.test.js +2 -2
  26. package/specs/editor/various/block-mover.test.js +1 -1
  27. package/specs/editor/various/change-detection.test.js +2 -1
  28. package/specs/editor/various/copy-cut-paste-whole-blocks.test.js +92 -0
  29. package/specs/editor/various/embedding.test.js +1 -1
  30. package/specs/editor/various/inserting-blocks.test.js +23 -0
  31. package/specs/editor/various/keyboard-navigable-blocks.test.js +2 -2
  32. package/specs/editor/various/multi-block-selection.test.js +23 -0
  33. package/specs/editor/various/new-post.test.js +6 -3
  34. package/specs/editor/various/preview.test.js +5 -1
  35. package/specs/editor/various/rich-text.test.js +29 -1
  36. package/specs/editor/various/splitting-merging.test.js +48 -2
  37. package/specs/editor/various/writing-flow.test.js +14 -13
  38. package/specs/experiments/__snapshots__/navigation-editor.test.js.snap +27 -33
  39. package/specs/experiments/blocks/__snapshots__/navigation.test.js.snap +35 -25
  40. package/specs/experiments/blocks/navigation.test.js +93 -17
  41. package/specs/experiments/fixtures/menu-items-request-fixture.json +84 -0
  42. package/specs/experiments/navigation-editor.test.js +341 -231
  43. package/specs/experiments/template-part.test.js +6 -3
  44. package/specs/experiments/template-revert.test.js +1 -1
  45. package/specs/performance/post-editor.test.js +108 -80
  46. package/specs/performance/site-editor.test.js +2 -17
  47. package/specs/widgets/customizing-widgets.test.js +118 -7
  48. package/specs/widgets/editing-widgets.test.js +52 -88
  49. package/plugins/classic-widgets.php +0 -11
@@ -14,6 +14,7 @@ import {
14
14
  } from '@wordpress/e2e-test-utils';
15
15
 
16
16
  const alignLabels = {
17
+ none: 'None',
17
18
  left: 'Align left',
18
19
  center: 'Align center',
19
20
  right: 'Align right',
@@ -21,114 +22,129 @@ const alignLabels = {
21
22
  full: 'Full width',
22
23
  };
23
24
 
24
- describe( 'Align Hook Works As Expected', () => {
25
- beforeAll( async () => {
26
- await activatePlugin( 'gutenberg-test-align-hook' );
25
+ /**
26
+ * Helper function to get the `labels` of align control. It actually evaluates the
27
+ * basic label of the button without the `info` text node if exists.
28
+ *
29
+ * @param {Object} options Options for the util function
30
+ * @param {boolean} [options.getActiveButtonLabels=false] Flag for returning the active buttons labels only.
31
+ * @return {string[]} The matched labels.
32
+ */
33
+ const getAlignmentToolbarLabels = async ( {
34
+ getActiveButtonLabels = false,
35
+ } = {} ) => {
36
+ const selector = `.components-dropdown-menu__menu button${
37
+ getActiveButtonLabels ? '.is-active' : ''
38
+ } .components-menu-item__item`;
39
+ return page.evaluate( ( _selector ) => {
40
+ return (
41
+ Array.from( document.querySelectorAll( _selector ) )
42
+ /**
43
+ * We neede this for now because conditionally there could be two nodes
44
+ * with the same class(). This should be removed when the following
45
+ * issue is resolved.
46
+ *
47
+ * @see https://github.com/WordPress/gutenberg/issues/34838
48
+ */
49
+ .filter( ( contentNode ) => ! contentNode.childElementCount )
50
+ .map( ( contentNode ) => {
51
+ return contentNode.innerText;
52
+ } )
53
+ );
54
+ }, selector );
55
+ };
56
+
57
+ const expectActiveButtonLabelToBe = async ( expected ) => {
58
+ await clickBlockToolbarButton( 'Align' );
59
+ const activeButtonLabels = await getAlignmentToolbarLabels( {
60
+ getActiveButtonLabels: true,
27
61
  } );
62
+ expect( activeButtonLabels ).toHaveLength( 1 );
63
+ expect( activeButtonLabels[ 0 ] ).toEqual( expected );
64
+ };
28
65
 
29
- beforeEach( async () => {
30
- await createNewPost();
66
+ const createShowsTheExpectedButtonsTest = ( blockName, buttonLabels ) => {
67
+ it( 'Shows the expected buttons on the alignment toolbar', async () => {
68
+ await insertBlock( blockName );
69
+ await clickBlockToolbarButton( 'Align' );
70
+ expect( await getAlignmentToolbarLabels() ).toEqual(
71
+ expect.arrayContaining( buttonLabels )
72
+ );
31
73
  } );
74
+ };
32
75
 
33
- afterAll( async () => {
34
- await deactivatePlugin( 'gutenberg-test-align-hook' );
76
+ const createAppliesNoneAlignmentByDefaultTest = ( blockName ) => {
77
+ it( 'applies none alignment by default', async () => {
78
+ await insertBlock( blockName );
79
+ await expectActiveButtonLabelToBe( alignLabels.none );
35
80
  } );
81
+ };
82
+
83
+ const verifyMarkupIsValid = async ( htmlMarkup ) => {
84
+ await setPostContent( htmlMarkup );
85
+ const blocks = await getAllBlocks();
86
+ expect( blocks ).toHaveLength( 1 );
87
+ expect( blocks[ 0 ].isValid ).toBeTruthy();
88
+ };
89
+
90
+ const createCorrectlyAppliesAndRemovesAlignmentTest = (
91
+ blockName,
92
+ alignment
93
+ ) => {
94
+ it( 'Correctly applies the selected alignment and correctly removes the alignment', async () => {
95
+ const BUTTON_XPATH = `//button[contains(@class,'components-dropdown-menu__menu-item')]//span[contains(text(), '${ alignLabels[ alignment ] }')]`;
36
96
 
37
- const getAlignmentToolbarLabels = async () => {
97
+ // set the specified alignment.
98
+ await insertBlock( blockName );
38
99
  await clickBlockToolbarButton( 'Align' );
39
- const buttonLabels = await page.evaluate( () => {
40
- return Array.from(
41
- document.querySelectorAll(
42
- '.components-dropdown-menu__menu button'
43
- )
44
- ).map( ( button ) => {
45
- return button.innerText;
46
- } );
47
- } );
48
- return buttonLabels;
49
- };
100
+ await ( await page.$x( BUTTON_XPATH ) )[ 0 ].click();
50
101
 
51
- const createShowsTheExpectedButtonsTest = ( blockName, buttonLabels ) => {
52
- it( 'Shows the expected buttons on the alignment toolbar', async () => {
53
- await insertBlock( blockName );
54
- expect( await getAlignmentToolbarLabels() ).toEqual( buttonLabels );
55
- } );
56
- };
102
+ // verify the button of the specified alignment is pressed.
103
+ await expectActiveButtonLabelToBe( alignLabels[ alignment ] );
57
104
 
58
- const createDoesNotApplyAlignmentByDefaultTest = ( blockName ) => {
59
- it( 'Does not apply any alignment by default', async () => {
60
- await insertBlock( blockName );
61
- await clickBlockToolbarButton( 'Align' );
62
- const pressedButtons = await page.$$(
63
- '.components-dropdown-menu__menu button.is-active'
64
- );
65
- expect( pressedButtons ).toHaveLength( 0 );
66
- } );
67
- };
68
-
69
- const verifyMarkupIsValid = async ( htmlMarkup ) => {
70
- await setPostContent( htmlMarkup );
71
- const blocks = await getAllBlocks();
72
- expect( blocks ).toHaveLength( 1 );
73
- expect( blocks[ 0 ].isValid ).toBeTruthy();
74
- };
75
-
76
- const createCorrectlyAppliesAndRemovesAlignmentTest = (
77
- blockName,
78
- alignment
79
- ) => {
80
- it( 'Correctly applies the selected alignment and correctly removes the alignment', async () => {
81
- const BUTTON_XPATH = `//button[contains(@class,'components-dropdown-menu__menu-item') and contains(text(), '${ alignLabels[ alignment ] }')]`;
82
- const BUTTON_PRESSED_SELECTOR =
83
- '.components-dropdown-menu__menu button.is-active';
84
- // set the specified alignment.
85
- await insertBlock( blockName );
86
- await clickBlockToolbarButton( 'Align' );
87
- await ( await page.$x( BUTTON_XPATH ) )[ 0 ].click();
105
+ let htmlMarkup = await getEditedPostContent();
88
106
 
89
- // verify the button of the specified alignment is pressed.
90
- await clickBlockToolbarButton( 'Align' );
91
- let pressedButtons = await page.$$( BUTTON_PRESSED_SELECTOR );
92
- expect( pressedButtons ).toHaveLength( 1 );
107
+ // verify the markup of the selected alignment was generated.
108
+ expect( htmlMarkup ).toMatchSnapshot();
93
109
 
94
- let htmlMarkup = await getEditedPostContent();
110
+ // verify the markup can be correctly parsed
111
+ await verifyMarkupIsValid( htmlMarkup );
95
112
 
96
- // verify the markup of the selected alignment was generated.
97
- expect( htmlMarkup ).toMatchSnapshot();
113
+ await selectBlockByClientId( ( await getAllBlocks() )[ 0 ].clientId );
98
114
 
99
- // verify the markup can be correctly parsed
100
- await verifyMarkupIsValid( htmlMarkup );
115
+ // remove the alignment.
116
+ await clickBlockToolbarButton( 'Align' );
117
+ await ( await page.$x( BUTTON_XPATH ) )[ 0 ].click();
101
118
 
102
- await selectBlockByClientId(
103
- ( await getAllBlocks() )[ 0 ].clientId
104
- );
119
+ // verify 'none' alignment button is in pressed state.
120
+ await expectActiveButtonLabelToBe( alignLabels.none );
105
121
 
106
- // remove the alignment.
107
- await clickBlockToolbarButton( 'Align' );
108
- await ( await page.$x( BUTTON_XPATH ) )[ 0 ].click();
122
+ // verify alignment markup was removed from the block.
123
+ htmlMarkup = await getEditedPostContent();
124
+ expect( htmlMarkup ).toMatchSnapshot();
109
125
 
110
- // verify no alignment button is in pressed state.
111
- await clickBlockToolbarButton( 'Align' );
112
- pressedButtons = await page.$$( BUTTON_PRESSED_SELECTOR );
113
- expect( pressedButtons ).toHaveLength( 0 );
126
+ // verify the markup when no alignment is set is valid
127
+ await verifyMarkupIsValid( htmlMarkup );
114
128
 
115
- // verify alignment markup was removed from the block.
116
- htmlMarkup = await getEditedPostContent();
117
- expect( htmlMarkup ).toMatchSnapshot();
129
+ await selectBlockByClientId( ( await getAllBlocks() )[ 0 ].clientId );
118
130
 
119
- // verify the markup when no alignment is set is valid
120
- await verifyMarkupIsValid( htmlMarkup );
131
+ // verify alignment `none` button is in pressed state after parsing the block.
132
+ await expectActiveButtonLabelToBe( alignLabels.none );
133
+ } );
134
+ };
121
135
 
122
- await selectBlockByClientId(
123
- ( await getAllBlocks() )[ 0 ].clientId
124
- );
136
+ describe( 'Align Hook Works As Expected', () => {
137
+ beforeAll( async () => {
138
+ await activatePlugin( 'gutenberg-test-align-hook' );
139
+ } );
125
140
 
126
- // verify no alignment button is in pressed state after parsing the block.
127
- await clickBlockToolbarButton( 'Align' );
128
- pressedButtons = await page.$$( BUTTON_PRESSED_SELECTOR );
129
- expect( pressedButtons ).toHaveLength( 0 );
130
- } );
131
- };
141
+ beforeEach( async () => {
142
+ await createNewPost();
143
+ } );
144
+
145
+ afterAll( async () => {
146
+ await deactivatePlugin( 'gutenberg-test-align-hook' );
147
+ } );
132
148
 
133
149
  describe( 'Block with no alignment set', () => {
134
150
  const BLOCK_NAME = 'Test No Alignment Set';
@@ -151,15 +167,12 @@ describe( 'Align Hook Works As Expected', () => {
151
167
  describe( 'Block with align true', () => {
152
168
  const BLOCK_NAME = 'Test Align True';
153
169
 
154
- createShowsTheExpectedButtonsTest( BLOCK_NAME, [
155
- alignLabels.left,
156
- alignLabels.center,
157
- alignLabels.right,
158
- alignLabels.wide,
159
- alignLabels.full,
160
- ] );
170
+ createShowsTheExpectedButtonsTest(
171
+ BLOCK_NAME,
172
+ Object.values( alignLabels )
173
+ );
161
174
 
162
- createDoesNotApplyAlignmentByDefaultTest( BLOCK_NAME );
175
+ createAppliesNoneAlignmentByDefaultTest( BLOCK_NAME );
163
176
 
164
177
  createCorrectlyAppliesAndRemovesAlignmentTest( BLOCK_NAME, 'right' );
165
178
  } );
@@ -168,11 +181,12 @@ describe( 'Align Hook Works As Expected', () => {
168
181
  const BLOCK_NAME = 'Test Align Array';
169
182
 
170
183
  createShowsTheExpectedButtonsTest( BLOCK_NAME, [
184
+ alignLabels.none,
171
185
  alignLabels.left,
172
186
  alignLabels.center,
173
187
  ] );
174
188
 
175
- createDoesNotApplyAlignmentByDefaultTest( BLOCK_NAME );
189
+ createAppliesNoneAlignmentByDefaultTest( BLOCK_NAME );
176
190
 
177
191
  createCorrectlyAppliesAndRemovesAlignmentTest( BLOCK_NAME, 'center' );
178
192
  } );
@@ -180,14 +194,11 @@ describe( 'Align Hook Works As Expected', () => {
180
194
  describe( 'Block with default align', () => {
181
195
  const BLOCK_NAME = 'Test Default Align';
182
196
  const SELECTED_ALIGNMENT_CONTROL_SELECTOR =
183
- '//div[contains(@class, "components-dropdown-menu__menu")]//button[contains(@class, "is-active")][text()="Align right"]';
184
- createShowsTheExpectedButtonsTest( BLOCK_NAME, [
185
- alignLabels.left,
186
- alignLabels.center,
187
- alignLabels.right,
188
- alignLabels.wide,
189
- alignLabels.full,
190
- ] );
197
+ '//div[contains(@class, "components-dropdown-menu__menu")]//button[contains(@class, "is-active")]/span[text()="Align right"]';
198
+ createShowsTheExpectedButtonsTest(
199
+ BLOCK_NAME,
200
+ Object.values( alignLabels )
201
+ );
191
202
 
192
203
  it( 'Applies the selected alignment by default', async () => {
193
204
  await insertBlock( BLOCK_NAME );
@@ -52,7 +52,7 @@ describe( 'Using Plugins API', () => {
52
52
  )[ 0 ];
53
53
  await addAnnotationButton.click();
54
54
  await page.evaluate( () =>
55
- document.querySelector( '[contenteditable]' ).focus()
55
+ document.querySelector( '.wp-block-paragraph' ).focus()
56
56
  );
57
57
  }
58
58
 
@@ -91,7 +91,7 @@ describe( 'Using Plugins API', () => {
91
91
  * @return {Promise<string>} Inner HTML.
92
92
  */
93
93
  async function getRichTextInnerHTML() {
94
- const htmlContent = await page.$$( '*[contenteditable]' );
94
+ const htmlContent = await page.$$( '.wp-block-paragraph' );
95
95
  return await page.evaluate( ( el ) => {
96
96
  return el.innerHTML;
97
97
  }, htmlContent[ 0 ] );
@@ -139,7 +139,7 @@ describe( 'Using Plugins API', () => {
139
139
  await page.keyboard.type( 'D' );
140
140
 
141
141
  await removeAnnotations();
142
- const htmlContent = await page.$$( '*[contenteditable]' );
142
+ const htmlContent = await page.$$( '.wp-block-paragraph' );
143
143
  const html = await page.evaluate( ( el ) => {
144
144
  return el.innerHTML;
145
145
  }, htmlContent[ 0 ] );
@@ -0,0 +1,58 @@
1
+ /**
2
+ * WordPress dependencies
3
+ */
4
+ import {
5
+ activatePlugin,
6
+ createNewPost,
7
+ deactivatePlugin,
8
+ insertBlock,
9
+ getEditedPostContent,
10
+ openDocumentSettingsSidebar,
11
+ clickButton,
12
+ canvas,
13
+ } from '@wordpress/e2e-test-utils';
14
+
15
+ describe( 'changing image size', () => {
16
+ beforeEach( async () => {
17
+ await activatePlugin( 'gutenberg-test-iframed-block' );
18
+ await createNewPost( { postType: 'page' } );
19
+ } );
20
+
21
+ afterEach( async () => {
22
+ await deactivatePlugin( 'gutenberg-test-iframed-block' );
23
+ } );
24
+
25
+ it( 'should load script and dependencies in iframe', async () => {
26
+ await insertBlock( 'Iframed Block' );
27
+
28
+ expect( await getEditedPostContent() ).toMatchSnapshot();
29
+
30
+ const element = await page.waitForSelector(
31
+ '.wp-block-test-iframed-block'
32
+ );
33
+ const text = await element.evaluate( ( el ) => el.textContent );
34
+
35
+ expect( text ).toBe( 'Iframed Block (set with jQuery)' );
36
+
37
+ await openDocumentSettingsSidebar();
38
+ await clickButton( 'Page' );
39
+ await clickButton( 'Template' );
40
+ await clickButton( 'New' );
41
+ await page.keyboard.press( 'Tab' );
42
+ await page.keyboard.press( 'Tab' );
43
+ await page.keyboard.type( 'Iframed Test' );
44
+ await clickButton( 'Create' );
45
+ await page.waitForSelector( 'iframe[name="editor-canvas"]' );
46
+
47
+ const iframeElement = await canvas().waitForSelector(
48
+ '.wp-block-test-iframed-block'
49
+ );
50
+ const iframedText = await iframeElement.evaluate(
51
+ ( el ) => el.textContent
52
+ );
53
+
54
+ // Expect the script to load in the iframe, which replaces the block
55
+ // text.
56
+ expect( iframedText ).toBe( 'Iframed Block (set with jQuery)' );
57
+ } );
58
+ } );
@@ -1,5 +1,19 @@
1
1
  // Jest Snapshot v1, https://goo.gl/fbAQLP
2
2
 
3
+ exports[`Copy/cut/paste of whole blocks can copy group onto non textual element (image, spacer) 1`] = `""`;
4
+
5
+ exports[`Copy/cut/paste of whole blocks can copy group onto non textual element (image, spacer) 2`] = `
6
+ "<!-- wp:paragraph -->
7
+ <p></p>
8
+ <!-- /wp:paragraph -->
9
+
10
+ <!-- wp:group -->
11
+ <div class=\\"wp-block-group\\"><!-- wp:paragraph -->
12
+ <p>P</p>
13
+ <!-- /wp:paragraph --></div>
14
+ <!-- /wp:group -->"
15
+ `;
16
+
3
17
  exports[`Copy/cut/paste of whole blocks should copy and paste individual blocks 1`] = `
4
18
  "<!-- wp:paragraph -->
5
19
  <p>Here is a unique string so we can test copying.</p>
@@ -56,6 +70,20 @@ exports[`Copy/cut/paste of whole blocks should cut and paste individual blocks 2
56
70
  <!-- /wp:paragraph -->"
57
71
  `;
58
72
 
73
+ exports[`Copy/cut/paste of whole blocks should handle paste events once 1`] = `""`;
74
+
75
+ exports[`Copy/cut/paste of whole blocks should handle paste events once 2`] = `
76
+ "<!-- wp:paragraph -->
77
+ <p></p>
78
+ <!-- /wp:paragraph -->
79
+
80
+ <!-- wp:group -->
81
+ <div class=\\"wp-block-group\\"><!-- wp:paragraph -->
82
+ <p>P</p>
83
+ <!-- /wp:paragraph --></div>
84
+ <!-- /wp:group -->"
85
+ `;
86
+
59
87
  exports[`Copy/cut/paste of whole blocks should respect inline copy in places like input fields and textareas 1`] = `
60
88
  "<!-- wp:shortcode -->
61
89
  [my-shortcode]
@@ -54,6 +54,18 @@ exports[`RichText should navigate arround emoji 1`] = `
54
54
  <!-- /wp:paragraph -->"
55
55
  `;
56
56
 
57
+ exports[`RichText should navigate consecutive format boundaries 1`] = `
58
+ "<!-- wp:paragraph -->
59
+ <p><strong>1</strong><em>2</em></p>
60
+ <!-- /wp:paragraph -->"
61
+ `;
62
+
63
+ exports[`RichText should navigate consecutive format boundaries 2`] = `
64
+ "<!-- wp:paragraph -->
65
+ <p><strong>1</strong>-<em>2</em></p>
66
+ <!-- /wp:paragraph -->"
67
+ `;
68
+
57
69
  exports[`RichText should not format text after code backtick 1`] = `
58
70
  "<!-- wp:paragraph -->
59
71
  <p>A <code>backtick</code> and more.</p>
@@ -90,17 +102,17 @@ exports[`RichText should only mutate text data on input 1`] = `
90
102
 
91
103
  exports[`RichText should preserve internal formatting 1`] = `
92
104
  "<!-- wp:paragraph -->
93
- <p><span class=\\"has-inline-color has-cyan-bluish-gray-color\\">1</span></p>
105
+ <p><mark style=\\"background-color:rgba(0, 0, 0, 0)\\" class=\\"has-inline-color has-cyan-bluish-gray-color\\">1</mark></p>
94
106
  <!-- /wp:paragraph -->"
95
107
  `;
96
108
 
97
109
  exports[`RichText should preserve internal formatting 2`] = `
98
110
  "<!-- wp:paragraph -->
99
- <p><span class=\\"has-inline-color has-cyan-bluish-gray-color\\">1</span></p>
111
+ <p><mark style=\\"background-color:rgba(0, 0, 0, 0)\\" class=\\"has-inline-color has-cyan-bluish-gray-color\\">1</mark></p>
100
112
  <!-- /wp:paragraph -->
101
113
 
102
114
  <!-- wp:paragraph -->
103
- <p><span class=\\"has-inline-color has-cyan-bluish-gray-color\\">1</span></p>
115
+ <p><mark style=\\"background-color:rgba(0, 0, 0, 0)\\" class=\\"has-inline-color has-cyan-bluish-gray-color\\">1</mark></p>
104
116
  <!-- /wp:paragraph -->"
105
117
  `;
106
118
 
@@ -15,7 +15,7 @@ describe( 'a11y', () => {
15
15
  } );
16
16
 
17
17
  it( 'tabs header bar', async () => {
18
- await pressKeyWithModifier( 'ctrl', '~' );
18
+ await pressKeyWithModifier( 'ctrl', '`' );
19
19
 
20
20
  await page.keyboard.press( 'Tab' );
21
21
 
@@ -156,10 +156,10 @@ describe( 'deleting all blocks', () => {
156
156
  await page.keyboard.type( 'Paragraph' );
157
157
  await clickOnBlockSettingsMenuRemoveBlockButton();
158
158
 
159
- // There is a default block:
159
+ // There is a default block and post title:
160
160
  expect(
161
161
  await page.$$( '.block-editor-block-list__block' )
162
- ).toHaveLength( 1 );
162
+ ).toHaveLength( 2 );
163
163
 
164
164
  // But the effective saved content is still empty:
165
165
  expect( await getEditedPostContent() ).toBe( '' );
@@ -163,7 +163,7 @@ describe( 'Block Grouping', () => {
163
163
  await insertBlock( 'Image' );
164
164
  await clickBlockToolbarButton( 'Align' );
165
165
  const fullButton = await page.waitForXPath(
166
- `//button[contains(@class,'components-dropdown-menu__menu-item') and contains(text(), 'Full width')]`
166
+ `//button[contains(@class,'components-dropdown-menu__menu-item')]//span[contains(text(), 'Full width')]`
167
167
  );
168
168
  await fullButton.evaluate( ( element ) =>
169
169
  element.scrollIntoView()
@@ -174,7 +174,7 @@ describe( 'Block Grouping', () => {
174
174
  await insertBlock( 'Image' );
175
175
  await clickBlockToolbarButton( 'Align' );
176
176
  const wideButton = await page.waitForXPath(
177
- `//button[contains(@class,'components-dropdown-menu__menu-item') and contains(text(), 'Wide width')]`
177
+ `//button[contains(@class,'components-dropdown-menu__menu-item')]//span[contains(text(), 'Wide width')]`
178
178
  );
179
179
  await wideButton.evaluate( ( element ) =>
180
180
  element.scrollIntoView()
@@ -16,7 +16,7 @@ describe( 'block mover', () => {
16
16
  await page.keyboard.type( 'Second Paragraph' );
17
17
 
18
18
  // Select a block so the block mover is rendered.
19
- await page.focus( '.block-editor-block-list__block' );
19
+ await page.focus( '[data-type="core/paragraph"]' );
20
20
 
21
21
  await showBlockToolbar();
22
22
 
@@ -331,7 +331,8 @@ describe( 'Change detection', () => {
331
331
  // Verify that the title is empty.
332
332
  const title = await page.$eval(
333
333
  '.editor-post-title__input',
334
- ( element ) => element.innerHTML
334
+ // Trim padding non-breaking space
335
+ ( element ) => element.textContent.trim()
335
336
  );
336
337
  expect( title ).toBe( '' );
337
338
 
@@ -92,4 +92,96 @@ describe( 'Copy/cut/paste of whole blocks', () => {
92
92
  await pressKeyWithModifier( 'primary', 'v' );
93
93
  expect( await getEditedPostContent() ).toMatchSnapshot();
94
94
  } );
95
+
96
+ it( 'should handle paste events once', async () => {
97
+ // Add group block with paragraph
98
+ await insertBlock( 'Group' );
99
+ await page.click( '.block-editor-button-block-appender' );
100
+ await page.click( '.editor-block-list-item-paragraph' );
101
+ await page.keyboard.type( 'P' );
102
+ await page.keyboard.press( 'ArrowLeft' );
103
+ await page.keyboard.press( 'ArrowLeft' );
104
+ // Cut group
105
+ await pressKeyWithModifier( 'primary', 'x' );
106
+ expect( await getEditedPostContent() ).toMatchSnapshot();
107
+
108
+ await page.keyboard.press( 'Enter' );
109
+
110
+ await page.evaluate( () => {
111
+ window.e2eTestPasteOnce = [];
112
+ let oldBlocks = wp.data.select( 'core/block-editor' ).getBlocks();
113
+ wp.data.subscribe( () => {
114
+ const blocks = wp.data
115
+ .select( 'core/block-editor' )
116
+ .getBlocks();
117
+ if ( blocks !== oldBlocks ) {
118
+ window.e2eTestPasteOnce.push(
119
+ blocks.map( ( { clientId, name } ) => ( {
120
+ clientId,
121
+ name,
122
+ } ) )
123
+ );
124
+ }
125
+ oldBlocks = blocks;
126
+ } );
127
+ } );
128
+
129
+ // Paste
130
+ await pressKeyWithModifier( 'primary', 'v' );
131
+
132
+ // Blocks should only be modified once, not twice with new clientIds on a single paste action
133
+ const blocksUpdated = await page.evaluate(
134
+ () => window.e2eTestPasteOnce
135
+ );
136
+
137
+ expect( blocksUpdated.length ).toEqual( 1 );
138
+ expect( await getEditedPostContent() ).toMatchSnapshot();
139
+ } );
140
+
141
+ it( 'can copy group onto non textual element (image, spacer)', async () => {
142
+ // Add group block with paragraph
143
+ await insertBlock( 'Group' );
144
+ await page.click( '.block-editor-button-block-appender' );
145
+ await page.click( '.editor-block-list-item-paragraph' );
146
+ await page.keyboard.type( 'P' );
147
+ await page.keyboard.press( 'ArrowLeft' );
148
+ await page.keyboard.press( 'ArrowLeft' );
149
+ // Cut group
150
+ await pressKeyWithModifier( 'primary', 'x' );
151
+ expect( await getEditedPostContent() ).toMatchSnapshot();
152
+
153
+ await page.keyboard.press( 'Enter' );
154
+
155
+ // Insert a non textual element (a spacer)
156
+ await insertBlock( 'Spacer' );
157
+ // Spacer is focused
158
+ await page.evaluate( () => {
159
+ window.e2eTestPasteOnce = [];
160
+ let oldBlocks = wp.data.select( 'core/block-editor' ).getBlocks();
161
+ wp.data.subscribe( () => {
162
+ const blocks = wp.data
163
+ .select( 'core/block-editor' )
164
+ .getBlocks();
165
+ if ( blocks !== oldBlocks ) {
166
+ window.e2eTestPasteOnce.push(
167
+ blocks.map( ( { clientId, name } ) => ( {
168
+ clientId,
169
+ name,
170
+ } ) )
171
+ );
172
+ }
173
+ oldBlocks = blocks;
174
+ } );
175
+ } );
176
+
177
+ await pressKeyWithModifier( 'primary', 'v' );
178
+
179
+ // Paste should be handled on non-textual elements and only handled once.
180
+ const blocksUpdated = await page.evaluate(
181
+ () => window.e2eTestPasteOnce
182
+ );
183
+
184
+ expect( blocksUpdated.length ).toEqual( 1 );
185
+ expect( await getEditedPostContent() ).toMatchSnapshot();
186
+ } );
95
187
  } );
@@ -17,7 +17,7 @@ import {
17
17
  const MOCK_EMBED_WORDPRESS_SUCCESS_RESPONSE = {
18
18
  url: 'https://wordpress.org/gutenberg/handbook/block-api/attributes/',
19
19
  html:
20
- '<div class="wp-embedded-content" data-secret="shhhh it is a secret">WordPress embed</div>',
20
+ '<div class="wp-embedded-content" data-secret="shhhh it is a secret"></div>',
21
21
  type: 'rich',
22
22
  provider_name: 'WordPress',
23
23
  provider_url: 'https://wordpress.org',
@@ -11,6 +11,7 @@ import {
11
11
  searchForBlock,
12
12
  setBrowserViewport,
13
13
  showBlockToolbar,
14
+ pressKeyWithModifier,
14
15
  } from '@wordpress/e2e-test-utils';
15
16
 
16
17
  /** @typedef {import('puppeteer').ElementHandle} ElementHandle */
@@ -354,6 +355,28 @@ describe( 'Inserting blocks', () => {
354
355
  expect( await getEditedPostContent() ).toMatchSnapshot();
355
356
  } );
356
357
 
358
+ it( 'passes the search value in the main inserter when clicking `Browse all`', async () => {
359
+ const INSERTER_SEARCH_SELECTOR =
360
+ '.block-editor-inserter__search input,.block-editor-inserter__search-input,input.block-editor-inserter__search';
361
+ await insertBlock( 'Group' );
362
+ await insertBlock( 'Paragraph' );
363
+ await page.keyboard.type( 'Text' );
364
+ await page.click( '[data-type="core/group"] [aria-label="Add block"]' );
365
+ await page.waitForSelector( INSERTER_SEARCH_SELECTOR );
366
+ await page.focus( INSERTER_SEARCH_SELECTOR );
367
+ await pressKeyWithModifier( 'primary', 'a' );
368
+ const searchTerm = 'Heading';
369
+ await page.keyboard.type( searchTerm );
370
+ const browseAll = await page.waitForXPath(
371
+ '//button[text()="Browse all"]'
372
+ );
373
+ await browseAll.click();
374
+ const availableBlocks = await page.$$(
375
+ '.block-editor-block-types-list__list-item'
376
+ );
377
+ expect( availableBlocks ).toHaveLength( 1 );
378
+ } );
379
+
357
380
  // Check for regression of https://github.com/WordPress/gutenberg/issues/27586
358
381
  it( 'closes the main inserter after inserting a single-use block, like the More block', async () => {
359
382
  await insertBlock( 'More' );