Skip to content

Commit

Permalink
[RNMobile] Initial HTML E2E test (#26405)
Browse files Browse the repository at this point in the history
* added initial-html to test-data.js

* Added a test that loads all the blocks within a post.

* Updated the ci test runners to run gutenberg-editor-blocks

* added a function that scrolls to a specific element and returns it.

* updated the test to utilize the scroll and return functionality

* utilize a shared initialHtml for both test and initial editor content.

* added delay to swipe methods.

* Remove unneeded code after merging master

* Paste instead of typing when setting HTML content

* Paste using keycode on Android

* Select html content view only on android

* Use iOS 14.0 simulator on server

* Print driver data on stop

* Enable remaining paragraph tests on iOS

* Remove dot at the end of jobURL

* Print menuButton

* Print error if environment cannot be initialized

* Revert "Use iOS 14.0 simulator on server"

This reverts commit 85178d1.

* Revert "Print driver data on stop"

This reverts commit f136234.

* Revert "Print menuButton"

This reverts commit 9e16a13.

* Update server appium version to 1.18 and iOS to 14.0

* Add teardown to paste test

* On iOS wait for paste notification to disappear

* Double tap instead of long press to paste

* Fix lint error

* On iOS also long press before pasting

* On iOS use click instead of clickBeginningOfElement in paragraph test

* Scroll to bottom by adding a paragraph block to the end

* Use .type on Android instead of pressing paste keycode

* Check last block without scrolling on iOS

* Update package-lock.json

* added endYCoefficient to increase the swipe distance on Android.

* added package-lock.json

* Retry one more time getting the last block after a delay on iOS

* Temporarily delete cancel workflow (and others) to run native jobs multiple times more quickly in consecutive commits

* Run 1

* Run 2

* Run 3

* Run 4

* Run 5

* Run 6

* Run 7

* Run 8

* Revert "Temporarily delete cancel workflow (and others) to run native jobs multiple times more quickly in consecutive commits"

This reverts commit fe5a1b1.

* Temporarily delete cancel workflow

* Run 1

* Run 2

* Run 3

* Run 4

* Run 5

* Run 6

* Run 7

* Run 8

* Temporarily delete non-native workflows

* Bump reactivecircus/android-emulator-runner action to v2.15.0

* Run 1

* Run 2

* Run 3

* Run 4

* Run 5

* Run 6

* Run 7

* Run 8

* Run 9

* Run 10

* Run 11

* Run 12

* Run 13

* Run 14

* Run 15

* Run 16

* Revert "Temporarily delete cancel workflow"

This reverts commit b31695a.

* Revert "Temporarily delete non-native workflows"

This reverts commit ff5a3c4.

* Rename test file

Co-authored-by: Ceyhun Ozugur <ceyhunozugur@gmail.com>
  • Loading branch information
jd-alexander and ceyhun committed Mar 31, 2021
1 parent 87db183 commit 58dc864
Show file tree
Hide file tree
Showing 9 changed files with 180 additions and 79 deletions.
4 changes: 2 additions & 2 deletions .github/workflows/rnmobile-android-runner.yml
Original file line number Diff line number Diff line change
Expand Up @@ -11,7 +11,7 @@ jobs:
strategy:
matrix:
native-test-name: [
gutenberg-editor-gallery
gutenberg-editor-initial-html
]

steps:
Expand All @@ -35,7 +35,7 @@ jobs:
key: ${{ runner.os }}-gradle-${{ hashFiles('**/*.gradle') }}
restore-keys: ${{ runner.os }}-gradle

- uses: reactivecircus/android-emulator-runner@08b092e904025fada32a01b711af1e7ff7b7a4a3 # v2.14.3
- uses: reactivecircus/android-emulator-runner@d2799957d660add41c61a5103e2fbb9e2889eb73 # v2.15.0
with:
api-level: 28
profile: pixel_xl
Expand Down
2 changes: 1 addition & 1 deletion .github/workflows/rnmobile-ios-runner.yml
Original file line number Diff line number Diff line change
Expand Up @@ -12,7 +12,7 @@ jobs:
matrix:
xcode: [12.2]
native-test-name: [
gutenberg-editor-gallery
gutenberg-editor-initial-html
]

steps:
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,39 @@
/**
* Internal dependencies
*/
import initialHtml from '../src/initial-html';
import { isAndroid } from './helpers/utils';

describe( 'Gutenberg Editor Blocks test', () => {
it( 'should be able to create a post with all blocks and scroll to the last one', async () => {
await editorPage.setHtmlContent( initialHtml );

const lastBlockAccessibilityLabel =
'This block is used in initial HTML e2e tests and should be kept as the last block.';
let lastBlockElement;
if ( isAndroid() ) {
lastBlockElement = await editorPage.androidScrollAndReturnElement(
lastBlockAccessibilityLabel
);
} else {
lastBlockElement = await editorPage.getLastElementByXPath(
lastBlockAccessibilityLabel
);
if ( ! lastBlockElement ) {
const retryDelay = 5000;
// eslint-disable-next-line no-console
console.log(
`Warning: "lastBlockElement" was not found in the first attempt. Could be that all the blocks were not loaded yet.
Will retry one more time after ${ retryDelay / 1000 } seconds.`,
lastBlockElement
);
await editorPage.driver.sleep( retryDelay );
lastBlockElement = await editorPage.getLastElementByXPath(
lastBlockAccessibilityLabel
);
}
}

expect( lastBlockElement ).toBeTruthy();
} );
} );
Original file line number Diff line number Diff line change
Expand Up @@ -148,10 +148,8 @@ describe( 'Gutenberg Editor tests for Paragraph Block', () => {
}
} );

// Restricting these test to Android because I was not able to update the html on iOS
if ( isAndroid() ) {
it( 'should be able to merge blocks with unknown html elements', async () => {
await editorPage.setHtmlContent( `
it( 'should be able to merge blocks with unknown html elements', async () => {
await editorPage.setHtmlContent( `
<!-- wp:paragraph -->
<p><unknownhtmlelement>abc</unknownhtmlelement>D</p>
<!-- /wp:paragraph -->
Expand All @@ -160,61 +158,53 @@ describe( 'Gutenberg Editor tests for Paragraph Block', () => {
<p>E</p>
<!-- /wp:paragraph -->` );

// // Merge paragraphs
const secondParagraphBlockElement = await editorPage.getBlockAtPosition(
blockNames.paragraph,
2
);
await clickBeginningOfElement(
editorPage.driver,
secondParagraphBlockElement
);
await editorPage.typeTextToParagraphBlock(
secondParagraphBlockElement,
backspace
);

// verify the editor has not crashed
const text = await editorPage.getTextForParagraphBlockAtPosition(
1
);
expect( text.length ).not.toEqual( 0 );

await editorPage.removeBlockAtPosition( blockNames.paragraph );
} );

// Based on https://github.com/wordpress-mobile/gutenberg-mobile/pull/1507
it( 'should handle multiline paragraphs from web', async () => {
await editorPage.setHtmlContent( `
<!-- wp:paragraph -->
<p>multiple lines<br><br></p>
<!-- /wp:paragraph -->
<!-- wp:paragraph -->
<p></p>
<!-- /wp:paragraph -->` );

// // Merge paragraphs
const secondParagraphBlockElement = await editorPage.getBlockAtPosition(
blockNames.paragraph,
2
);
await clickBeginningOfElement(
editorPage.driver,
secondParagraphBlockElement
);
await editorPage.typeTextToParagraphBlock(
secondParagraphBlockElement,
backspace
);

// verify the editor has not crashed
const text = await editorPage.getTextForParagraphBlockAtPosition(
1
);
expect( text.length ).not.toEqual( 0 );

await editorPage.removeBlockAtPosition( blockNames.paragraph );
} );
}
// // Merge paragraphs
const secondParagraphBlockElement = await editorPage.getBlockAtPosition(
blockNames.paragraph,
2
);
await clickBeginningOfElement(
editorPage.driver,
secondParagraphBlockElement
);
await editorPage.typeTextToParagraphBlock(
secondParagraphBlockElement,
backspace
);

// verify the editor has not crashed
const text = await editorPage.getTextForParagraphBlockAtPosition( 1 );
expect( text.length ).not.toEqual( 0 );

await editorPage.removeBlockAtPosition( blockNames.paragraph );
} );

// Based on https://github.com/wordpress-mobile/gutenberg-mobile/pull/1507
it( 'should handle multiline paragraphs from web', async () => {
await editorPage.setHtmlContent( `
<!-- wp:paragraph -->
<p>multiple lines<br><br></p>
<!-- /wp:paragraph -->
<!-- wp:paragraph -->
<p></p>
<!-- /wp:paragraph -->` );

// // Merge paragraphs
const secondParagraphBlockElement = await editorPage.getBlockAtPosition(
blockNames.paragraph,
2
);
await secondParagraphBlockElement.click();
await editorPage.typeTextToParagraphBlock(
secondParagraphBlockElement,
backspace
);

// verify the editor has not crashed
const text = await editorPage.getTextForParagraphBlockAtPosition( 1 );
expect( text.length ).not.toEqual( 0 );

await editorPage.removeBlockAtPosition( blockNames.paragraph );
} );
} );
Original file line number Diff line number Diff line change
Expand Up @@ -70,6 +70,9 @@ describe( 'Gutenberg Editor paste tests', () => {

const text = await editorPage.getTextForParagraphBlockAtPosition( 2 );
expect( text ).toBe( testData.pastePlainText );

await editorPage.removeBlockAtPosition( blockNames.paragraph, 2 );
await editorPage.removeBlockAtPosition( blockNames.paragraph, 1 );
} );

it( 'copies styled text from one paragraph block and pastes in another', async () => {
Expand Down
6 changes: 3 additions & 3 deletions packages/react-native-editor/__device-tests__/helpers/caps.js
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,7 @@ const ios = {
os: 'iOS',
deviceOrientation: 'portrait',
automationName: 'XCUITest',
appiumVersion: '1.17.1', // Sauce Labs requires appiumVersion to be specified.
appiumVersion: '1.18.3', // Sauce Labs requires appiumVersion to be specified.
app: undefined, // will be set later, locally this is relative to root of project
processArguments: {
args: [ 'uitesting' ],
Expand All @@ -20,7 +20,7 @@ exports.iosLocal = {

exports.iosServer = {
...ios,
platformVersion: '13.4', // Supported Sauce Labs platforms can be found here: https://saucelabs.com/rest/v1/info/platforms/appium
platformVersion: '14.0', // Supported Sauce Labs platforms can be found here: https://saucelabs.com/rest/v1/info/platforms/appium
deviceName: 'iPhone 11 Simulator',
};

Expand All @@ -34,6 +34,6 @@ exports.android = {
appPackage: 'com.gutenberg',
appActivity: 'com.gutenberg.MainActivity',
deviceOrientation: 'portrait',
appiumVersion: '1.16.0',
appiumVersion: '1.18.1',
app: undefined,
};
43 changes: 32 additions & 11 deletions packages/react-native-editor/__device-tests__/helpers/utils.js
Original file line number Diff line number Diff line change
Expand Up @@ -172,7 +172,7 @@ const stopDriver = async ( driver ) => {
.createHmac( 'md5', jobID )
.update( serverConfigs.sauce.auth )
.digest( 'hex' );
const jobURL = `https://saucelabs.com/jobs/${ jobID }?auth=${ hash }.`;
const jobURL = `https://saucelabs.com/jobs/${ jobID }?auth=${ hash }`;
// eslint-disable-next-line no-console
console.log( `You can view the video of this test run at ${ jobURL }` );
}
Expand Down Expand Up @@ -218,15 +218,19 @@ const clearTextBox = async ( driver, element ) => {
// We are double tapping on the text field and pressing backspace until all content is removed.
do {
originalText = await element.text();
const action = new wd.TouchAction( driver );
action.tap( { el: element, count: 2 } );
await action.perform();
await doubleTap( driver, element );
await element.type( '\b' );
text = await element.text();
// We compare with the original content and not empty because text always return any hint set on the element.
} while ( originalText !== text );
};

const doubleTap = async ( driver, element ) => {
const action = new wd.TouchAction( driver );
action.tap( { el: element, count: 2 } );
await action.perform();
};

const typeStringAndroid = async (
driver,
element,
Expand Down Expand Up @@ -351,7 +355,12 @@ const tapPasteAboveElement = async ( driver, element ) => {

// Starts from the middle of the screen or the element(if specified)
// and swipes upwards
const swipeUp = async ( driver, element = undefined ) => {
const swipeUp = async (
driver,
element = undefined,
delay = 3000,
endYCoefficient = 0.5
) => {
let size = await driver.getWindowSize();
let y = 0;
if ( element !== undefined ) {
Expand All @@ -363,27 +372,33 @@ const swipeUp = async ( driver, element = undefined ) => {
const startX = size.width / 2;
const startY = y + size.height / 3;
const endX = startX;
const endY = startY + startY * -1 * 0.5;
const endY = startY + startY * -1 * endYCoefficient;

await swipeFromTo( driver, { x: startX, y: startY }, { x: endX, y: endY } );
await swipeFromTo(
driver,
{ x: startX, y: startY },
{ x: endX, y: endY },
delay
);
};

const defaultCoordinates = { x: 0, y: 0 };
const swipeFromTo = async (
driver,
from = defaultCoordinates,
to = defaultCoordinates
to = defaultCoordinates,
delay
) => {
const action = await new wd.TouchAction( driver );
action.press( from );
action.wait( 3000 );
action.wait( delay );
action.moveTo( to );
action.release();
await action.perform();
};

// Starts from the middle of the screen and swipes downwards
const swipeDown = async ( driver ) => {
const swipeDown = async ( driver, delay = 3000 ) => {
const size = await driver.getWindowSize();
const y = 0;

Expand All @@ -392,7 +407,12 @@ const swipeDown = async ( driver ) => {
const endX = startX;
const endY = startY - startY * -1 * 0.5;

await swipeFromTo( driver, { x: startX, y: startY }, { x: endX, y: endY } );
await swipeFromTo(
driver,
{ x: startX, y: startY },
{ x: endX, y: endY },
delay
);
};

const toggleHtmlMode = async ( driver, toggleOn ) => {
Expand Down Expand Up @@ -451,4 +471,5 @@ module.exports = {
stopDriver,
toggleHtmlMode,
toggleOrientation,
doubleTap,
};
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,7 @@ const {
toggleHtmlMode,
swipeFromTo,
longPressMiddleOfElement,
doubleTap,
} = require( '../helpers/utils' );

const initializeEditorPage = async () => {
Expand Down Expand Up @@ -135,6 +136,27 @@ class EditorPage {
return elements[ elements.length - 1 ];
}

// iOS loads the block list more eagerly compared to Android.
// This makes this function return elements without scrolling on iOS.
// So we are keeping this Android only.
async androidScrollAndReturnElement( accessibilityLabel ) {
const elements = await this.driver.elementsByXPath(
`//*[contains(@${ this.accessibilityIdXPathAttrib }, "${ accessibilityLabel }")]`
);
if ( elements.length === 0 ) {
await swipeUp( this.driver, undefined, 100, 1 );
return this.androidScrollAndReturnElement( accessibilityLabel );
}
return elements[ elements.length - 1 ];
}

async getLastElementByXPath( accessibilityLabel ) {
const elements = await this.driver.elementsByXPath(
`//*[contains(@${ this.accessibilityIdXPathAttrib }, "${ accessibilityLabel }")]`
);
return elements[ elements.length - 1 ];
}

async getTextViewForHtmlViewContent() {
const accessibilityId = 'html-view-content';
let blockLocator = `//*[@${ this.accessibilityIdXPathAttrib }="${ accessibilityId }"]`;
Expand All @@ -161,8 +183,30 @@ class EditorPage {
async setHtmlContent( html ) {
await toggleHtmlMode( this.driver, true );

const base64String = Buffer.from( html ).toString( 'base64' );

await this.driver.setClipboard( base64String, 'plaintext' );

const htmlContentView = await this.getTextViewForHtmlViewContent();
await htmlContentView.type( html );

if ( isAndroid() ) {
// Attention! On Android `.type()` replaces the content of htmlContentView instead of appending
// contrary to what iOS is doing. On Android tried calling `driver.pressKeycode( 279 ) // KEYCODE_PASTE`
// before to paste, but for some reason it didn't work on GitHub Actions but worked only on Sauce Labs
await htmlContentView.type( html );
} else {
await htmlContentView.click();
await doubleTap( this.driver, htmlContentView );
// Sometimes double tap is not enough for paste menu to appear, so we also long press
await longPressMiddleOfElement( this.driver, htmlContentView );

const pasteButton = this.driver.elementByXPath(
'//XCUIElementTypeMenuItem[@name="Paste"]'
);

await pasteButton.click();
await this.driver.sleep( 3000 ); // wait for paste notification to disappear
}

await toggleHtmlMode( this.driver, false );
}
Expand Down
Loading

0 comments on commit 58dc864

Please sign in to comment.