Skip to content
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

Create shared UIElement finders #37

Open
wants to merge 33 commits into
base: main
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
33 commits
Select commit Hold shift + click to select a range
4b64076
Create ui testing assertion module
corrado4eyes Sep 20, 2023
8e01f4c
Update strings structure and references
corrado4eyes Sep 20, 2023
261df5a
Update unit tests
corrado4eyes Sep 20, 2023
0c126d5
Update gradle files so that platforms can use the new module
corrado4eyes Sep 20, 2023
d0b1367
Update platforms tests and layout
corrado4eyes Sep 20, 2023
5052d8f
Add simple stub implementation for Node
corrado4eyes Sep 20, 2023
ea46cd7
Update tests
corrado4eyes Sep 20, 2023
9a8435d
Update TestCase class with sealed class and cases
corrado4eyes Sep 20, 2023
4fb3b11
Update assertion library
corrado4eyes Sep 20, 2023
bef8a79
Remove prints and update feature file
corrado4eyes Sep 20, 2023
8946885
Use identifier in android source set instead of Hardcoded string. Add…
corrado4eyes Sep 25, 2023
9f08e91
Add usage of application map for arguments
corrado4eyes Sep 25, 2023
157d5b5
Add doc for applicationArguments
corrado4eyes Sep 25, 2023
d928217
Rename exceptions removing prefix
corrado4eyes Sep 25, 2023
b3aced6
Update feature file
corrado4eyes Sep 26, 2023
ae07b35
Remove waitUntil from assertion method
corrado4eyes Sep 26, 2023
3eccea1
Rename files
corrado4eyes Sep 26, 2023
379c97f
Extract interface
corrado4eyes Sep 27, 2023
9f1a71a
Refactor so that TestCase interface is used. Also add platform specif…
corrado4eyes Sep 27, 2023
0f68652
Update view model and views created unit test
corrado4eyes Sep 27, 2023
1a51498
Update feature files
corrado4eyes Sep 27, 2023
32171a5
Update ui test files
corrado4eyes Sep 27, 2023
9109529
Update mock auth service and LoginVMTest
corrado4eyes Sep 27, 2023
223e289
Add `launchScreen` paramenter. Put arguments in different lines
corrado4eyes Sep 29, 2023
1d0af2c
Add APIs to scroll scroll views
corrado4eyes Sep 29, 2023
521bf85
Add SKIE to convert kotlin sealed classes into exhaustive enums in sw…
corrado4eyes Oct 2, 2023
dbfa64e
Remove empty lines
corrado4eyes Oct 2, 2023
9507f10
Add example for cross platform code
corrado4eyes Oct 2, 2023
57ffde9
Surround call by try/catches and add 25 retries on scrolls
corrado4eyes Oct 2, 2023
bd05f28
Update ui tests
corrado4eyes Oct 2, 2023
61b37da
Add logout call if the test configuration states that the user is log…
corrado4eyes Oct 2, 2023
da1690c
Add documentation
corrado4eyes Oct 2, 2023
4f606b3
Merge branch 'main' into feature/36-create-shared-button-finder
corrado4eyes Oct 2, 2023
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions android/build.gradle.kts
Original file line number Diff line number Diff line change
Expand Up @@ -53,6 +53,7 @@ dependencies {

implementation(project(":shared"))
implementation(project(":cucumber"))
implementation(project(":pistakio"))
implementation("androidx.compose.ui:ui:1.4.3")
implementation("androidx.compose.ui:ui-tooling:1.4.3")
implementation("androidx.compose.ui:ui-tooling-preview:1.4.3")
Expand Down
3 changes: 2 additions & 1 deletion android/src/androidTest/assets/features/Home.feature
Original file line number Diff line number Diff line change
Expand Up @@ -4,5 +4,6 @@ Feature: Home
And I am in the "Home" screen
Then I see "[email protected]" text
And I see the "Logout" button
And I see "15" in the scrollview
When I press the "Logout" button
Then I see the "Login" screen
Then I see the "Login" screen
2 changes: 1 addition & 1 deletion android/src/androidTest/assets/features/Login.feature
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,7 @@ Feature: Login
And I see the "Password" textfield with text "Password"
And I see the "Login" button
When I type "[email protected]" in the "Email" textfield
And I type "1234" in the "Password" secure textfield
And I type "1234" in the "Password" textfield
And I press the "Login" button
Then I see the "Home" screen
And I see "[email protected]" text
Original file line number Diff line number Diff line change
@@ -1,114 +1,80 @@
package com.corrado4eyes.cucumberplayground.test

import android.app.Activity
import android.content.Intent
import androidx.compose.ui.test.assertHasClickAction
import androidx.compose.ui.test.assertIsDisplayed
import androidx.compose.ui.test.assertTextContains
import androidx.compose.ui.test.junit4.createComposeRule
import androidx.compose.ui.test.onNodeWithTag
import androidx.compose.ui.test.onNodeWithText
import androidx.compose.ui.test.performClick
import androidx.compose.ui.test.performTextInput
import androidx.test.core.app.ActivityScenario
import androidx.test.platform.app.InstrumentationRegistry
import com.corrado4eyes.cucumber.errors.UIElementException
import com.corrado4eyes.cucumberplayground.android.MainActivity
import androidx.compose.ui.test.performScrollToIndex
import com.corrado4eyes.cucumberplayground.models.Strings
import com.corrado4eyes.cucumbershared.tests.Definitions
import com.corrado4eyes.cucumbershared.tests.AppDefinitions
import com.corrado4eyes.pistakio.DefaultApplicationAdapter
import io.cucumber.java8.En
import io.cucumber.junit.WithJunitRule
import org.junit.Rule

@WithJunitRule
class StepDefinitions : En {

private val arguments = mutableMapOf<String, String>()
private var scenario: ActivityScenario<*>? = null

@get:Rule(order = 0)
val testRule = createComposeRule()
private val application = DefaultApplicationAdapter(testRule)

init {
Definitions.values().forEach {
AppDefinitions.allCases.forEach {
val definitionString = it.definition.definitionString
when (it) {
Definitions.I_AM_IN_THE_EXPECT_VALUE_STRING_SCREEN -> Given(definitionString) { screenName: String ->
val screenTitleTag = when (screenName) {
Strings.Screen.Tag.login -> {
arguments["isLoggedIn"] = "false"
arguments["testEmail"] = ""
Strings.Screen.Title.login
}

Strings.Screen.Tag.home -> {
arguments["isLoggedIn"] = "true"
Strings.Screen.Title.home
}

else -> throw UIElementException.Screen.NotFound(screenName)
}
setLaunchScreen()
testRule.onNodeWithTag(screenTitleTag).assertIsDisplayed()
is AppDefinitions.CrossPlatform.IAmInScreen -> Given(definitionString) { screenName: String ->
val assertions = AppDefinitions.CrossPlatform.IAmInScreen(
"MainActivity",
application,
listOf(screenName)
).runAndGetAssertions()
application.assertAll(assertions)
}
Definitions.I_SEE_EXPECT_VALUE_STRING_TEXT -> Then(definitionString) { title: String ->
testRule.onNodeWithText(title).assertIsDisplayed()
is AppDefinitions.CrossPlatform.ISeeText -> Then(definitionString) { textViewTitle: String ->
val assertions = AppDefinitions.CrossPlatform.ISeeText(application, listOf(textViewTitle)).runAndGetAssertions()
application.assertAll(assertions)
}
Definitions.I_SEE_THE_EXPECT_VALUE_STRING_BUTTON -> Then(definitionString) { buttonName: String ->
when (buttonName) {
Strings.Button.Text.login -> testRule.onNodeWithText(Strings.Button.Text.login).assertIsDisplayed().assertHasClickAction()
Strings.Button.Text.logout -> testRule.onNodeWithTag(Strings.Button.Text.logout).assertIsDisplayed().assertHasClickAction()
else -> throw UIElementException.Button.NotFound(buttonName)
}
is AppDefinitions.CrossPlatform.ISeeButton -> Then(definitionString) { buttonTitle: String ->
val assertions = AppDefinitions.CrossPlatform.ISeeButton(application, listOf(buttonTitle)).runAndGetAssertions()
application.assertAll(assertions)
}
Definitions.I_SEE_THE_EXPECT_VALUE_STRING_SCREEN -> Then(definitionString) { screenName: String ->
Thread.sleep(1000)
when (screenName) {
Strings.Screen.Tag.login -> testRule.onNodeWithTag(Strings.Screen.Title.login).assertIsDisplayed()
Strings.Screen.Tag.home -> testRule.onNodeWithTag(Strings.Screen.Title.home).assertIsDisplayed()
else -> throw UIElementException.Screen.NotFound(screenName)
}
is AppDefinitions.CrossPlatform.ISeeScreen -> Then(definitionString) { screenTitle: String ->
val assertions = AppDefinitions.CrossPlatform.ISeeScreen(application, listOf(screenTitle)).runAndGetAssertions()
application.assertAll(assertions)
}
Definitions.I_SEE_THE_EXPECT_VALUE_STRING_TEXT_FIELD_WITH_TEXT_EXPECT_VALUE_STRING -> Then(definitionString) { tag: String, text: String ->
val elementNode = try {
testRule.onNodeWithTag(tag).assertIsDisplayed()
} catch (e: AssertionError) {
throw UIElementException.TextField.NotFound(tag)
}

elementNode.assertTextContains(text)
is AppDefinitions.CrossPlatform.ISeeTextFieldWithText -> Then(definitionString) { textFieldTag: String, textFieldText: String ->
val assertions = AppDefinitions.CrossPlatform.ISeeTextFieldWithText(application, listOf(textFieldTag, textFieldText)).runAndGetAssertions()
application.assertAll(assertions)
}
Definitions.I_TYPE_EXPECT_VALUE_STRING_IN_THE_EXPECT_VALUE_STRING_TEXT_FIELD -> When(definitionString) { textInput: String, tag: String, ->
testRule.onNodeWithText(tag).performTextInput(textInput)
is AppDefinitions.CrossPlatform.ITypeTextIntoTextField -> When(definitionString) { textInput: String, tag: String, ->
val assertions = AppDefinitions.CrossPlatform.ITypeTextIntoTextField(application, listOf(textInput, tag)).runAndGetAssertions()
application.assertAll(assertions)
}
Definitions.I_TYPE_EXPECT_VALUE_STRING_IN_THE_EXPECT_VALUE_STRING_SECURE_TEXT_FIELD -> When(definitionString) { textInput: String, tag: String ->
testRule.onNodeWithText(tag).performTextInput(textInput)
is AppDefinitions.CrossPlatform.IPressTheButton -> When(definitionString) { buttonTag: String ->
val assertions = AppDefinitions.CrossPlatform.IPressTheButton(application, listOf(buttonTag)).runAndGetAssertions()
application.assertAll(assertions)
}
Definitions.I_PRESS_THE_EXPECT_VALUE_STRING_BUTTON -> When(definitionString) { buttonName: String ->
val elementNode = try {
testRule.onNodeWithTag(buttonName).assertIsDisplayed()
} catch (e: AssertionError) {
throw UIElementException.TextField.NotFound(buttonName)
}
elementNode.performClick()
is AppDefinitions.CrossPlatform.SetLoggedInUserEmail -> Given(definitionString) { loggedInEmail: String ->
application.assertAll(AppDefinitions.CrossPlatform.SetLoggedInUserEmail(application, listOf(loggedInEmail)).runAndGetAssertions())
}
Definitions.EMAIL_IS_EXPECT_VALUE_STRING -> Given(definitionString) { loggedInEmail: String ->
arguments["testEmail"] = loggedInEmail
is AppDefinitions.Platform.ISeeValueInScrollView -> When(definitionString) { index: String ->

// Platform implementation
testRule
.onNodeWithTag(Strings.ScrollView.Tag.homeScrollView)
.performScrollToIndex(index.toInt())

testRule.onNodeWithText(index).assertIsDisplayed()

// Cross-platform implementation
// val element = application.findView(Strings.ScrollView.Tag.homeScrollView)
// element.swipeUntilIndex(index.toInt())
// val text = application.findView(index)
// application.assert(text.isVisible())
}

}
}
}

private fun setLaunchScreen() {
val instrumentation = InstrumentationRegistry.getInstrumentation()
launch<MainActivity>(
Intent(instrumentation.targetContext, MainActivity::class.java)
.putExtra("isLoggedIn", arguments["isLoggedIn"])
.putExtra("testEmail", arguments["testEmail"])
)
}

private fun <T: Activity> launch(intent: Intent) {
scenario = ActivityScenario.launch<T>(intent)
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -2,12 +2,19 @@ package com.corrado4eyes.cucumberplayground.android.home

import androidx.compose.foundation.layout.Column
import androidx.compose.foundation.layout.fillMaxSize
import androidx.compose.foundation.layout.fillMaxWidth
import androidx.compose.foundation.layout.height
import androidx.compose.foundation.lazy.LazyColumn
import androidx.compose.foundation.lazy.items
import androidx.compose.material.Button
import androidx.compose.material.Text
import androidx.compose.runtime.Composable
import androidx.compose.ui.Alignment
import androidx.compose.ui.Modifier
import androidx.compose.ui.platform.testTag
import androidx.compose.ui.tooling.preview.Preview
import androidx.compose.ui.unit.dp
import com.corrado4eyes.cucumberplayground.models.Strings
import com.corrado4eyes.cucumberplayground.viewModels.home.HomeViewModel
import com.splendo.kaluga.architecture.compose.viewModel.ViewModelComposable
import org.koin.androidx.compose.koinViewModel
Expand All @@ -19,15 +26,33 @@ fun HomeLayout() {
Column(modifier = Modifier.fillMaxSize()) {
Text(
[email protected],
modifier = Modifier.testTag([email protected])
modifier = Modifier.testTag(Strings.Screen.Tag.home)
)
Text(
[email protected]().email,
modifier = Modifier.testTag([email protected]().email)
)
Text([email protected]().email)
Button(
this@ViewModelComposable::logout,
modifier = Modifier.testTag([email protected])
modifier = Modifier.testTag(Strings.Button.Tag.logout)
) {
Text([email protected])
}

LazyColumn(
horizontalAlignment = Alignment.CenterHorizontally,
modifier = Modifier
.fillMaxWidth()
.height(200.dp)
.testTag(Strings.ScrollView.Tag.homeScrollView)
) {
items(
viewModel.scrollableItems,
key = { it }
) {
Text(it.toString())
}
}
}
}
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -14,6 +14,7 @@ import androidx.compose.ui.graphics.Color
import androidx.compose.ui.platform.testTag
import androidx.compose.ui.text.input.TextFieldValue
import androidx.compose.ui.tooling.preview.Preview
import com.corrado4eyes.cucumberplayground.models.Strings
import com.corrado4eyes.cucumberplayground.viewModels.login.LoginViewModel
import com.splendo.kaluga.architecture.compose.state
import com.splendo.kaluga.architecture.compose.viewModel.ViewModelComposable
Expand All @@ -26,18 +27,21 @@ fun LoginLayout() {
ViewModelComposable(viewModel) {
val isLoading by this.isLoading.state()
Column {
Text(text = [email protected], modifier = Modifier.testTag("Login screen"))
Text(
text = [email protected],
modifier = Modifier.testTag(Strings.Screen.Tag.login)
)
CustomTextField(
value = [email protected],
label = "Email",
modifier = Modifier.testTag("Email")
modifier = Modifier.testTag(Strings.TextField.Tag.email)
)
val emailErrorText by [email protected]()
Text(text = emailErrorText, color = Color.Red)
CustomTextField(
value = [email protected],
label = "Password",
modifier = Modifier.testTag("Password")
modifier = Modifier.testTag(Strings.TextField.Tag.password)
)
val passwordErrorText by [email protected]()
Text(text = passwordErrorText, color = Color.Red)
Expand All @@ -50,9 +54,9 @@ fun LoginLayout() {

Button(
this@ViewModelComposable::login,
modifier = Modifier.testTag("Login")
modifier = Modifier.testTag(Strings.Button.Tag.login)
) {
Text("Login")
Text(viewModel.buttonTitle)
}
}
}
Expand Down
1 change: 1 addition & 0 deletions build.gradle.kts
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,7 @@ plugins {

buildscript {
repositories {
mavenLocal()
mavenCentral()
}

Expand Down
18 changes: 17 additions & 1 deletion cucumberShared/build.gradle.kts
Original file line number Diff line number Diff line change
@@ -1,3 +1,4 @@
import co.touchlab.skie.configuration.SealedInterop
import org.jetbrains.kotlin.gradle.plugin.mpp.KotlinNativeTarget
import org.jetbrains.kotlin.konan.file.File
import org.jetbrains.kotlin.konan.properties.Properties
Expand All @@ -7,6 +8,7 @@ import org.jetbrains.kotlin.konan.properties.loadProperties
plugins {
kotlin("multiplatform")
id("com.android.library")
id("co.touchlab.skie") version "0.5.0"
}

kotlin {
Expand All @@ -25,12 +27,14 @@ kotlin {
transitiveExport = true
export(project(":shared"))
export(project(":cucumber"))
export(project(":pistakio"))
baseName = "shared"

linkFrameworkSearchPaths("$projectDir/../cucumber")
linkFrameworkSearchPaths("$projectDir/../pistakio")

getTest("DEBUG").apply {
linkFrameworkSearchPaths("$projectDir/../cucumber")
linkFrameworkSearchPaths("$projectDir/../pistakio")
}
}
}
Expand All @@ -45,6 +49,7 @@ kotlin {
dependencies {
api(project(":shared"))
api(project(":cucumber"))
api(project(":pistakio"))
}
}
val commonTest by getting {
Expand Down Expand Up @@ -167,4 +172,15 @@ fun org.jetbrains.kotlin.gradle.plugin.mpp.NativeBinary.linkFrameworkSearchPaths
linkerOpts("-rpath", it)
}
}
}

skie {
analytics {
disableUpload.set(true)
}
features {
group {
SealedInterop.Enabled(true)
}
}
}
Loading