Skip to content

Commit

Permalink
fix
Browse files Browse the repository at this point in the history
  • Loading branch information
trOnk12 committed Oct 20, 2023
2 parents 3c8868f + 0848e01 commit 32ecbe9
Show file tree
Hide file tree
Showing 19 changed files with 741 additions and 60 deletions.
9 changes: 0 additions & 9 deletions .github/tests.yml

This file was deleted.

29 changes: 20 additions & 9 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -66,22 +66,33 @@ Here's an example of an experiment that meets our rules:

## 🚀 Project setup

In order to properly start the application and use it, the CLIENT_SECRET environment variable must
be set on your computer. CLIENT_SECRET is a GitHub client secret key provided
from ``Settings -> Developer Settings -> OAuth Apps -> my application``.
In order to properly start the application and use it, the `LOUDIUS_CLIENT_SECRET` and
`LOUDIUS_CLIENT_ID` environment variables must be set on your computer.

If you're AppUniter, you can find this secrets [here](https://www.notion.so/appunite/Github-Secrets-0c2c6c1b56e2472c8a4752241f1e20d3?pvs=4).
* `LOUDIUS_CLIENT_SECRET` is a GitHub client secret key
* `LOUDIUS_CLIENT_ID` is a GitHub client id
*
both are provided from ``Settings -> Developer Settings -> OAuth Apps -> my application``.

If you're not, don't worry, here's a video to help you create a new one:
If you're not AppUniter, here's a video to help you create such appliation:

<https://github.com/appunite/Loudius/assets/72873966/4820b6df-81ca-48ed-9f3c-425011b758dd>
<https://github.com/appunite/Loudius/assets/72873966/4820b6df-81ca-48ed-9f3c-425011b758dd>.

If you'd like to run end-to-end tests you'd also need `LOUDIUS_GITHUB_USER_NAME` and
`LOUDIUS_GITHUB_USER_PASSWORD` which are credentials to GitHub test account.
This is just a standard GitHub account that you can create by yourself.

If you're AppUniter, you can find those secrets [here](https://www.notion.so/appunite/Github-Secrets-0c2c6c1b56e2472c8a4752241f1e20d3?pvs=4).

### How to set environmental variable on mac?

1. Launch zsh (command `zsh`)
2. `$ echo 'export CLIENT_SECRET=you know what' >> ~/.zshenv`
3. Restart Android studio and Terminal.
4. `$ echo $CLIENT_SECRET`
2. `$ echo 'export LOUDIUS_CLIENT_SECRET="you know what"' >> ~/.zshenv`
3. `$ echo 'export LOUDIUS_CLIENT_ID="you know what"' >> ~/.zshenv`
4. optionally: `$ echo 'export LOUDIUS_GITHUB_USER_NAME="you know what"' >> ~/.zshenv`
5. optionally: `$ echo 'export LOUDIUS_GITHUB_USER_PASSWORD="you know what"' >> ~/.zshenv`
6. Restart Android studio and Terminal.
7. `$ echo $LOUDIUS_CLIENT_ID/$LOUDIUS_CLIENT_SECRET $LOUDIUS_GITHUB_USER_NAME/$LOUDIUS_GITHUB_USER_PASSWORD`

### Screenshots tests

Expand Down
13 changes: 13 additions & 0 deletions app-shared-tests/build.gradle
Original file line number Diff line number Diff line change
Expand Up @@ -17,6 +17,18 @@ android {
versionName "1.0"

testInstrumentationRunner "androidx.test.runner.AndroidJUnitRunner"
if (System.env.LOUDIUS_GITHUB_USER_PASSWORD == null || System.env.LOUDIUS_GITHUB_USER_PASSWORD.isEmpty()) {
logger.warn("You need to set LOUDIUS_GITHUB_USER_PASSWORD in your environment variables")
}
buildConfigField "String", "LOUDIUS_GITHUB_USER_PASSWORD", "\"${System.env.LOUDIUS_GITHUB_USER_PASSWORD}\""
if (System.env.LOUDIUS_GITHUB_USER_NAME == null || System.env.LOUDIUS_GITHUB_USER_NAME.isEmpty()) {
logger.warn("You need to set LOUDIUS_GITHUB_USER_NAME in your environment variables")
}
buildConfigField "String", "LOUDIUS_GITHUB_USER_NAME", "\"${System.env.LOUDIUS_GITHUB_USER_NAME}\""
if (System.env.LOUDIUS_GITHUB_USER_OTP_SECRET == null || System.env.LOUDIUS_GITHUB_USER_OTP_SECRET.isEmpty()) {
logger.warn("You need to set LOUDIUS_GITHUB_USER_OTP_SECRET in your environment variables")
}
buildConfigField "String", "LOUDIUS_GITHUB_USER_OTP_SECRET", "\"${System.env.LOUDIUS_GITHUB_USER_OTP_SECRET}\""
}
buildFeatures {
compose true
Expand Down Expand Up @@ -92,6 +104,7 @@ dependencies {

// Firebase instrumentation lib
api 'com.google.firebase:testlab-instr-lib:0.2'
api 'dev.samstevens.totp:totp:1.7.1'

// ktlint
ktlintRuleset project(":custom-ktlint-rules")
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -21,6 +21,7 @@ import androidx.compose.ui.test.onNodeWithText
import com.appunite.loudius.components.theme.LoudiusTheme
import com.appunite.loudius.ui.pullrequests.PullRequestsScreen
import com.appunite.loudius.util.IntegrationTestRule
import com.appunite.loudius.util.MockWebServerRule
import com.appunite.loudius.util.Register
import com.appunite.loudius.util.waitUntilLoadingDoesNotExist
import org.junit.Before
Expand All @@ -29,9 +30,12 @@ import org.junit.Test

abstract class AbsPullRequestsScreenTest {

@get:Rule
@get:Rule(order = 0)
val integrationTestRule by lazy { IntegrationTestRule(this) }

@get:Rule(order = 1)
var mockWebServer: MockWebServerRule = MockWebServerRule()

@Before
fun setUp() {
integrationTestRule.setUp()
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -22,6 +22,7 @@ import androidx.compose.ui.test.performClick
import com.appunite.loudius.components.theme.LoudiusTheme
import com.appunite.loudius.ui.reviewers.ReviewersScreen
import com.appunite.loudius.util.IntegrationTestRule
import com.appunite.loudius.util.MockWebServerRule
import com.appunite.loudius.util.Register
import com.appunite.loudius.util.waitUntilLoadingDoesNotExist
import org.junit.Before
Expand All @@ -30,9 +31,12 @@ import org.junit.Test

abstract class AbsReviewersScreenTest {

@get:Rule
@get:Rule(order = 0)
val integrationTestRule by lazy { IntegrationTestRule(this) }

@get:Rule(order = 1)
var mockWebServer: MockWebServerRule = MockWebServerRule()

@Before
fun setUp() {
integrationTestRule.initTests()
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -20,35 +20,25 @@ import android.app.Activity
import android.app.Instrumentation
import android.content.Intent
import android.net.Uri
import androidx.compose.ui.test.assertIsDisplayed
import androidx.compose.ui.test.onNodeWithText
import androidx.compose.ui.test.performClick
import androidx.test.core.app.ActivityScenario
import androidx.test.espresso.intent.Intents.intending
import androidx.test.espresso.intent.matcher.IntentMatchers
import androidx.test.espresso.intent.rule.IntentsRule
import com.appunite.loudius.util.IntegrationTestRule
import com.appunite.loudius.util.MockWebServerRule
import com.appunite.loudius.util.Register
import com.appunite.loudius.util.waitUntilLoadingDoesNotExist
import org.junit.Before
import org.junit.Rule
import org.junit.Test

abstract class AbsWalkThroughAppTest {

@get:Rule(order = 0)
val integrationTestRule by lazy { IntegrationTestRule(this, MainActivity::class.java) }
abstract class AbsWalkThroughAppTest : UniversalWalkThroughAppTest() {

@get:Rule(order = 1)
var mockWebServer: MockWebServerRule = MockWebServerRule()

@get:Rule(order = 2)
val intents = IntentsRule()

@Before
fun setUp() {
integrationTestRule.setUp()
}

@Test
fun whenLoginScreenIsVisible_LoginButtonOpensGithubAuth(): Unit = with(integrationTestRule) {
fun prepareMocks() {
Register.run {
user(mockWebServer)
accessToken(mockWebServer)
Expand All @@ -62,31 +52,17 @@ abstract class AbsWalkThroughAppTest {
.respondWithFunction {
Instrumentation.ActivityResult(Activity.RESULT_OK, null)
}
}

composeTestRule.onNodeWithText("Log in").performClick()

override fun performGitHubLogin() {
// simulate opening a deeplink
ActivityScenario.launch<MainActivity>(
Intent(
Intent.ACTION_VIEW,
Uri.parse("loudius://callback?code=example_code"),
).apply {
setPackage(composeTestRule.activity.packageName)
setPackage(integrationTestRule.composeTestRule.activity.packageName)
},
)

composeTestRule.waitUntilLoadingDoesNotExist()

composeTestRule.onNodeWithText("First Pull-Request title").performClick()

composeTestRule.waitUntilLoadingDoesNotExist()

composeTestRule.onNodeWithText("Notify").performClick()

composeTestRule.waitUntilLoadingDoesNotExist()

composeTestRule
.onNodeWithText("Awesome! Your collaborator have been pinged for some serious code review action! \uD83C\uDF89")
.assertIsDisplayed()
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,60 @@
/*
* Copyright 2023 AppUnite S.A.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/

package com.appunite.loudius

import androidx.compose.ui.test.assertIsDisplayed
import androidx.compose.ui.test.onNodeWithText
import androidx.compose.ui.test.performClick
import com.appunite.loudius.util.IntegrationTestRule
import com.appunite.loudius.util.waitUntilLoadingDoesNotExist
import org.junit.Before
import org.junit.Rule
import org.junit.Test

abstract class UniversalWalkThroughAppTest {

@get:Rule(order = 0)
val integrationTestRule by lazy { IntegrationTestRule(this, MainActivity::class.java) }

@Before
fun setUp() {
integrationTestRule.setUp()
}

@Test
fun whenLoginScreenIsVisible_LoginButtonOpensGithubAuth(): Unit = with(integrationTestRule) {
composeTestRule.onNodeWithText("Log in").performClick()

performGitHubLogin()

composeTestRule.waitUntilLoadingDoesNotExist()

composeTestRule.onNodeWithText("First Pull-Request title").performClick()

composeTestRule.waitUntilLoadingDoesNotExist()

composeTestRule.onNodeWithText("Notify").performClick()

composeTestRule.waitUntilLoadingDoesNotExist()

composeTestRule
.onNodeWithText("Awesome! Your collaborator have been pinged for some serious code review action! \uD83C\uDF89")
.assertIsDisplayed()
}

abstract fun performGitHubLogin()
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,57 @@
/*
* Copyright 2023 AppUnite S.A.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/

package com.appunite.loudius.util

/**
* Adds description to a test, so failures are better recorded
*
* Example usage:
* ```kt
* description("Log-in") {
* description("Fill e-mail and password") {
* onView(withHint("E-mail")).perform(type("[email protected]"))
* onView(withHint("Password")).perform(type("password"))
* }
* description("Submit") {
* onView(withId(R.id.login_button)).perform(click())
* }
* }
* description("Ensure home screen is displayed") {
* onView(withClass("com.example.com.LoginScreen")).check(isDisplayed())
* }
* ```
*
* In case of failure, you'll see:
*
* ```
* Exception thrown DescriptionAssertionError("Error in step: Log-in -> Fill e-mail and password")
* ```
*
* The exception is always thrown with the root cause attached.
*/
fun <T> description(description: String, lambda: () -> T): T {
try {
return lambda()
} catch (error: DescriptionAssertionError) {
throw DescriptionAssertionError("$description -> ${error.step}", error.cause!!)
} catch (error: AssertionError) {
throw DescriptionAssertionError(description, error)
}
}

class DescriptionAssertionError(val step: String, cause: Throwable) :
AssertionError("Error in step: \"$step\"", cause)
Original file line number Diff line number Diff line change
Expand Up @@ -32,15 +32,14 @@ class IntegrationTestRule(
testActivity: Class<out ComponentActivity> = TestActivity::class.java,
) : TestRule {

val mockWebServer = MockWebServerRule()
val composeTestRule = createAndroidComposeRule(testActivity).apply {
registerIdlingResource(countingResource.toIdlingResource())
}
private val hiltRule = HiltAndroidRule(testClass)
private val screenshotTestRule = ScreenshotTestRule()

override fun apply(base: Statement, description: Description): Statement {
return RuleChain.outerRule(mockWebServer)
return RuleChain.emptyRuleChain()
.around(hiltRule)
.around(composeTestRule)
.around(screenshotTestRule)
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,50 @@
/*
* Copyright 2023 AppUnite S.A.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
@file:Suppress("ktlint:filename")

package com.appunite.loudius.util

import android.view.KeyEvent

/**
* Optimize typing by grouping characters with the same metaState
*
* UiDevice has UiDevice.pressKeyCode and UiDevice.pressKeyCodes methods.
* Using UiDevice.pressKeyCodes is much faster for typing multiple characters, but
* it couldn't be used for characters with a different metaState (e.g. Alt/Shift/Ctr).
* i.e. to write "Jacek Marchwicki" we need to use:
* * pressKeyCodes "j" with Shift
* * pressKeyCodes "acek " without metaState
* * pressKeyCodes "m" with Shift
* * pressKeyCodes "archwicki " without metaState
*
* so instead of calling "pressKeyCode" 16 times, we call "pressKeyCodes" 4 times - this is
* significantly faster.
*
* This method groups those calls, so we can type faster.
*/
fun groupKeys(list: List<KeyEvent>): List<KeyTypeEvent> = list
.filter { it.action == KeyEvent.ACTION_UP }
.fold(listOf()) { acc, event ->
val last = acc.lastOrNull()
if (last != null && last.metaState == event.metaState) {
acc.dropLast(1).plus(KeyTypeEvent(last.keyCodes.plus(event.keyCode), event.metaState))
} else {
acc.plus(KeyTypeEvent(listOf(event.keyCode), event.metaState))
}
}

data class KeyTypeEvent(val keyCodes: List<Int>, val metaState: Int)
Loading

0 comments on commit 32ecbe9

Please sign in to comment.