-
Notifications
You must be signed in to change notification settings - Fork 665
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
Test automation starter #44
Changes from 2 commits
f4ec208
aebd2b2
4841f0f
9bd38a8
014061e
dcd75d5
30711f7
00d4fdb
a6abf1e
6836161
7ad5d75
309a3a7
f33e9d8
217b15b
700d476
4228a78
5d603ce
5e2f7d4
1d14e36
52a6235
fc234ea
b1a48a8
7bd4780
b226bb6
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,9 @@ | ||
*.iml | ||
.gradle | ||
/local.properties | ||
/.idea/workspace.xml | ||
/.idea/libraries | ||
.DS_Store | ||
/build | ||
/captures | ||
.externalNativeBuild |
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,31 @@ | ||
## Overview | ||
|
||
A sample retail store app that showcases current testing methodologies for Android. It comes with a simple Heroku server that can be run locally. | ||
|
||
The aim is to provide a hands-on introduction to those new to test engineering and a challenge for those who want to further improve the current implementation. | ||
|
||
|
||
## Getting started | ||
|
||
Before launching the app you'll need to start the server. Make sure you have Ruby installed by running `ruby -v` from the terminal. If it's not installed run `brew install ruby` | ||
|
||
Navigate to the `simplesinatraapi` directory in this project and run `ruby server.rb` | ||
|
||
You can check if the server is running by going to http://localhost:4567/ where you should see **Sinatra doesn’t know this ditty.** | ||
|
||
Now all you need to do is run the app from Android Studio! | ||
|
||
## Tests | ||
|
||
We have tests to validate both the UI and the JSON Schema. The first are in `src/androidTest/java/com/novoda/androidstoreexample` and the latter in `src/test/java/com/novoda/androidstoreexample` | ||
|
||
To find out more about our approach to UI tests, see https://docs.google.com/document/d/1S6f3tVwB0se1Xe3qc1Cv4I5ZVch-7lKJiuVgW_ktLkY/edit (to be replaced with wiki pages) | ||
|
||
## Current limitations | ||
|
||
* The content from the server can only be viewed on an emulator. | ||
|
||
* Right now we only have data for the "Hats" section. | ||
|
||
* The shopping basket is still work in progress. | ||
|
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1 @@ | ||
/build |
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,59 @@ | ||
apply plugin: 'com.android.application' | ||
apply plugin: 'kotlin-android' | ||
apply plugin: 'kotlin-android-extensions' | ||
apply plugin: 'kotlin-kapt' | ||
|
||
def API_URL = 'API_URL' | ||
def STRING = 'String' | ||
|
||
kapt { | ||
generateStubs = true | ||
} | ||
|
||
android { | ||
compileSdkVersion 26 | ||
defaultConfig { | ||
applicationId "com.novoda.testautomationstarter" | ||
minSdkVersion 24 | ||
targetSdkVersion 26 | ||
versionCode 1 | ||
versionName "1.0" | ||
buildConfigField STRING, API_URL, '"http://10.0.2.2:4567"' | ||
testInstrumentationRunner "com.novoda.androidstoreexample.TestRunner" | ||
} | ||
buildTypes { | ||
release { | ||
minifyEnabled false | ||
proguardFiles getDefaultProguardFile('proguard-android.txt'), 'proguard-rules.pro' | ||
} | ||
} | ||
|
||
} | ||
|
||
dependencies { | ||
implementation fileTree(include: ['*.jar'], dir: 'libs') | ||
implementation "org.jetbrains.kotlin:kotlin-stdlib-jre7:$kotlin_version" | ||
implementation 'com.android.support:appcompat-v7:26.1.0' | ||
implementation 'com.android.support.constraint:constraint-layout:1.0.2' | ||
implementation 'com.android.support:recyclerview-v7:26.1.0' | ||
kapt 'com.google.dagger:dagger-compiler:2.11' | ||
implementation 'com.google.dagger:dagger:2.11' | ||
implementation 'com.squareup.retrofit2:retrofit:2.3.0' | ||
implementation 'com.squareup.retrofit2:converter-moshi:2.3.0' | ||
implementation 'com.squareup.retrofit2:adapter-rxjava2:2.3.0' | ||
testImplementation 'junit:junit:4.12' | ||
testImplementation 'io.rest-assured:rest-assured:3.0.6' | ||
testImplementation 'io.rest-assured:json-schema-validator:3.0.6' | ||
androidTestImplementation 'com.android.support.test:runner:1.0.1' | ||
androidTestImplementation 'com.android.support.test.espresso:espresso-core:3.0.1' | ||
androidTestImplementation 'com.squareup.okhttp:mockwebserver:1.3.0' | ||
androidTestImplementation 'org.mockito:mockito-core:2.8.9' | ||
androidTestImplementation('com.android.support.test.espresso:espresso-contrib:3.0.1') { | ||
exclude module: 'support-annotations' | ||
exclude module: 'support-v4' | ||
exclude module: 'design' | ||
exclude module: 'recyclerview-v7' | ||
} | ||
androidTestImplementation 'io.cucumber:cucumber-java8:2.3.1' | ||
androidTestImplementation 'io.cucumber:cucumber-junit:2.3.1' | ||
} |
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,21 @@ | ||
# Add project specific ProGuard rules here. | ||
# You can control the set of applied configuration files using the | ||
# proguardFiles setting in build.gradle. | ||
# | ||
# For more details, see | ||
# http://developer.android.com/guide/developing/tools/proguard.html | ||
|
||
# If your project uses WebView with JS, uncomment the following | ||
# and specify the fully qualified class name to the JavaScript interface | ||
# class: | ||
#-keepclassmembers class fqcn.of.javascript.interface.for.webview { | ||
# public *; | ||
#} | ||
|
||
# Uncomment this to preserve the line number information for | ||
# debugging stack traces. | ||
#-keepattributes SourceFile,LineNumberTable | ||
|
||
# If you keep the line number information, uncomment this to | ||
# hide the original source file name. | ||
#-renamesourcefileattribute SourceFile |
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,16 @@ | ||
{ | ||
"categories": [ | ||
{ | ||
"title": "SHIRTS", | ||
"image": "shirtimage" | ||
}, | ||
{ | ||
"title": "Thingies to wear", | ||
"image": "hoodieimage" | ||
}, | ||
{ | ||
"title": "HATS", | ||
"image": "hatimage" | ||
} | ||
] | ||
} |
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,10 @@ | ||
Feature: Product Category list | ||
|
||
Scenario: User can see the Product Category list | ||
Given The user has launched the app | ||
Then The user can see the following Product Categories: | ||
| Shirts | | ||
| Hoodies | | ||
| Hats | | ||
| Digital Goods | | ||
|
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,10 @@ | ||
package com.novoda.androidstoreexample | ||
|
||
import com.novoda.androidstoreexample.dagger.module.HostModule | ||
|
||
|
||
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. Empty line |
||
class EspressoHostModule(val port: Int): HostModule(){ | ||
override fun provideBaseUrl():String { | ||
return "http://127.0.0.1:$port" | ||
} | ||
} |
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,13 @@ | ||
package com.novoda.androidstoreexample | ||
|
||
import cucumber.api.CucumberOptions | ||
import cucumber.api.junit.Cucumber | ||
import org.junit.runner.RunWith | ||
|
||
@RunWith(Cucumber::class) | ||
@CucumberOptions( | ||
features = ["/src/androidTest/assets/features"], | ||
tags = ["not @ignored"]) | ||
|
||
class RunCucumberTest | ||
|
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,44 @@ | ||
package com.novoda.androidstoreexample | ||
|
||
import android.support.test.espresso.matcher.BoundedMatcher | ||
import android.support.v7.widget.RecyclerView | ||
import com.novoda.androidstoreexample.adapters.CategoryAdapter | ||
import com.novoda.androidstoreexample.adapters.ProductListAdapter | ||
import org.hamcrest.Description | ||
import org.hamcrest.Matcher | ||
|
||
object ViewMatchers { | ||
|
||
@JvmStatic | ||
fun withCategoryTitle(title: String): Matcher<RecyclerView.ViewHolder> { | ||
return object : BoundedMatcher<RecyclerView.ViewHolder, CategoryAdapter.Holder>(CategoryAdapter.Holder::class.java) { | ||
override fun matchesSafely(item: CategoryAdapter.Holder): Boolean { | ||
if (item.categoryName != null) { | ||
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. you can do |
||
return item.categoryName!!.text.toString().equals(title, true) | ||
} | ||
return false | ||
} | ||
|
||
override fun describeTo(description: Description) { | ||
description.appendText("view holder with title: " + title) | ||
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. you can do |
||
} | ||
} | ||
} | ||
|
||
@JvmStatic | ||
fun withProductTitle(title: String): Matcher<RecyclerView.ViewHolder> { | ||
return object : BoundedMatcher<RecyclerView.ViewHolder, ProductListAdapter.Holder>(ProductListAdapter.Holder::class.java) { | ||
override fun describeTo(description: Description) { | ||
description.appendText("view holder with title: " + title) | ||
} | ||
|
||
override fun matchesSafely(item: ProductListAdapter.Holder?): Boolean { | ||
if (item?.productName != null) { | ||
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. you can use the let here (as before) |
||
return item.productName!!.text.toString().equals(title, true) | ||
} | ||
return false | ||
} | ||
} | ||
} | ||
|
||
} |
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,19 @@ | ||
package com.novoda.androidstoreexample.pageobjects | ||
|
||
import android.support.test.espresso.Espresso.onView | ||
import android.support.test.espresso.ViewInteraction | ||
import android.support.test.espresso.assertion.ViewAssertions.matches | ||
import android.support.test.espresso.matcher.ViewMatchers.isDisplayed | ||
import android.support.test.espresso.matcher.ViewMatchers.withId | ||
import com.novoda.androidstoreexample.R | ||
|
||
|
||
class HeaderPageObject { | ||
private val HEADER: ViewInteraction = onView(withId(R.id.action_bar_container)) | ||
|
||
fun verifyHeader() { | ||
HEADER.check(matches(isDisplayed())) | ||
} | ||
|
||
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. Empty line |
||
} | ||
|
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,27 @@ | ||
package com.novoda.androidstoreexample.pageobjects | ||
|
||
import android.support.test.espresso.Espresso.onView | ||
import android.support.test.espresso.action.ViewActions.click | ||
import android.support.test.espresso.assertion.ViewAssertions.matches | ||
import android.support.test.espresso.contrib.RecyclerViewActions.actionOnHolderItem | ||
import android.support.test.espresso.matcher.ViewMatchers.isDisplayed | ||
import android.support.test.espresso.matcher.ViewMatchers.withId | ||
import com.novoda.androidstoreexample.R | ||
import com.novoda.androidstoreexample.ViewMatchers | ||
|
||
class MainActivityPageObject { | ||
|
||
private val TITLE = onView(withId(R.id.titleTextView)) | ||
|
||
fun verifyTitle() { | ||
TITLE.check(matches(isDisplayed())) | ||
} | ||
|
||
fun navigateToProductList() { | ||
val categoryMatcher = ViewMatchers.withCategoryTitle("HATS") | ||
|
||
onView(withId(R.id.categoryListView)).perform( actionOnHolderItem(categoryMatcher, click())) | ||
} | ||
|
||
} | ||
|
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,14 @@ | ||
package com.novoda.androidstoreexample.pageobjects | ||
|
||
import android.support.test.espresso.Espresso.onView | ||
import android.support.test.espresso.assertion.ViewAssertions.matches | ||
import android.support.test.espresso.matcher.ViewMatchers.isDisplayed | ||
import android.support.test.espresso.matcher.ViewMatchers.withId | ||
import com.novoda.androidstoreexample.R | ||
|
||
class ProductDetailsPageObject { | ||
|
||
fun assertProductDetailsDisplayed() { | ||
onView(withId(R.id.productDetailDescription)).check(matches(isDisplayed())) | ||
} | ||
} |
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,19 @@ | ||
package com.novoda.androidstoreexample.pageobjects | ||
|
||
import android.support.test.espresso.Espresso.onView | ||
import android.support.test.espresso.action.ViewActions | ||
import android.support.test.espresso.contrib.RecyclerViewActions | ||
import android.support.test.espresso.matcher.ViewMatchers.withId | ||
import com.novoda.androidstoreexample.R | ||
import com.novoda.androidstoreexample.ViewMatchers | ||
|
||
|
||
class ProductListPageObject { | ||
|
||
fun navigateToProductDetails() { | ||
val productMatcher = ViewMatchers.withProductTitle("hat white") | ||
|
||
onView(withId(R.id.productListView)).perform( RecyclerViewActions.actionOnHolderItem(productMatcher, ViewActions.click())) | ||
} | ||
|
||
} |
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,30 @@ | ||
package com.novoda.androidstoreexample.stepdefs | ||
|
||
import cucumber.api.DataTable | ||
import cucumber.api.java8.En | ||
import java.lang.Thread.sleep | ||
|
||
class StepDefs: En { | ||
|
||
init { | ||
Given("^The user has launched the app$") { | ||
println("banana") | ||
fun launch(){ | ||
sleep(1000) | ||
|
||
} | ||
// Write code here that turns the phrase above into concrete actions | ||
// throw PendingException() | ||
} | ||
|
||
Then("^The user can see the following Product Categories:$") { arg1: DataTable -> | ||
// Write code here that turns the phrase above into concrete actions | ||
// For automatic transformation, change DataTable to one of | ||
// List<YourType>, List<List<E>>, List<Map<K,V>> or Map<K,V>. | ||
// E,K,V must be a scalar (String, Integer, Date, enum etc) | ||
assert(true) | ||
// throw PendingException() | ||
} | ||
|
||
} | ||
} |
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,44 @@ | ||
package com.novoda.androidstoreexample.tests; | ||
|
||
import android.support.test.rule.ActivityTestRule; | ||
import android.support.test.runner.AndroidJUnit4; | ||
import android.support.v7.widget.RecyclerView; | ||
|
||
import com.novoda.androidstoreexample.R; | ||
import com.novoda.androidstoreexample.ViewMatchers; | ||
import com.novoda.androidstoreexample.activities.MainActivity; | ||
|
||
import org.hamcrest.Matcher; | ||
import org.junit.Rule; | ||
import org.junit.Test; | ||
import org.junit.runner.RunWith; | ||
|
||
import static android.support.test.espresso.Espresso.onView; | ||
import static android.support.test.espresso.action.ViewActions.click; | ||
import static android.support.test.espresso.assertion.ViewAssertions.matches; | ||
import static android.support.test.espresso.contrib.RecyclerViewActions.actionOnHolderItem; | ||
import static android.support.test.espresso.contrib.RecyclerViewActions.scrollToHolder; | ||
import static android.support.test.espresso.matcher.ViewMatchers.isDisplayed; | ||
import static android.support.test.espresso.matcher.ViewMatchers.withId; | ||
|
||
@RunWith(AndroidJUnit4.class) | ||
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. this isn't required anymore btw (doesn't do anything) 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. Nice, tbh the whole test is redundant by now but we can leave it to have a Java/Kotlin side by side example (also demonstrating the difference between using or not using page objects) |
||
public class EspressoJavaTestExample { | ||
|
||
@Rule | ||
public ActivityTestRule<MainActivity> activityTestRule = new ActivityTestRule<MainActivity>(MainActivity.class); | ||
|
||
@Test | ||
public void firstNavigationTest() { | ||
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 saw some page objects before, how come this test uses directly espresso? 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. Just replied in the previous comment before seeing this one 😅 |
||
Matcher<RecyclerView.ViewHolder> categoryMatcher = ViewMatchers.withCategoryTitle("HATS"); | ||
|
||
onView(android.support.test.espresso.matcher.ViewMatchers.withId(R.id.categoryListView)).perform(scrollToHolder(categoryMatcher), actionOnHolderItem(categoryMatcher, click())); | ||
|
||
Matcher<RecyclerView.ViewHolder> productMatcher = ViewMatchers.withProductTitle("hat white"); | ||
|
||
onView(withId(R.id.productListView)).perform(scrollToHolder(productMatcher), actionOnHolderItem(productMatcher, click())); | ||
|
||
onView(withId(R.id.productDetailDescription)).check(matches(isDisplayed())); | ||
|
||
} | ||
|
||
} |
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.
Shall we also add the information on how to start the server or at least point to the website how to do that?
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.
It's the next line, where it says
Navigate to the simplesinatraapi directory in this project and run ruby server.rb
. The directory is included in the project.