Skip to content

Commit

Permalink
#395 Add E2E test
Browse files Browse the repository at this point in the history
Signed-off-by: Stefan Niedermann <[email protected]>
Signed-off-by: Andy Scherzinger <[email protected]>
  • Loading branch information
stefan-niedermann authored and AndyScherzinger committed Jan 19, 2024
1 parent e6e9d27 commit 9377094
Show file tree
Hide file tree
Showing 5 changed files with 283 additions and 47 deletions.
91 changes: 91 additions & 0 deletions .github/workflows/e2e.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,91 @@
on: [push]

jobs:
setup_nextcloud:
runs-on: ubuntu-latest
name: Run e2e test
strategy:
matrix:
api-level: [ 24 ] #, 25, 26, 27, 28, 29 ]
nextcloud-version: [ 'nextcloud:latest' ] #, 'nextcloud:stable', 'nextcloud:production' ]
services:
nextcloud:
image: ${{ matrix.nextcloud-version }}
env:
SQLITE_DATABASE: db.sqlite
NEXTCLOUD_ADMIN_USER: Test
NEXTCLOUD_ADMIN_PASSWORD: Test
ports:
- 8080:80
options: >-
--health-cmd "curl GET 'http://Test:Test@localhost:80/ocs/v2.php/apps/serverinfo/api/v1/info' -f -H 'OCS-APIRequest: true' || exit 1"
--health-interval 1s
--health-timeout 2s
--health-retries 10
--health-start-period 3s
steps:
- name: Checkout
uses: actions/checkout@v2

- name: Make Nextcloud accessible from AVD
run: |
docker exec `docker ps -f 'name=_nextcloud' -l -q` bash -c 'runuser -u www-data -- php occ config:system:set trusted_domains 2 --value=172.17.0.1'
# TODO 172.17.0.1 is the hard coded IP address of the docker container. Make this more generic.
- name: Verify Nextcloud being present on 172.17.0.1
run: |
curl -v -X GET 'http://Test:[email protected]:8080/ocs/v2.php/cloud/capabilities?format=json' -H 'OCS-APIRequest: true' | jq
##########################
# AVD CACHING START #
##########################

- name: Gradle cache
uses: actions/cache@v2
with:
path: |
~/.gradle/caches
~/.gradle/wrapper
key: gradle-${{ runner.os }}-${{ hashFiles('**/*.gradle*') }}-${{ hashFiles('**/gradle/wrapper/gradle-wrapper.properties') }}-${{ hashFiles('**/buildSrc/**/*.kt') }}

- name: AVD cache
uses: actions/cache@v2
id: avd-cache
with:
path: |
~/.android/avd/*
~/.android/adb*
key: avd-${{ matrix.api-level }}

- name: Create AVD and generate snapshot for caching
if: steps.avd-cache.outputs.cache-hit != 'true'
uses: reactivecircus/android-emulator-runner@v2
with:
api-level: ${{ matrix.api-level }}
force-avd-creation: false
sdcard-path-or-size: sdcard
emulator-options: -gpu swiftshader_indirect -no-window -noaudio -no-boot-anim -camera-back none
disable-animations: true
script: echo "Generated AVD snapshot for caching."

##########################
# AVD CACHING END #
##########################

- name: Run e2e tests
uses: reactivecircus/android-emulator-runner@v2
with:
api-level: ${{ matrix.api-level }}
force-avd-creation: false
emulator-options: -no-snapshot-save -no-window -gpu swiftshader_indirect -noaudio -no-boot-anim -camera-back none
disable-animations: true
# FIXME Execution of connectedDebugAndroidTest fails
# TODO latest.apk should be cached when matrix testing is used
script: |
adb shell pm uninstall -k --user 0 com.nextcloud.android.beta || true
wget -q https://download.nextcloud.com/android/dev/latest.apk
adb install latest.apk
adb shell pm grant com.nextcloud.android.beta android.permission.READ_EXTERNAL_STORAGE
adb logcat -c || true
adb logcat *:I -v color &
./gradlew connectedDebugAndroidTest || true
6 changes: 2 additions & 4 deletions sample/build.gradle
Original file line number Diff line number Diff line change
Expand Up @@ -38,8 +38,6 @@ dependencies {
implementation 'androidx.constraintlayout:constraintlayout:2.1.4'
implementation 'com.squareup.retrofit2:retrofit:2.9.0'

testImplementation 'junit:junit:4.13.2'

androidTestImplementation 'androidx.test.ext:junit:1.1.5'
androidTestImplementation 'androidx.test.espresso:espresso-core:3.5.1'
androidTestImplementation 'androidx.test:runner:1.4.0'
androidTestImplementation 'androidx.test.uiautomator:uiautomator:2.2.0'
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,190 @@
package com.nextcloud.android.sso.sample;

import static androidx.test.platform.app.InstrumentationRegistry.getInstrumentation;
import static androidx.test.uiautomator.Until.findObject;
import static androidx.test.uiautomator.Until.hasObject;

import android.content.Intent;
import android.util.Log;
import android.webkit.WebView;
import android.widget.Button;
import android.widget.EditText;

import androidx.annotation.NonNull;
import androidx.test.uiautomator.By;
import androidx.test.uiautomator.UiDevice;
import androidx.test.uiautomator.UiObjectNotFoundException;
import androidx.test.uiautomator.UiSelector;

import org.junit.After;
import org.junit.Before;
import org.junit.FixMethodOrder;
import org.junit.Test;
import org.junit.runners.MethodSorters;

/**
* FIXME This does not yet work
*/
@FixMethodOrder(MethodSorters.NAME_ASCENDING)
public class E2ETest {

private static final String TAG = E2ETest.class.getSimpleName();

private UiDevice mDevice;

private static final int TIMEOUT = 60_000;

private static final String APP_NEXTCLOUD = "com.nextcloud.android.beta";
// TODO This should be passed as argument
private static final String APP_SAMPLE = BuildConfig.APPLICATION_ID;
private static final String SERVER_URL = "http://172.17.0.1:8080";
private static final String SERVER_USERNAME = "Test";
private static final String SERVER_PASSWORD = "Test";

@Before
public void before() {
mDevice = UiDevice.getInstance(getInstrumentation());
mDevice.pressHome();
}

@After
public void after() {
mDevice.pressHome();
}

@Test
public void test_00_configureNextcloudAccount() throws UiObjectNotFoundException {
launch(APP_NEXTCLOUD);

final var loginButton = mDevice.findObject(new UiSelector().textContains("Log in"));
loginButton.waitForExists(TIMEOUT);
log("Login Button exists. Clicking on it...");
loginButton.click();
log("Login Button clicked.");

final var urlInput = mDevice.findObject(new UiSelector().focused(true));
urlInput.waitForExists(TIMEOUT);
log("URL input exists.");
log("Entering URL...");
urlInput.setText(SERVER_URL);
log("URL entered.");

log("Pressing enter...");
mDevice.pressEnter();
log("Enter pressed.");

log("Waiting for WebView...");
mDevice.wait(findObject(By.clazz(WebView.class)), TIMEOUT);
log("WebView exists.");

final var webViewLoginButton = mDevice.findObject(new UiSelector()
.instance(0)
.className(Button.class));
log("Waiting for WebView Login Button...");
webViewLoginButton.waitForExists(TIMEOUT);
log("WebView Login Button exists. Clicking on it...");
webViewLoginButton.click();

final var usernameInput = mDevice.findObject(new UiSelector()
.instance(0)
.className(EditText.class));
log("Waiting for Username Input...");
usernameInput.waitForExists(TIMEOUT);
log("Username Input exists. Setting text...");
usernameInput.setText(SERVER_USERNAME);
log("Username has been set.");

final var passwordInput = mDevice.findObject(new UiSelector()
.instance(1)
.className(EditText.class));
log("Waiting for Password Input...");
passwordInput.waitForExists(TIMEOUT);
log("Password Input exists. Setting text...");
passwordInput.setText(SERVER_PASSWORD);

final var webViewSubmitButton = mDevice.findObject(new UiSelector()
.instance(0)
.className(Button.class));
log("Waiting for WebView Submit Button...");
webViewSubmitButton.waitForExists(TIMEOUT);
log("WebView Submit Button exists. Clicking on it...");
webViewSubmitButton.click();

final var webViewGrantAccessButton = mDevice.findObject(new UiSelector()
.instance(0)
.className(Button.class));
log("Waiting for WebView Grant Access Button...");
webViewGrantAccessButton.waitForExists(TIMEOUT);
log("WebView Grant Access Button exists. Clicking on it...");
webViewGrantAccessButton.click();
}

@Test
public void test_01_importAccountIntoSampleApp() throws UiObjectNotFoundException, InterruptedException {
launch(APP_SAMPLE);

final var accountButton = mDevice.findObject(new UiSelector()
.instance(0)
.className(Button.class));
accountButton.waitForExists(TIMEOUT);
accountButton.click();

mDevice.waitForWindowUpdate(null, TIMEOUT);

final var radioAccount = mDevice.findObject(new UiSelector()
.clickable(true)
.instance(0));
radioAccount.waitForExists(TIMEOUT);
radioAccount.click();

mDevice.waitForWindowUpdate(null, TIMEOUT);

Thread.sleep(10_000);
final var okButton = mDevice.findObject(new UiSelector()
.textContains("OK"));
log("Waiting for OK Button...");
okButton.waitForExists(TIMEOUT);
log("OK Button exists. Clicking on it...");
okButton.click();
log("OK Button clicked");

mDevice.waitForWindowUpdate(null, TIMEOUT);

final var allowButton = mDevice.findObject(new UiSelector()
.instance(1)
.className(Button.class));
log("Waiting for Allow Button...");
allowButton.waitForExists(TIMEOUT);
log("Allow Button exists. Clicking on it...");
allowButton.click();
log("Allow Button clicked");

log("Waiting for finished import...");
final var welcomeText = mDevice.findObject(new UiSelector().description("Filter"));
welcomeText.waitForExists(TIMEOUT);
log("Import finished.");
}

@Test
public void test_02_verifyResult() throws UiObjectNotFoundException {
launch(APP_SAMPLE);

final var taskCard = mDevice.findObject(new UiSelector()
.textContains("Test on Nextcloud"));
taskCard.waitForExists(TIMEOUT);
System.out.println("Found: " + taskCard.getText());
}

private void log(@NonNull String message) {
Log.i(TAG, message);
}

private void launch(@NonNull String packageName) {
final var context = getInstrumentation().getContext();
context.startActivity(context
.getPackageManager()
.getLaunchIntentForPackage(packageName)
.addFlags(Intent.FLAG_ACTIVITY_CLEAR_TASK));
mDevice.wait(hasObject(By.pkg(packageName).depth(0)), TIMEOUT);
}
}

This file was deleted.

This file was deleted.

0 comments on commit 9377094

Please sign in to comment.