From 2b4264b60a5375bcd7e8655c54908888b164bfb2 Mon Sep 17 00:00:00 2001 From: Jani Hautakangas Date: Sat, 3 Aug 2024 09:56:59 +0300 Subject: [PATCH] [WIP] Instrumentation tests --- .github/workflows/build-feature-branch.yml | 11 +++ .github/workflows/build-latest-preview.yml | 11 +++ .github/workflows/build.yml | 52 ++-------- .github/workflows/check.yml | 34 +++++++ .github/workflows/lint.yml | 25 +++++ .github/workflows/run-webdriver-tests.yml | 1 + .github/workflows/test.yml | 56 +++++++++++ .../wpe/tools/minibrowser/BrowserFragment.kt | 1 - wpe/build.gradle | 5 + .../java/com/wpe/wpeview/WPEViewImeTest.java | 94 +++++++++++++++++++ .../com/wpe/wpeview/WPEViewTestActivity.java | 30 ++++++ wpe/src/main/AndroidManifest.xml | 10 ++ wpe/src/main/cpp/Browser/Page.cpp | 2 + wpe/src/main/java/com/wpe/wpe/Page.java | 4 +- .../main/java/com/wpe/wpeview/WPEView.java | 2 +- 15 files changed, 290 insertions(+), 48 deletions(-) create mode 100644 .github/workflows/check.yml create mode 100644 .github/workflows/lint.yml create mode 100644 .github/workflows/test.yml create mode 100644 wpe/src/androidTest/java/com/wpe/wpeview/WPEViewImeTest.java create mode 100644 wpe/src/androidTest/java/com/wpe/wpeview/WPEViewTestActivity.java diff --git a/.github/workflows/build-feature-branch.yml b/.github/workflows/build-feature-branch.yml index eb152b582..dcc268599 100644 --- a/.github/workflows/build-feature-branch.yml +++ b/.github/workflows/build-feature-branch.yml @@ -5,7 +5,18 @@ on: - main jobs: + check: + uses: ./.github/workflows/check.yml + lint: + needs: check + uses: ./.github/workflows/lint.yml + with: + build_type: Debug + test: + needs: lint + uses: ./.github/workflows/test.yml build: + needs: test strategy: matrix: build_type: [Release, Debug] diff --git a/.github/workflows/build-latest-preview.yml b/.github/workflows/build-latest-preview.yml index 39c6a9efd..7dc6dba3d 100644 --- a/.github/workflows/build-latest-preview.yml +++ b/.github/workflows/build-latest-preview.yml @@ -5,7 +5,18 @@ on: - main jobs: + check: + uses: ./.github/workflows/check.yml + lint: + needs: check + uses: ./.github/workflows/lint.yml + with: + build_type: Debug + test: + needs: lint + uses: ./.github/workflows/test.yml build: + needs: test uses: ./.github/workflows/build.yml with: build_type: Debug diff --git a/.github/workflows/build.yml b/.github/workflows/build.yml index d2d2cfc9c..546fb0b79 100644 --- a/.github/workflows/build.yml +++ b/.github/workflows/build.yml @@ -7,70 +7,34 @@ on: type: string jobs: - check-format: - runs-on: ubuntu-22.04 - steps: - - name: Checkout - uses: actions/checkout@v3 - - name: Java™ Setup - uses: actions/setup-java@v3 - with: - distribution: temurin - java-version: 17 - - name: Check Code Format - uses: gradle/gradle-build-action@v2.4.2 - with: - arguments: checkFormat - - check-jni: - runs-on: ubuntu-22.04 - steps: - - name: Checkout - uses: actions/checkout@v3 - - name: Java™ Setup - uses: actions/setup-java@v3 - with: - distribution: temurin - java-version: 17 - - name: Check JNI - run: | - cd tools/jni-test && \ - cmake -S. -Bbuild && \ - cmake --build build --target run - build-code: - needs: [check-format, check-jni] runs-on: ubuntu-22.04 steps: - name: Checkout - uses: actions/checkout@v3 + uses: actions/checkout@v4 - name: Fetch Dependencies (WPE Bootstrap) run: python3 tools/scripts/bootstrap.py -a all - name: Java™ Setup - uses: actions/setup-java@v3 + uses: actions/setup-java@v4 with: distribution: temurin java-version: 17 - - name: Execute Android Linter - uses: gradle/gradle-build-action@v2.4.2 - with: - arguments: lint + - name: Setup Gradle + uses: gradle/actions/setup-gradle@v4 - name: Build Project - uses: gradle/gradle-build-action@v2.4.2 - with: - arguments: assemble${{ inputs.build_type }} + run: ./gradlew assemble${{ inputs.build_type }} - name: Save MiniBrowser Artifacts - uses: actions/upload-artifact@v3 + uses: actions/upload-artifact@v4 with: name: minibrowser-${{ inputs.build_type }} path: tools/minibrowser/build/outputs/apk/**/*.apk - name: Save Mediaplayer Artifacts - uses: actions/upload-artifact@v3 + uses: actions/upload-artifact@v4 with: name: mediaplayer-${{ inputs.build_type }} path: tools/mediaplayer/build/outputs/apk/**/*.apk - name: Save WebDriver Artifacts - uses: actions/upload-artifact@v3 + uses: actions/upload-artifact@v4 with: name: webdriver-${{ inputs.build_type }} path: tools/webdriver/build/outputs/apk/**/*.apk diff --git a/.github/workflows/check.yml b/.github/workflows/check.yml new file mode 100644 index 000000000..9b541fc5e --- /dev/null +++ b/.github/workflows/check.yml @@ -0,0 +1,34 @@ +name: Check +on: workflow_call + +jobs: + check-format: + runs-on: ubuntu-22.04 + steps: + - name: Checkout + uses: actions/checkout@v4 + - name: Java™ Setup + uses: actions/setup-java@v4 + with: + distribution: temurin + java-version: 17 + - name: Setup Gradle + uses: gradle/actions/setup-gradle@v4 + - name: Check Code Format + run: ./gradlew checkFormat + + check-jni: + runs-on: ubuntu-22.04 + steps: + - name: Checkout + uses: actions/checkout@v4 + - name: Java™ Setup + uses: actions/setup-java@v4 + with: + distribution: temurin + java-version: 17 + - name: Check JNI + run: | + cd tools/jni-test && \ + cmake -S. -Bbuild && \ + cmake --build build --target run diff --git a/.github/workflows/lint.yml b/.github/workflows/lint.yml new file mode 100644 index 000000000..fb8dd0680 --- /dev/null +++ b/.github/workflows/lint.yml @@ -0,0 +1,25 @@ +name: Lint +on: + workflow_call: + inputs: + build_type: + required: true + type: string + +jobs: + lint-checks: + runs-on: ubuntu-22.04 + steps: + - name: Checkout + uses: actions/checkout@v4 + - name: Fetch Dependencies (WPE Bootstrap) + run: python3 tools/scripts/bootstrap.py -a all + - name: Java™ Setup + uses: actions/setup-java@v4 + with: + distribution: temurin + java-version: 17 + - name: Setup Gradle + uses: gradle/actions/setup-gradle@v4 + - name: Lint checks + run: ./gradlew lint${{ inputs.build_type }} diff --git a/.github/workflows/run-webdriver-tests.yml b/.github/workflows/run-webdriver-tests.yml index 41311d923..21d5308f5 100644 --- a/.github/workflows/run-webdriver-tests.yml +++ b/.github/workflows/run-webdriver-tests.yml @@ -14,6 +14,7 @@ jobs: matrix: api-level: [33] arch: [x86_64] + if: ${{ github.event.workflow_run.conclusion == 'success' }} steps: - name: checkout uses: actions/checkout@v4 diff --git a/.github/workflows/test.yml b/.github/workflows/test.yml new file mode 100644 index 000000000..30b30b3b3 --- /dev/null +++ b/.github/workflows/test.yml @@ -0,0 +1,56 @@ +name: Test +on: workflow_call + +env: + apiLevel: '33' + arch: 'x86_64' + +jobs: + instrumentation-tests: + runs-on: ubuntu-22.04 + steps: + - name: Checkout + uses: actions/checkout@v4 + - name: Java™ Setup + uses: actions/setup-java@v4 + with: + distribution: temurin + java-version: 17 + - name: Fetch Dependencies (WPE Bootstrap) + run: python3 tools/scripts/bootstrap.py -a x86_64 + - name: Remmove layouttests and webdrivertests + run: | + rm -rf layouttests + rm -rf webdrivertests + - name: Setup Gradle + uses: gradle/actions/setup-gradle@v4 + - name: Enable KVM + run: | + echo 'KERNEL=="kvm", GROUP="kvm", MODE="0666", OPTIONS+="static_node=kvm"' | sudo tee /etc/udev/rules.d/99-kvm4all.rules + sudo udevadm control --reload-rules + sudo udevadm trigger --name-match=kvm + - name: AVD cache + uses: actions/cache@v4 + id: avd-cache + with: + path: | + ~/.android/avd/* + ~/.android/adb* + key: avd-${{ env.apiLevel }} + - 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: ${{ env.apiLevel }} + arch: ${{ env.arch }} + force-avd-creation: false + emulator-options: -no-window -gpu swiftshader_indirect -noaudio -no-boot-anim -camera-back none + disable-animations: false + script: echo "Generated AVD snapshot for caching." + - name: Instrumented Tests + uses: reactivecircus/android-emulator-runner@v2 + with: + api-level: ${{ env.apiLevel }} + arch: ${{ env.arch }} + emulator-options: -no-window -gpu swiftshader_indirect -noaudio -no-boot-anim -camera-back none + script: ./gradlew wpe:connectedDebugAndroidTest diff --git a/tools/minibrowser/src/main/java/com/wpe/tools/minibrowser/BrowserFragment.kt b/tools/minibrowser/src/main/java/com/wpe/tools/minibrowser/BrowserFragment.kt index eb0e7210e..fce2ebdbb 100644 --- a/tools/minibrowser/src/main/java/com/wpe/tools/minibrowser/BrowserFragment.kt +++ b/tools/minibrowser/src/main/java/com/wpe/tools/minibrowser/BrowserFragment.kt @@ -32,7 +32,6 @@ import androidx.fragment.app.activityViewModels import androidx.navigation.fragment.findNavController import com.wpe.tools.minibrowse.Utils import com.wpe.tools.minibrowser.databinding.FragmentBrowserBinding -import com.wpe.wpeview.WPECallback import com.wpe.wpeview.WPEChromeClient import com.wpe.wpeview.WPEView import com.wpe.wpeview.WPEViewClient diff --git a/wpe/build.gradle b/wpe/build.gradle index b0b144c6c..971469126 100644 --- a/wpe/build.gradle +++ b/wpe/build.gradle @@ -10,6 +10,8 @@ android { defaultConfig { minSdk 31 targetSdk 35 + + testInstrumentationRunner = "androidx.test.runner.AndroidJUnitRunner" } lintOptions { @@ -90,6 +92,9 @@ android { dependencies { implementation 'androidx.annotation:annotation:1.8.2' + androidTestImplementation "androidx.test:runner:1.6.1" + androidTestImplementation "androidx.test:rules:1.6.1" + androidTestImplementation "androidx.test.ext:junit:1.2.1" } gradle.afterProject { project -> diff --git a/wpe/src/androidTest/java/com/wpe/wpeview/WPEViewImeTest.java b/wpe/src/androidTest/java/com/wpe/wpeview/WPEViewImeTest.java new file mode 100644 index 000000000..1f1138d21 --- /dev/null +++ b/wpe/src/androidTest/java/com/wpe/wpeview/WPEViewImeTest.java @@ -0,0 +1,94 @@ +package com.wpe.wpeview; + +import android.content.Context; +import android.graphics.Insets; +import android.util.Log; +import android.view.WindowInsets; +import android.view.inputmethod.InputMethodManager; + +import androidx.annotation.NonNull; +import androidx.lifecycle.Lifecycle; +import androidx.test.core.app.ActivityScenario; +import androidx.test.ext.junit.rules.ActivityScenarioRule; +import androidx.test.runner.AndroidJUnit4; + +import org.junit.Rule; +import org.junit.Test; +import org.junit.runner.RunWith; + +import java.util.concurrent.CountDownLatch; +import java.util.concurrent.TimeUnit; +import java.util.concurrent.atomic.AtomicBoolean; + +import static org.junit.Assert.assertFalse; +import static org.junit.Assert.assertTrue; + +@RunWith(AndroidJUnit4.class) +public class WPEViewImeTest { + @Rule + public ActivityScenarioRule wpeViewActivityTestRule = + new ActivityScenarioRule<>(WPEViewTestActivity.class); + @Test + public void testIMEVisible() { + CountDownLatch latch = new CountDownLatch(1); + ActivityScenario scenario = wpeViewActivityTestRule.getScenario(); + scenario.onActivity(activity -> { + activity.getWPEView().setWPEViewClient(new WPEViewClient() { + @Override + public void onPageFinished(@NonNull WPEView view, @NonNull String url) { + String focusScript = "function onDocumentFocused() {\n" + + " document.getElementById('editor').focus();\n" + + " test.onEditorFocused();\n" + + "}\n" + + "(function() {\n" + + "if (document.hasFocus()) {\n" + + " onDocumentFocused();" + + "} else {\n" + + " window.addEventListener('focus', onDocumentFocused);\n" + + "}})();"; + view.evaluateJavascript(focusScript, new WPECallback() { + @Override + public void onResult(String value) { + latch.countDown(); + } + }); + } + }); + + String htmlDocument = ""; + activity.getWPEView().loadHtml(htmlDocument, null); + }); + + scenario.moveToState(Lifecycle.State.RESUMED); + + try { + latch.await(10, TimeUnit.SECONDS); + } catch (InterruptedException e) { + throw new RuntimeException(e); + } + + try { + // Give IME time to popup + Thread.sleep(10000); + } catch (InterruptedException e) { + throw new RuntimeException(e); + } + + CountDownLatch secondLatch = new CountDownLatch(1); + + AtomicBoolean keyboardShown = new AtomicBoolean(false); + scenario.onActivity(activity -> { + keyboardShown.set(activity.getWPEView().getRootWindowInsets().isVisible(WindowInsets.Type.ime())); + + secondLatch.countDown(); + }); + + try { + secondLatch.await(10, TimeUnit.SECONDS); + } catch (InterruptedException e) { + throw new RuntimeException(e); + } + + assertTrue(keyboardShown.get()); + } +} diff --git a/wpe/src/androidTest/java/com/wpe/wpeview/WPEViewTestActivity.java b/wpe/src/androidTest/java/com/wpe/wpeview/WPEViewTestActivity.java new file mode 100644 index 000000000..82444151b --- /dev/null +++ b/wpe/src/androidTest/java/com/wpe/wpeview/WPEViewTestActivity.java @@ -0,0 +1,30 @@ +package com.wpe.wpeview; + +import android.app.Activity; +import android.os.Bundle; +import android.util.Log; +import android.widget.LinearLayout; + +public class WPEViewTestActivity extends Activity { + + private WPEView wpeView; + + @Override + public void onCreate(Bundle savedInstanceState) { + super.onCreate(savedInstanceState); + + LinearLayout linearLayout = new LinearLayout(this); + linearLayout.setOrientation(LinearLayout.VERTICAL); + linearLayout.setLayoutParams(new LinearLayout.LayoutParams(LinearLayout.LayoutParams.MATCH_PARENT, + LinearLayout.LayoutParams.MATCH_PARENT)); + + wpeView = new WPEView(getApplicationContext()); + wpeView.setLayoutParams(new LinearLayout.LayoutParams(LinearLayout.LayoutParams.MATCH_PARENT, + LinearLayout.LayoutParams.MATCH_PARENT, 1f)); + linearLayout.addView(wpeView); + + setContentView(linearLayout); + } + + public WPEView getWPEView() { return wpeView; } +} diff --git a/wpe/src/main/AndroidManifest.xml b/wpe/src/main/AndroidManifest.xml index 0988b73e4..685a551ab 100644 --- a/wpe/src/main/AndroidManifest.xml +++ b/wpe/src/main/AndroidManifest.xml @@ -4,6 +4,16 @@ + + + + + + + + diff --git a/wpe/src/main/cpp/Browser/Page.cpp b/wpe/src/main/cpp/Browser/Page.cpp index 516bdc105..ae98dd33c 100644 --- a/wpe/src/main/cpp/Browser/Page.cpp +++ b/wpe/src/main/cpp/Browser/Page.cpp @@ -62,11 +62,13 @@ class JNIPageCache final : public JNI::TypedClass { static void onLoadChanged(Page* page, WebKitLoadEvent loadEvent, WebKitWebView* /*webView*/) noexcept { + Logging::logDebug("Page::onLoadChanged() [tid %d]", gettid()); callJavaMethod(getJNIPageCache().m_onLoadChanged, page->m_pageJavaInstance.get(), static_cast(loadEvent)); } static void onLoadProgress(Page* page, GParamSpec* /*pspec*/, WebKitWebView* webView) noexcept { + Logging::logDebug("Page::onLoadProgress() [tid %d]", gettid()); callJavaMethod(getJNIPageCache().m_onLoadProgress, page->m_pageJavaInstance.get(), static_cast(webkit_web_view_get_estimated_load_progress(webView))); } diff --git a/wpe/src/main/java/com/wpe/wpe/Page.java b/wpe/src/main/java/com/wpe/wpe/Page.java index e45e9608b..76d469985 100644 --- a/wpe/src/main/java/com/wpe/wpe/Page.java +++ b/wpe/src/main/java/com/wpe/wpe/Page.java @@ -78,7 +78,7 @@ public final class Page { private native void nativeClose(long nativePtr); private native void nativeDestroy(long nativePtr); private native void nativeLoadUrl(long nativePtr, @NonNull String url); - private native void nativeLoadHtml(long nativePtr, @NonNull String content, @NonNull String baseUri); + private native void nativeLoadHtml(long nativePtr, @NonNull String content, @Nullable String baseUri); private native void nativeGoBack(long nativePtr); private native void nativeGoForward(long nativePtr); private native void nativeStopLoading(long nativePtr); @@ -177,7 +177,7 @@ public void loadUrl(@NonNull String url) { nativeLoadUrl(nativePtr, url); } - public void loadHtml(@NonNull String content, @NonNull String baseUri) { + public void loadHtml(@NonNull String content, @Nullable String baseUri) { Log.d(LOGTAG, "loadHtml(..., '" + baseUri + "')"); nativeLoadHtml(nativePtr, content, baseUri); } diff --git a/wpe/src/main/java/com/wpe/wpeview/WPEView.java b/wpe/src/main/java/com/wpe/wpeview/WPEView.java index 01829e269..3530cc22b 100644 --- a/wpe/src/main/java/com/wpe/wpeview/WPEView.java +++ b/wpe/src/main/java/com/wpe/wpeview/WPEView.java @@ -235,7 +235,7 @@ public void loadUrl(@NonNull String url) { * @param content The HTML content to load. * @param baseUri The base URI for the content loaded. */ - public void loadHtml(@NonNull String content, @NonNull String baseUri) { + public void loadHtml(@NonNull String content, @Nullable String baseUri) { originalUrl = baseUri; page.loadHtml(content, baseUri); }