-
Notifications
You must be signed in to change notification settings - Fork 4.2k
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
[Mobile] - Add E2E tests for the Drag & Drop blocks feature #41368
Changes from all commits
55588c3
3ecad42
1edaae0
85bc0f5
387b288
a4bb605
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,153 @@ | ||
/** | ||
* Internal dependencies | ||
*/ | ||
import { blockNames } from './pages/editor-page'; | ||
import { | ||
clearClipboard, | ||
clickElementOutsideOfTextInput, | ||
dragAndDropAfterElement, | ||
isAndroid, | ||
setClipboard, | ||
tapPasteAboveElement, | ||
} from './helpers/utils'; | ||
import testData from './helpers/test-data'; | ||
|
||
describe( 'Gutenberg Editor Drag & Drop blocks tests', () => { | ||
beforeEach( async () => { | ||
await clearClipboard( editorPage.driver ); | ||
} ); | ||
|
||
it( 'should be able to drag & drop a block', async () => { | ||
// Initialize the editor with a Spacer and Paragraph block | ||
await editorPage.setHtmlContent( | ||
[ testData.spacerBlock, testData.paragraphBlockShortText ].join( | ||
'\n\n' | ||
) | ||
); | ||
|
||
// Get elements for both blocks | ||
const spacerBlock = await editorPage.getBlockAtPosition( | ||
blockNames.spacer | ||
); | ||
const paragraphBlock = await editorPage.getParagraphBlockWrapperAtPosition( | ||
2 | ||
); | ||
|
||
// Drag & drop the Spacer block after the Paragraph block | ||
await dragAndDropAfterElement( | ||
editorPage.driver, | ||
spacerBlock, | ||
paragraphBlock | ||
); | ||
|
||
// Get the first block, in this case the Paragraph block | ||
// and check the text value is the expected one | ||
const firstBlockText = await editorPage.getTextForParagraphBlockAtPosition( | ||
1 | ||
); | ||
expect( firstBlockText ).toMatch( testData.shortText ); | ||
|
||
// Remove the blocks | ||
await spacerBlock.click(); | ||
await editorPage.removeBlockAtPosition( blockNames.spacer, 2 ); | ||
await editorPage.removeBlockAtPosition( blockNames.paragraph, 1 ); | ||
} ); | ||
|
||
it( 'should be able to long-press on a text-based block to paste a text in a focused textinput', async () => { | ||
// Add a Paragraph block | ||
await editorPage.addNewBlock( blockNames.paragraph ); | ||
const paragraphBlockElement = await editorPage.getTextBlockAtPosition( | ||
blockNames.paragraph | ||
); | ||
|
||
// Set clipboard text | ||
await setClipboard( editorPage.driver, testData.shortText ); | ||
|
||
// Dismiss auto-suggestion popup | ||
if ( isAndroid() ) { | ||
// On Andrdoid 10 a new auto-suggestion popup is appearing to let the user paste text recently put in the clipboard. Let's dismiss it. | ||
await editorPage.dismissAndroidClipboardSmartSuggestion(); | ||
} | ||
|
||
// Paste into the Paragraph block | ||
await tapPasteAboveElement( editorPage.driver, paragraphBlockElement ); | ||
const paragraphText = await editorPage.getTextForParagraphBlockAtPosition( | ||
1 | ||
); | ||
|
||
// Expect to have the pasted text in the Paragraph block | ||
expect( paragraphText ).toMatch( testData.shortText ); | ||
|
||
// Remove the block | ||
await editorPage.removeBlockAtPosition( blockNames.paragraph ); | ||
} ); | ||
|
||
it( 'should be able to long-press on a text-based block using the PlainText component to paste a text in a focused textinput', async () => { | ||
// Add a Shortcode block | ||
await editorPage.addNewBlock( blockNames.shortcode ); | ||
const shortcodeBlockElement = await editorPage.getShortBlockTextInputAtPosition( | ||
blockNames.shortcode | ||
); | ||
|
||
// Set clipboard text | ||
await setClipboard( editorPage.driver, testData.shortText ); | ||
|
||
// Dismiss auto-suggestion popup | ||
if ( isAndroid() ) { | ||
// On Andrdoid 10 a new auto-suggestion popup is appearing to let the user paste text recently put in the clipboard. Let's dismiss it. | ||
await editorPage.dismissAndroidClipboardSmartSuggestion(); | ||
} | ||
|
||
// Paste into the Shortcode block | ||
await tapPasteAboveElement( editorPage.driver, shortcodeBlockElement ); | ||
const shortcodeText = await shortcodeBlockElement.text(); | ||
|
||
// Expect to have the pasted text in the Shortcode block | ||
expect( shortcodeText ).toMatch( testData.shortText ); | ||
|
||
// Remove the block | ||
await editorPage.removeBlockAtPosition( blockNames.shortcode ); | ||
} ); | ||
|
||
it( 'should be able to drag & drop a text-based block when the textinput is not focused', async () => { | ||
// Initialize the editor with two Paragraph blocks | ||
await editorPage.setHtmlContent( | ||
[ | ||
testData.paragraphBlockShortText, | ||
testData.paragraphBlockEmpty, | ||
].join( '\n\n' ) | ||
); | ||
|
||
// Get elements for both blocks | ||
const firstParagraphBlock = await editorPage.getParagraphBlockWrapperAtPosition( | ||
1 | ||
); | ||
const secondParagraphBlock = await editorPage.getParagraphBlockWrapperAtPosition( | ||
2 | ||
); | ||
|
||
// Tap on the first Paragraph block outside of the textinput | ||
await clickElementOutsideOfTextInput( | ||
editorPage.driver, | ||
firstParagraphBlock | ||
); | ||
|
||
// Drag & drop the first Paragraph block after the second Paragraph block | ||
await dragAndDropAfterElement( | ||
editorPage.driver, | ||
firstParagraphBlock, | ||
secondParagraphBlock | ||
); | ||
|
||
// Get the current second Paragraph block in the editor after dragging & dropping | ||
const secondBlockText = await editorPage.getTextForParagraphBlockAtPosition( | ||
2 | ||
); | ||
|
||
// Expect the second Paragraph block to have the expected content | ||
expect( secondBlockText ).toMatch( testData.shortText ); | ||
|
||
// Remove the block | ||
await editorPage.removeBlockAtPosition( blockNames.paragraph ); | ||
} ); | ||
} ); |
Original file line number | Diff line number | Diff line change |
---|---|---|
|
@@ -46,6 +46,9 @@ const strToKeycode = { | |
[ backspace ]: 67, | ||
}; | ||
|
||
// $block-edge-to-content value | ||
const blockEdgeToContent = 16; | ||
|
||
const timer = ( ms ) => new Promise( ( res ) => setTimeout( res, ms ) ); | ||
|
||
const isAndroid = () => { | ||
|
@@ -301,18 +304,27 @@ const clickBeginningOfElement = async ( driver, element ) => { | |
await action.perform(); | ||
}; | ||
|
||
// Clicks in the top left of a text-based element outside of the TextInput | ||
const clickElementOutsideOfTextInput = async ( driver, element ) => { | ||
const location = await element.getLocation(); | ||
const y = isAndroid() ? location.y - blockEdgeToContent : location.y; | ||
const x = isAndroid() ? location.x - blockEdgeToContent : location.x; | ||
|
||
const action = new wd.TouchAction( driver ).press( { x, y } ).release(); | ||
await action.perform(); | ||
}; | ||
|
||
// Long press to activate context menu. | ||
const longPressMiddleOfElement = async ( driver, element ) => { | ||
const location = await element.getLocation(); | ||
const size = await element.getSize(); | ||
|
||
const action = await new wd.TouchAction( driver ); | ||
const x = location.x + size.width / 2; | ||
const y = location.y + size.height / 2; | ||
action.press( { x, y } ); | ||
// Setting to wait a bit longer because this is failing more frequently on the CI | ||
action.wait( 5000 ); | ||
action.release(); | ||
const action = new wd.TouchAction( driver ) | ||
.longPress( { x, y } ) | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. I changed this from |
||
.wait( 5000 ) // Setting to wait a bit longer because this is failing more frequently on the CI | ||
.release(); | ||
await action.perform(); | ||
}; | ||
|
||
|
@@ -342,13 +354,21 @@ const tapCopyAboveElement = async ( driver, element ) => { | |
|
||
// Press "Paste" in floating context menu. | ||
const tapPasteAboveElement = async ( driver, element ) => { | ||
const location = await element.getLocation(); | ||
const action = await new wd.TouchAction( driver ); | ||
action.wait( 2000 ); | ||
action.press( { x: location.x + 100, y: location.y - 50 } ); | ||
action.wait( 2000 ); | ||
action.release(); | ||
await action.perform(); | ||
await longPressMiddleOfElement( driver, element ); | ||
|
||
if ( isAndroid() ) { | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more.
|
||
const location = await element.getLocation(); | ||
const action = await new wd.TouchAction( driver ); | ||
action.wait( 2000 ); | ||
action.press( { x: location.x + 100, y: location.y - 50 } ); | ||
action.wait( 2000 ); | ||
action.release(); | ||
await action.perform(); | ||
} else { | ||
const pasteButtonLocator = '//XCUIElementTypeMenuItem[@name="Paste"]'; | ||
await clickIfClickable( driver, pasteButtonLocator ); | ||
await driver.sleep( 3000 ); // Wait for paste notification to disappear. | ||
} | ||
}; | ||
|
||
// Starts from the middle of the screen or the element(if specified) | ||
|
@@ -413,6 +433,29 @@ const swipeDown = async ( driver, delay = 3000 ) => { | |
); | ||
}; | ||
|
||
// Drag & Drop after element | ||
const dragAndDropAfterElement = async ( driver, element, nextElement ) => { | ||
// Element to drag & drop | ||
const elementLocation = await element.getLocation(); | ||
const elementSize = await element.getSize(); | ||
const x = elementLocation.x + elementSize.width / 2; | ||
const y = elementLocation.y + elementSize.height / 2; | ||
|
||
// Element to drag & drop to | ||
const nextElementLocation = await nextElement.getLocation(); | ||
const nextElementSize = await nextElement.getSize(); | ||
const nextYPosition = isAndroid() | ||
? elementLocation.y + nextElementLocation.y + nextElementSize.height | ||
: nextElementLocation.y + nextElementSize.height; | ||
|
||
const action = new wd.TouchAction( driver ) | ||
.press( { x, y } ) | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. I ended up using |
||
.wait( 5000 ) | ||
.moveTo( { x, y: nextYPosition } ) | ||
.release(); | ||
await action.perform(); | ||
}; | ||
|
||
const toggleHtmlMode = async ( driver, toggleOn ) => { | ||
if ( isAndroid() ) { | ||
// Hit the "Menu" key. | ||
|
@@ -575,17 +618,50 @@ const waitIfAndroid = async () => { | |
} | ||
}; | ||
|
||
/** | ||
* Content type definitions. | ||
* Note: Android only supports plaintext. | ||
* | ||
* @typedef {"plaintext" | "image" | "url"} ClipboardContentType | ||
*/ | ||
|
||
/** | ||
* Helper to set content in the clipboard. | ||
* | ||
* @param {Object} driver Driver | ||
* @param {string} content Content to set in the clipboard | ||
* @param {ClipboardContentType} contentType Type of the content | ||
*/ | ||
const setClipboard = async ( driver, content, contentType = 'plaintext' ) => { | ||
const base64String = Buffer.from( content ).toString( 'base64' ); | ||
await driver.setClipboard( base64String, contentType ); | ||
}; | ||
|
||
/** | ||
* Helper to clear the clipboard | ||
* | ||
* @param {Object} driver Driver | ||
* @param {ClipboardContentType} contentType Type of the content | ||
*/ | ||
const clearClipboard = async ( driver, contentType = 'plaintext' ) => { | ||
await driver.setClipboard( '', contentType ); | ||
}; | ||
|
||
module.exports = { | ||
backspace, | ||
clearClipboard, | ||
clickBeginningOfElement, | ||
clickElementOutsideOfTextInput, | ||
clickIfClickable, | ||
clickMiddleOfElement, | ||
doubleTap, | ||
dragAndDropAfterElement, | ||
isAndroid, | ||
isEditorVisible, | ||
isElementVisible, | ||
isLocalEnvironment, | ||
longPressMiddleOfElement, | ||
setClipboard, | ||
setupDriver, | ||
stopDriver, | ||
swipeDown, | ||
|
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I moved this to
tapPasteAboveElement
so it handles all of the logic.