- mid commit

This commit is contained in:
2023-05-15 19:28:11 +01:00
parent e5556cdb8d
commit b12af77a31
17 changed files with 384 additions and 47 deletions

View File

@@ -5,7 +5,6 @@ import android.app.Instrumentation
import android.content.Intent
import android.content.res.Resources
import android.net.Uri
import android.provider.MediaStore
import android.widget.DatePicker
import androidx.annotation.StringRes
import androidx.test.espresso.Espresso.onData
@@ -19,12 +18,12 @@ import androidx.test.espresso.intent.Intents
import androidx.test.espresso.intent.Intents.intending
import androidx.test.espresso.intent.matcher.IntentMatchers
import androidx.test.espresso.intent.matcher.IntentMatchers.hasAction
import androidx.test.espresso.intent.matcher.IntentMatchers.isInternal
import androidx.test.espresso.matcher.ViewMatchers
import androidx.test.espresso.matcher.ViewMatchers.withId
import h_mal.appttude.com.driver.helpers.DataHelper
import h_mal.appttude.com.driver.helpers.DataHelper.addFilePaths
import org.hamcrest.CoreMatchers.*
import h_mal.appttude.com.driver.helpers.EspressoHelper.waitForView
import org.hamcrest.CoreMatchers.allOf
import org.hamcrest.CoreMatchers.anything
import org.hamcrest.Matchers
import java.io.File
@@ -41,6 +40,8 @@ open class BaseTestRobot {
fun matchView(resId: Int): ViewInteraction = onView(withId(resId))
fun matchViewWaitFor(resId: Int): ViewInteraction = waitForView(withId(resId))
fun matchText(viewInteraction: ViewInteraction, text: String): ViewInteraction = viewInteraction
.check(matches(ViewMatchers.withText(text)))
@@ -55,6 +56,9 @@ open class BaseTestRobot {
fun checkErrorOnTextEntry(resId: Int, errorMessage: String): ViewInteraction =
onView(withId(resId)).check(matches(checkErrorMessage(errorMessage)))
fun checkImageViewHasImage(resId: Int): ViewInteraction =
onView(withId(resId)).check(matches(checkImage()))
fun swipeDown(resId: Int): ViewInteraction =
onView(withId(resId)).perform(swipeDown())
@@ -71,12 +75,12 @@ open class BaseTestRobot {
)
}
fun selectSingleImageFromGallery(filePath: String, openSelector: () -> Unit) {
fun selectSingleImageFromGallery(filePath: FormRobot.FilePath, openSelector: () -> Unit) {
Intents.init()
// Build the result to return when the activity is launched.
val resultData = Intent()
resultData.flags = Intent.FLAG_GRANT_READ_URI_PERMISSION
resultData.data = Uri.fromFile(File(filePath))
resultData.data = Uri.fromFile(File(FormRobot.FilePath.getFilePath(filePath)))
val result = Instrumentation.ActivityResult(Activity.RESULT_OK, resultData)
// Set up result stubbing when an intent sent to image picker is seen.
intending(hasAction(Intent.ACTION_GET_CONTENT)).respondWith(result)
@@ -87,17 +91,15 @@ open class BaseTestRobot {
fun selectMultipleImageFromGallery(filePaths: Array<String>, openSelector: () -> Unit) {
Intents.init()
openSelector()
// Build the result to return when the activity is launched.
val resultData = Intent()
val clipData = DataHelper.createClipData(filePaths[0])
val remainingFiles = filePaths.copyOfRange(1, filePaths.size-1)
clipData.addFilePaths(remainingFiles)
val clipData = DataHelper.createClipData(filePaths)
resultData.clipData = clipData
val result = Instrumentation.ActivityResult(Activity.RESULT_OK, resultData)
// Set up result stubbing when an intent sent to "contacts" is seen.
Intents.intending(IntentMatchers.toPackage("android.intent.action.PICK")).respondWith(result)
intending(IntentMatchers.toPackage("android.intent.action.PICK")).respondWith(result)
openSelector()
Intents.release()
}
}

View File

@@ -1,7 +1,9 @@
package h_mal.appttude.com.driver
import android.graphics.drawable.BitmapDrawable
import android.view.View
import android.widget.EditText
import android.widget.ImageView
import com.google.android.material.textfield.TextInputLayout
import org.hamcrest.Description
import org.hamcrest.Matcher
@@ -11,7 +13,7 @@ import org.hamcrest.TypeSafeMatcher
/**
* Matcher for testing error of TextInputLayout
*/
fun checkErrorMessage(expectedErrorText: String): Matcher<View?>? {
fun checkErrorMessage(expectedErrorText: String): Matcher<View?> {
return object : TypeSafeMatcher<View?>() {
override fun matchesSafely(view: View?): Boolean {
if (view is EditText) {
@@ -28,3 +30,25 @@ fun checkErrorMessage(expectedErrorText: String): Matcher<View?>? {
}
}
fun checkImage(): Matcher<View?> {
return object : TypeSafeMatcher<View?>() {
override fun matchesSafely(view: View?): Boolean {
if (view is ImageView) {
return hasImage(view)
}
return false
}
override fun describeTo(d: Description?) {}
private fun hasImage(view: ImageView): Boolean {
val drawable = view.drawable
var hasImage = drawable != null
if (hasImage && drawable is BitmapDrawable) {
hasImage = drawable.bitmap != null
}
return hasImage
}
}
}

View File

@@ -1,9 +1,9 @@
package h_mal.appttude.com.driver
import android.net.Uri
import androidx.test.espresso.Espresso.onView
import androidx.test.espresso.action.ViewActions.click
import androidx.test.espresso.matcher.ViewMatchers.withId
import h_mal.appttude.com.driver.helpers.getImagePath
open class FormRobot : BaseTestRobot() {
@@ -15,7 +15,7 @@ open class FormRobot : BaseTestRobot() {
onView(withId(android.R.id.button1)).perform(click())
}
fun selectSingleImage(imagePickerLauncherViewId: Int, filePath: String) {
fun selectSingleImage(imagePickerLauncherViewId: Int, filePath: FilePath) {
selectSingleImageFromGallery(filePath) {
onView(withId(imagePickerLauncherViewId)).perform(click())
}
@@ -27,4 +27,20 @@ open class FormRobot : BaseTestRobot() {
onView(withId(imagePickerLauncherViewId)).perform(click())
}
}
enum class FilePath(val path: String) {
PROFILE_PIC("driver_profile_pic.jpg"),
INSURANCE("driver_insurance.jpg"),
PRIVATE_HIRE("driver_license_private_hire.jpg"),
PRIVATE_HIRE_CAR("driver_license_private_hire_car.jpg"),
LOGBOOK("driver_logbook.jpg"),
MOT("driver_mot.jpg"),
LICENSE("driver_license_driver.jpg");
companion object {
fun getFilePath(filePath: FilePath): String {
return getImagePath(filePath.path)
}
}
}
}

View File

@@ -6,15 +6,9 @@ import java.io.File
/**
* File paths for images on device
*/
private const val BASE = "/Camera/images/"
const val PROFILE_PIC = "${BASE}driver_profile_pic.jpg"
const val INSURANCE = "${BASE}driver_insurance.jpg"
const val PRIVATE_HIRE = "${BASE}driver_license_private_hire.jpg"
const val PRIVATE_HIRE_CAR = "${BASE}driver_license_private_hire_car.jpg"
const val LOGBOOK = "${BASE}driver_logbook.jpg"
const val MOT = "${BASE}driver_mot.jpg"
const val LICENSE = "${BASE}driver_license_driver.jpg"
fun getImagePath(imageConst: String): String {
return File(Environment.getExternalStoragePublicDirectory(Environment.DIRECTORY_DCIM), imageConst).absolutePath
return File(
Environment.getExternalStoragePublicDirectory(Environment.DIRECTORY_DCIM),
"/Camera/images/$imageConst"
).absolutePath
}

View File

@@ -2,20 +2,29 @@ package h_mal.appttude.com.driver.helpers
import android.content.ClipData
import android.content.ClipData.Item
import android.content.ClipDescription
import android.net.Uri
import java.io.File
object DataHelper {
fun createClipItem(filePath: String) = Item(
Uri.fromFile(File(filePath)
))
Uri.fromFile(
File(filePath)
)
)
fun createClipData(item: Item, mimeType: String = "text/uri-list") = ClipData(null, arrayOf(mimeType), item)
fun createClipData(item: Item, mimeType: String = "text/uri-list") =
ClipData(null, arrayOf(mimeType), item)
fun createClipData(filePath: String) = createClipData(createClipItem(filePath))
fun createClipData(filePaths: Array<String>): ClipData {
val clipData = createClipData(filePaths[0])
val remainingFiles = filePaths.copyOfRange(1, filePaths.size - 1)
clipData.addFilePaths(remainingFiles)
return clipData
}
fun createClipData(uri: Uri) = createClipData(Item(uri))
fun ClipData.addFilePaths(filePaths: Array<String>) {

View File

@@ -0,0 +1,123 @@
package h_mal.appttude.com.driver.helpers
import android.os.SystemClock.sleep
import android.view.View
import android.widget.CheckBox
import android.widget.Checkable
import androidx.test.espresso.Espresso.onView
import androidx.test.espresso.NoMatchingViewException
import androidx.test.espresso.UiController
import androidx.test.espresso.ViewAction
import androidx.test.espresso.ViewInteraction
import androidx.test.espresso.matcher.ViewMatchers.isRoot
import androidx.test.espresso.util.TreeIterables
import org.hamcrest.BaseMatcher
import org.hamcrest.CoreMatchers.isA
import org.hamcrest.Description
import org.hamcrest.Matcher
object EspressoHelper {
/**
* Perform action of waiting for a certain view within a single root view
* @param viewMatcher Generic Matcher used to find our view
*/
fun searchFor(viewMatcher: Matcher<View>): ViewAction {
return object : ViewAction {
override fun getConstraints(): Matcher<View> = isRoot()
override fun getDescription(): String {
return "searching for view $this in the root view"
}
override fun perform(uiController: UiController, view: View) {
var tries = 0
val childViews: Iterable<View> = TreeIterables.breadthFirstViewTraversal(view)
// Look for the match in the tree of childviews
childViews.forEach {
tries++
if (viewMatcher.matches(it)) {
// found the view
return
}
}
throw NoMatchingViewException.Builder()
.withRootView(view)
.withViewMatcher(viewMatcher)
.build()
}
}
}
/**
* Performs an action to check/uncheck a checkbox
*
*/
fun setChecked(checked: Boolean): ViewAction {
return object : ViewAction {
override fun getConstraints(): BaseMatcher<View> {
return object : BaseMatcher<View>() {
override fun describeTo(description: Description?) {}
override fun matches(actual: Any?): Boolean {
return isA(CheckBox::class.java).matches(actual)
}
}
}
override fun getDescription(): String {
return ""
}
override fun perform(uiController: UiController, view: View) {
val checkableView = view as Checkable
checkableView.isChecked = checked
}
}
}
/**
* Perform action of implicitly waiting for a certain view.
* This differs from EspressoExtensions.searchFor in that,
* upon failure to locate an element, it will fetch a new root view
* in which to traverse searching for our @param match
*
* @param viewMatcher ViewMatcher used to find our view
*/
fun waitForView(
viewMatcher: Matcher<View>,
waitMillis: Int = 5000,
waitMillisPerTry: Long = 100
): ViewInteraction {
// Derive the max tries
val maxTries = waitMillis / waitMillisPerTry.toInt()
var tries = 0
for (i in 0..maxTries)
try {
// Track the amount of times we've tried
tries++
// Search the root for the view
onView(isRoot()).perform(searchFor(viewMatcher))
// If we're here, we found our view. Now return it
return onView(viewMatcher)
} catch (e: Exception) {
if (tries == maxTries) {
throw e
}
sleep(waitMillisPerTry)
}
throw Exception("Error finding a view matching $viewMatcher")
}
}

View File

@@ -9,7 +9,7 @@ import h_mal.appttude.com.driver.R
fun home(func: HomeRobot.() -> Unit) = HomeRobot().apply { func() }
class HomeRobot : BaseTestRobot() {
fun checkTitleExists(title: String) = matchText(R.id.prova_title_tv, title)
fun checkTitleExists(title: String) = matchText(matchViewWaitFor(R.id.prova_title_tv), title)
fun openDrawer() {
onView(withId(R.id.drawer_layout)).perform(DrawerActions.open())

View File

@@ -2,13 +2,12 @@ package h_mal.appttude.com.driver.robots
import h_mal.appttude.com.driver.FormRobot
import h_mal.appttude.com.driver.R
import h_mal.appttude.com.driver.helpers.PROFILE_PIC
fun updateProfile(func: UpdateProfileRobot.() -> Unit) = UpdateProfileRobot().apply { func() }
class UpdateProfileRobot : FormRobot() {
fun enterName(name: String) = fillEditText(R.id.update_name, name)
// fun selectImage() = selectSingleImage(R.id.profile_img, PROFILE_PIC)
fun selectImage() = selectSingleImage(R.id.profile_img, FilePath.PROFILE_PIC)
fun submitForm(name: String) {
// selectImage()

View File

@@ -1,16 +1,16 @@
package h_mal.appttude.com.driver.robots
package h_mal.appttude.com.driver.robots.driver
import h_mal.appttude.com.driver.FormRobot
import h_mal.appttude.com.driver.R
import h_mal.appttude.com.driver.helpers.LICENSE
import h_mal.appttude.com.driver.helpers.getImagePath
fun driversLicense(func: DriversLicenseRobot.() -> Unit) = DriversLicenseRobot().apply { func() }
class DriversLicenseRobot : FormRobot() {
fun enterLicenseNumber(text: String) = fillEditText(R.id.lic_no, text)
fun enterLicenseExpiry(year: Int, monthOfYear: Int, dayOfMonth: Int) = setDate(R.id.lic_expiry, year, monthOfYear, dayOfMonth)
fun selectImage() = selectSingleImage(R.id.search_image, getImagePath(LICENSE))
fun enterLicenseExpiry(year: Int, monthOfYear: Int, dayOfMonth: Int) =
setDate(R.id.lic_expiry, year, monthOfYear, dayOfMonth)
fun selectImage() = selectSingleImage(R.id.search_image, FilePath.LICENSE)
fun submitForm(licenseNumber: String, year: Int, monthOfYear: Int, dayOfMonth: Int) {
selectImage()
@@ -18,4 +18,10 @@ class DriversLicenseRobot : FormRobot() {
enterLicenseExpiry(year, monthOfYear, dayOfMonth)
submit()
}
fun validate() {
checkImageViewHasImage(R.id.driversli_img)
}
}

View File

@@ -1,6 +1,5 @@
package h_mal.appttude.com.driver.robots
package h_mal.appttude.com.driver.robots.driver
import android.text.InputType
import h_mal.appttude.com.driver.FormRobot
import h_mal.appttude.com.driver.R
@@ -12,15 +11,28 @@ class DriversProfileRobot : FormRobot() {
fun enterPostcode(postcode: String) = fillEditText(R.id.postcode_input, postcode)
fun enterDateOfBirth(dob: String) = fillEditText(R.id.dob_input, dob)
fun enterNINumber(niNumber: String) = fillEditText(R.id.ni_number, niNumber)
fun enterDateFirstAvailable(year: Int, monthOfYear: Int, dayOfMonth: Int) = setDate(R.id.date_first, year, monthOfYear, dayOfMonth)
fun enterDateFirstAvailable(year: Int, monthOfYear: Int, dayOfMonth: Int) =
setDate(R.id.date_first, year, monthOfYear, dayOfMonth)
fun selectImage() = clickButton(R.id.add_photo)
fun selectImage() = selectSingleImage(R.id.add_photo, FilePath.PROFILE_PIC)
fun submitForm(name: String, address: String, postcode: String, dob: String, niNumber: String, year: Int, monthOfYear: Int, dayOfMonth: Int) {
fun submitForm(
name: String,
address: String,
postcode: String,
dob: String,
niNumber: String,
year: Int,
monthOfYear: Int,
dayOfMonth: Int
) {
selectImage()
// TODO: select image in gallery
enterName(name)
enterAddress(address)
enterPostcode(postcode)
enterDateOfBirth(dob)
enterNINumber(niNumber)
enterDateFirstAvailable(year, monthOfYear, dayOfMonth)
submit()
}
}

View File

@@ -0,0 +1,23 @@
package h_mal.appttude.com.driver.robots.driver
import h_mal.appttude.com.driver.FormRobot
import h_mal.appttude.com.driver.R
fun privateHireLicenseRobot(func: PrivateHireLicenseRobot.() -> Unit) =
PrivateHireLicenseRobot().apply { func() }
class PrivateHireLicenseRobot : FormRobot() {
fun enterLicenseNumber(text: String) = fillEditText(R.id.ph_no, text)
fun enterLicenseExpiry(year: Int, monthOfYear: Int, dayOfMonth: Int) =
setDate(R.id.ph_expiry, year, monthOfYear, dayOfMonth)
fun selectImage() = selectSingleImage(R.id.uploadphlic, FilePath.PRIVATE_HIRE)
fun submitForm(licenseNumber: String, year: Int, monthOfYear: Int, dayOfMonth: Int) {
selectImage()
enterLicenseNumber(licenseNumber)
enterLicenseExpiry(year, monthOfYear, dayOfMonth)
submit()
}
}

View File

@@ -0,0 +1,23 @@
package h_mal.appttude.com.driver.robots.vehicle
import h_mal.appttude.com.driver.FormRobot
import h_mal.appttude.com.driver.FormRobot.FilePath.Companion.getFilePath
import h_mal.appttude.com.driver.R
fun insurance(func: InsuranceRobot.() -> Unit) = InsuranceRobot().apply { func() }
class InsuranceRobot : FormRobot() {
fun enterInsurance(text: String) = fillEditText(R.id.insurer, text)
fun enterInsuranceExpiry(year: Int, monthOfYear: Int, dayOfMonth: Int) =
setDate(R.id.insurance_exp, year, monthOfYear, dayOfMonth)
fun selectImages() =
selectMultipleImage(R.id.uploadInsurance, arrayOf(getFilePath(FilePath.INSURANCE)))
fun submitForm(insurer: String, year: Int, monthOfYear: Int, dayOfMonth: Int) {
selectImages()
enterInsurance(insurer)
enterInsuranceExpiry(year, monthOfYear, dayOfMonth)
submit()
}
}

View File

@@ -0,0 +1,18 @@
package h_mal.appttude.com.driver.robots.vehicle
import h_mal.appttude.com.driver.FormRobot
import h_mal.appttude.com.driver.R
fun logbook(func: LogbookRobot.() -> Unit) = LogbookRobot().apply { func() }
class LogbookRobot : FormRobot() {
fun selectImages() = selectSingleImage(R.id.uploadmot, FilePath.MOT)
fun enterExpiryDate(year: Int, monthOfYear: Int, dayOfMonth: Int) =
setDate(R.id.mot_expiry, year, monthOfYear, dayOfMonth)
fun submitForm(year: Int, monthOfYear: Int, dayOfMonth: Int) {
selectImages()
enterExpiryDate(year, monthOfYear, dayOfMonth)
submit()
}
}

View File

@@ -0,0 +1,17 @@
package h_mal.appttude.com.driver.robots.vehicle
import h_mal.appttude.com.driver.FormRobot
import h_mal.appttude.com.driver.R
fun mot(func: MOTRobot.() -> Unit) = MOTRobot().apply { func() }
class MOTRobot : FormRobot() {
fun enterV5cNumber(v5c: String) = fillEditText(R.id.mot_expiry, v5c)
fun selectImages() = selectSingleImage(R.id.mot_expiry, FilePath.LOGBOOK)
fun submitForm(v5c: String) {
selectImages()
enterV5cNumber(v5c)
submit()
}
}

View File

@@ -0,0 +1,23 @@
package h_mal.appttude.com.driver.robots.vehicle
import h_mal.appttude.com.driver.FormRobot
import h_mal.appttude.com.driver.R
fun privateHireVehicleLicense(func: PrivateHireVehicleLicenseRobot.() -> Unit) =
PrivateHireVehicleLicenseRobot().apply { func() }
class PrivateHireVehicleLicenseRobot : FormRobot() {
fun enterLicenseNumber(text: String) = fillEditText(R.id.ph_no, text)
fun enterLicenseExpiry(year: Int, monthOfYear: Int, dayOfMonth: Int) =
setDate(R.id.ph_expiry, year, monthOfYear, dayOfMonth)
fun selectImage() = selectSingleImage(R.id.uploadphlic, FilePath.PRIVATE_HIRE)
fun submitForm(licenseNumber: String, year: Int, monthOfYear: Int, dayOfMonth: Int) {
selectImage()
enterLicenseNumber(licenseNumber)
enterLicenseExpiry(year, monthOfYear, dayOfMonth)
submit()
}
}

View File

@@ -0,0 +1,46 @@
package h_mal.appttude.com.driver.robots.vehicle
import h_mal.appttude.com.driver.FormRobot
import h_mal.appttude.com.driver.R
import h_mal.appttude.com.driver.helpers.EspressoHelper.setChecked
fun vehicleProfile(func: VehicleProfileRobot.() -> Unit) = VehicleProfileRobot().apply { func() }
class VehicleProfileRobot : FormRobot() {
fun enterRegistration(reg: String) = fillEditText(R.id.reg, reg)
fun enterMake(make: String) = fillEditText(R.id.make, make)
fun enterModel(model: String) = fillEditText(R.id.car_model, model)
fun enterColour(colour: String) = fillEditText(R.id.colour, colour)
fun enterAddress(address: String) = fillEditText(R.id.address, address)
fun enterPostcode(postCode: String) = fillEditText(R.id.postcode, postCode)
fun enterKeeperName(name: String) = fillEditText(R.id.keeper_name, name)
fun enterDateFirstAvailable(year: Int, monthOfYear: Int, dayOfMonth: Int) =
setDate(R.id.start_date, year, monthOfYear, dayOfMonth)
fun isSeized(seized: Boolean) = matchView(R.id.seized_checkbox).perform(setChecked(seized))
fun submitForm(
reg: String,
make: String,
model: String,
colour: String,
address: String,
postCode: String,
name: String,
year: Int,
monthOfYear: Int,
dayOfMonth: Int,
seized: Boolean = false
) {
enterRegistration(reg)
enterMake(make)
enterModel(model)
enterColour(colour)
enterAddress(address)
enterPostcode(postCode)
enterKeeperName(name)
enterDateFirstAvailable(year, monthOfYear, dayOfMonth)
isSeized(seized)
submit()
}
}

View File

@@ -7,8 +7,8 @@ import androidx.test.rule.GrantPermissionRule
import h_mal.appttude.com.driver.FirebaseTest
import h_mal.appttude.com.driver.R
import h_mal.appttude.com.driver.robots.*
import h_mal.appttude.com.driver.robots.driver.driversLicense
import h_mal.appttude.com.driver.ui.MainActivity
import h_mal.appttude.com.driver.ui.user.LoginActivity
import org.junit.*
import org.junit.runner.RunWith
@@ -18,7 +18,9 @@ import org.junit.runner.RunWith
class SubmitNewDataActivityTest :
FirebaseTest<MainActivity>(MainActivity::class.java, registered = true, signedIn = true) {
@get:Rule var permissionRule = GrantPermissionRule.grant(android.Manifest.permission.READ_EXTERNAL_STORAGE)
@get:Rule
var permissionRule =
GrantPermissionRule.grant(android.Manifest.permission.READ_EXTERNAL_STORAGE)
@Test
fun verifyUserRegistration_validUsernameAndPassword_loggedIn() {
@@ -29,13 +31,13 @@ class SubmitNewDataActivityTest :
openDriverProfile()
}
driverScreen {
driverLicense()
}
driversLicense {
submitForm("SAMPLE8456310LTU", 2022, 10, 2)
}
waitFor(5000)
}
}