Skip to content

Commit c02cd9c

Browse files
authored
Revert "Revert "Include UI tests for blocks with Appium""
1 parent 2f145bf commit c02cd9c

28 files changed

+14804
-723
lines changed

.circleci/config.yml

+118-3
Original file line numberDiff line numberDiff line change
@@ -6,15 +6,45 @@ commands:
66
- restore_cache:
77
name: Restore Yarn Cache
88
keys:
9-
- yarn-{{ checksum "yarn.lock" }}
9+
- yarn-v2-{{ arch }}-{{ checksum "yarn.lock" }}
1010
- run:
1111
name: Yarn Install
12-
command: yarn install --frozen-lockfile
12+
command: YARN_CACHE_FOLDER=~/.cache/yarn yarn install --frozen-lockfile
1313
- save_cache:
1414
name: Save Yarn Cache
15-
key: yarn-{{ checksum "yarn.lock" }}
15+
key: yarn-v2-{{ arch }}-{{ checksum "yarn.lock" }}
1616
paths:
1717
- ~/.cache/yarn
18+
build-android:
19+
steps:
20+
- run:
21+
name: Change initial HTML file
22+
command: |
23+
cp ./bin/tmp/initial-device-tests-html.js ./src/app/initial-html.js
24+
- run:
25+
name: Bundle Debug android
26+
command: yarn bundle:android:test
27+
- run:
28+
name: Gradle assemble debug android apk
29+
command: |
30+
cd android
31+
./gradlew clean
32+
./gradlew assembleDebug
33+
build-ios:
34+
steps:
35+
- run:
36+
name: Change initial HTML file
37+
command: |
38+
cp ./bin/tmp/initial-device-tests-html.js ./src/app/initial-html.js
39+
- run:
40+
name: Bundle iOS
41+
command: |
42+
yarn bundle:ios:test
43+
- run:
44+
name: Generate .app file
45+
command: |
46+
set +e
47+
yarn react-native run-ios --configuration Release
1848
1949
jobs:
2050
checks:
@@ -47,6 +77,87 @@ jobs:
4777
- run:
4878
name: Run Checks
4979
command: bin/ci-checks-js.sh
80+
android-device-checks:
81+
docker:
82+
- image: circleci/android:api-27-node8-alpha
83+
steps:
84+
- checkout
85+
- run:
86+
name: Checkout Gutenberg
87+
command: |
88+
git submodule init
89+
git submodule update --recursive
90+
- yarn-install
91+
- run:
92+
name: Accept Licenses
93+
command: |
94+
yes | sdkmanager --licenses > /dev/null
95+
sleep 10
96+
sdkmanager --update
97+
background: true
98+
- run:
99+
name: Set Environment Variables
100+
command: |
101+
echo 'export TEST_RN_PLATFORM=android' >> $BASH_ENV
102+
echo 'export TEST_ENV=sauce' >> $BASH_ENV
103+
- build-android
104+
- run:
105+
name: Upload apk to sauce labs
106+
command: |
107+
curl -u "$SAUCE_USERNAME:$SAUCE_ACCESS_KEY" -X POST -H "Content-Type: application/octet-stream" https://saucelabs.com/rest/v1/storage/automattic/Gutenberg.apk?overwrite=true --data-binary @./android/app/build/outputs/apk/debug/app-debug.apk
108+
- run:
109+
name: Run Device Tests
110+
command: yarn device-tests
111+
ios-device-checks:
112+
macos:
113+
xcode: "10.0"
114+
steps:
115+
- checkout
116+
- run:
117+
name: Checkout Gutenberg
118+
command: |
119+
git submodule init
120+
git submodule update --recursive
121+
- run: yarn clean:install
122+
- run:
123+
name: Set Environment Variables
124+
command: |
125+
echo 'export TEST_RN_PLATFORM=ios' >> $BASH_ENV
126+
echo 'export TEST_ENV=sauce' >> $BASH_ENV
127+
- restore_cache:
128+
name: Restore Dependencies Cache
129+
keys:
130+
- dependencies-{{ checksum "react-native-aztec/ios/Cartfile.resolved" }}-{{
131+
checksum "yarn.lock" }}
132+
- dependencies-{{ checksum "react-native-aztec/ios/Cartfile.resolved" }}
133+
- dependencies-
134+
- run:
135+
name: Yarn preios
136+
command: yarn preios
137+
- save_cache:
138+
name: Save Dependencies Cache
139+
key: dependencies-{{ checksum "react-native-aztec/ios/Cartfile.resolved" }}-{{
140+
checksum "yarn.lock" }}
141+
paths:
142+
- react-native-aztec/ios/Carthage
143+
- ~/.rncache
144+
- build-ios
145+
- run:
146+
name: Zip up .app file
147+
command: |
148+
cp ./ios/main.jsbundle ./ios/build/Gutenberg/Build/Products/Release-iphonesimulator/gutenberg.app/main.jsbundle
149+
cd ./ios/build/Gutenberg/Build/Products/Release-iphonesimulator/
150+
zip -r ./Gutenberg.app.zip ./gutenberg.app
151+
mv ./Gutenberg.app.zip ../../../../../../Gutenberg.app.zip
152+
cd ../../../../../../
153+
- run:
154+
name: Upload .app to sauce labs
155+
command: |
156+
curl -u "$SAUCE_USERNAME:$SAUCE_ACCESS_KEY" -X POST -H "Content-Type: application/octet-stream" https://saucelabs.com/rest/v1/storage/automattic/Gutenberg.app.zip?overwrite=true --data-binary @./Gutenberg.app.zip
157+
- run:
158+
name: Run Device Tests
159+
command: |
160+
yarn device-tests
50161
51162
workflows:
52163
gutenberg-mobile:
@@ -62,3 +173,7 @@ workflows:
62173
name: Test Android
63174
platform: android
64175
check-tests: true
176+
- ios-device-checks:
177+
name: Test iOS on Device
178+
- android-device-checks:
179+
name: Test Android on Device

.gitignore

+6-1
Original file line numberDiff line numberDiff line change
@@ -23,6 +23,7 @@ yarn-error.log*
2323
*.apk
2424
*.ap_
2525
.gradle/
26+
android/app/src/main/assets/
2627

2728
# files for the dex VM
2829
*.dex
@@ -95,4 +96,8 @@ buck-out/
9596
!.vscode/extensions.json
9697

9798
*.pot
98-
bin/wp-cli.phar
99+
100+
# e2e output log
101+
appium-out.log
102+
103+
bin/wp-cli.phar

README.md

+17
Original file line numberDiff line numberDiff line change
@@ -107,6 +107,23 @@ Then, open `chrome://inspect` in Chrome to attach the debugger (look into the "R
107107

108108
This project is set up to use [jest](https://facebook.github.io/jest/) for tests. You can configure whatever testing strategy you like, but jest works out of the box. Create test files in directories called `__tests__` or with the `.test.js` extension to have the files loaded by jest. See an example test [here](https://github.com/wordpress-mobile/gutenberg-mobile/blob/develop/src/app/App.test.js). The [jest documentation](https://facebook.github.io/jest/docs/en/getting-started.html) is also a wonderful resource, as is the [React Native testing tutorial](https://facebook.github.io/jest/docs/en/tutorial-react-native.html).
109109

110+
## UI Tests
111+
112+
This repository uses appium to run UI tests. The tests live in `__device-tests__` and are written using Appium to run tests against simulators and real devices. To run these you'll need to check off a few things:
113+
114+
* For now you'll need run `yarn start`, and then either `yarn ios` or `yarn android` at least once before trying to run the tests on the respective platform
115+
* [Appium cli](https://github.com/appium/appium/blob/master/docs/en/about-appium/getting-started.md) installed and available globally, I'd also recommend using [appium doctor](https://github.com/appium/appium-doctor) to ensure all of appium's dependencies are good to go. You don't have to worry about starting the server yourself, the tests handle starting the server on port 4728, just be sure that the port is free or feel free to change the port number in the test file.
116+
* For iOS a simulator should automatically launch but for Android you'll need to have an emulator fired up and running.
117+
118+
After those are checked off to run the UI tests on ios run
119+
120+
`yarn test:ui:ios`
121+
122+
and for android run,
123+
124+
`yarn test:ui:android`
125+
126+
Alternatively if you're experiencing problems that seem to be related to the tests starting the appium server, for example errors that say `Connection Refused`, `Connection Reset` or `The requested environment is not available`. Sorry about that this is still a WIP, you can manually start the appium server via [appium desktop](https://github.com/appium/appium-desktop) or the cli, then change the port number in the tests while commenting out the `beforeAll` and `afterAll` block.
110127

111128
## Static analysis and code style
112129

Original file line numberDiff line numberDiff line change
@@ -0,0 +1,73 @@
1+
/** @flow
2+
* @format */
3+
4+
/**
5+
* External dependencies
6+
*/
7+
import wd from 'wd';
8+
/**
9+
* Internal dependencies
10+
*/
11+
import { isAndroid } from '../helpers/utils';
12+
13+
// Common code across used to interact with all blocks
14+
export default class BlockInteraction {
15+
driver: wd.PromiseChainWebdriver;
16+
accessibilityIdKey: string;
17+
name: string;
18+
blockName: string;
19+
element: wd.PromiseChainWebdriver.Element;
20+
accessibilityId: string;
21+
accessibilityIdXPathAttrib: string;
22+
static index = 0;
23+
24+
constructor( driver: wd.PromiseChainWebdriver, name: string = 'Unsupported Block' ) {
25+
this.driver = driver;
26+
this.accessibilityIdKey = 'name';
27+
this.accessibilityIdXPathAttrib = 'name';
28+
this.name = name;
29+
30+
if ( isAndroid() ) {
31+
this.accessibilityIdXPathAttrib = 'content-desc';
32+
this.accessibilityIdKey = 'contentDescription';
33+
}
34+
}
35+
36+
// Each subclass must include a method to do the following:
37+
// * Initialise the element and accessibilityId(By calling this.setupElement in most cases)
38+
// * Initialise any elements specific to interactions with that block
39+
async setup() {
40+
throw 'Unimplemented setup function for this block';
41+
}
42+
43+
async getAttribute( attributeName: string ) {
44+
return await this.element.getAttribute( attributeName );
45+
}
46+
47+
// Finds the wd element for new block that was added and sets the element attribute
48+
// and accessibilityId attributes on this object
49+
async setupElement( blockName: string, blocks: Set<string> ) {
50+
await this.driver.sleep( 2000 );
51+
const blockLocator = `block-${ BlockInteraction.index }-${ blockName }`;
52+
this.element = await this.driver.elementByAccessibilityId( blockLocator );
53+
this.accessibilityId = await this.getAttribute( this.accessibilityIdKey );
54+
55+
BlockInteraction.index += 1;
56+
return blocks;
57+
}
58+
59+
// attempts to type a string to a given element, need for this stems from
60+
// https://github.com/appium/appium/issues/12285#issuecomment-471872239
61+
// https://github.com/facebook/WebDriverAgent/issues/1084
62+
async typeString( element: wd.PromiseChainWebdriver.Element, str: string ) {
63+
await element.clear();
64+
if ( isAndroid() ) {
65+
return await element.type( str );
66+
}
67+
// iOS: Problem with Appium type function requiring me to do a little hacking to get it work,
68+
// as a result typing on iOS will be slower
69+
for ( let i = 0; i < str.length; i++ ) {
70+
await element.type( str.charAt( i ) );
71+
}
72+
}
73+
}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,56 @@
1+
/** @flow
2+
* @format */
3+
4+
/**
5+
* Internal dependencies
6+
*/
7+
import BlockInteraction from './block-interaction';
8+
/**
9+
* External dependencies
10+
*/
11+
import wd from 'wd';
12+
import { isAndroid } from '../helpers/utils';
13+
14+
/**
15+
* WordPress dependencies
16+
*/
17+
import { __ } from '@wordpress/i18n';
18+
19+
export default class ParagraphBlockInteraction extends BlockInteraction {
20+
// FLow complaining about type annotation on Set class here but Set<string>(); doesn't resolve
21+
// $FlowFixMe
22+
static blocks = new Set();
23+
textViewElement: wd.PromiseChainWebdriver.Element;
24+
25+
constructor( driver: wd.PromiseChainWebdriver ) {
26+
super( driver, __( 'Paragraph' ) );
27+
this.driver = driver;
28+
this.blockName = 'core/paragraph';
29+
}
30+
31+
// gets the TextView wd element for this paragraph block and sets it to
32+
// the textViewElement attribute for this object
33+
async setupTextView() {
34+
await this.driver.sleep( 2000 );
35+
let textViewElement = 'XCUIElementTypeTextView';
36+
if ( isAndroid() ) {
37+
textViewElement = 'android.widget.EditText';
38+
}
39+
const blockLocator = `//*[@${ this.accessibilityIdXPathAttrib }="${ this.accessibilityId }"]//${ textViewElement }`;
40+
this.textViewElement = await this.driver.elementByXPath( blockLocator );
41+
}
42+
43+
async setup() {
44+
await this.setupElement( this.blockName, ParagraphBlockInteraction.blocks );
45+
await this.setupTextView();
46+
}
47+
48+
async sendText( str: string ) {
49+
return await this.typeString( this.textViewElement, str );
50+
}
51+
52+
async getText() {
53+
const text = await this.textViewElement.text();
54+
return text.toString().trim();
55+
}
56+
}
+74
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,74 @@
1+
/**
2+
* @format
3+
* */
4+
5+
/**
6+
* Internal dependencies
7+
*/
8+
import EditorPage from './pages/editor-page';
9+
import ParagraphBlockInteraction from './blocks/paragraph-block-interaction';
10+
import { setupAppium, setupDriver, isLocalEnvironment, timer } from './helpers/utils';
11+
12+
jasmine.DEFAULT_TIMEOUT_INTERVAL = 120000;
13+
14+
describe( 'Gutenberg Editor tests', () => {
15+
let appium;
16+
let driver;
17+
let editorPage;
18+
let allPassed = true;
19+
20+
// Use reporter for setting status for saucelabs Job
21+
if ( ! isLocalEnvironment() ) {
22+
const reporter = {
23+
specDone: async ( result ) => {
24+
allPassed = allPassed && result.status !== 'failed';
25+
},
26+
};
27+
28+
jasmine.getEnv().addReporter( reporter );
29+
}
30+
31+
beforeAll( async () => {
32+
if ( isLocalEnvironment() ) {
33+
appium = await setupAppium();
34+
}
35+
36+
driver = await setupDriver();
37+
} );
38+
39+
it( 'should be able to see visual editor', async () => {
40+
editorPage = new EditorPage( driver );
41+
await editorPage.expect();
42+
} );
43+
44+
it( 'should be able to add a new Paragraph block', async () => {
45+
let paragraphBlockInteraction = new ParagraphBlockInteraction( driver );
46+
paragraphBlockInteraction = await editorPage.addNewBlock( paragraphBlockInteraction );
47+
await paragraphBlockInteraction.sendText( 'Hello Gutenberg!' );
48+
await timer( 3000 );
49+
expect( await paragraphBlockInteraction.getText() ).toBe( 'Hello Gutenberg!' );
50+
} );
51+
52+
afterAll( async () => {
53+
if ( isLocalEnvironment() ) {
54+
if ( driver === undefined ) {
55+
if ( appium !== undefined ) {
56+
await appium.kill( 'SIGINT' );
57+
}
58+
return;
59+
}
60+
61+
await driver.quit();
62+
await appium.kill( 'SIGINT' );
63+
} else {
64+
if ( driver === undefined ) {
65+
if ( appium !== undefined ) {
66+
await appium.kill( 'SIGINT' );
67+
}
68+
return;
69+
}
70+
driver.sauceJobStatus( allPassed );
71+
await driver.quit();
72+
}
73+
} );
74+
} );

0 commit comments

Comments
 (0)