diff --git a/.github/workflows/omnibar-nightly-e2e-tests.yml b/.github/workflows/omnibar-nightly-e2e-tests.yml new file mode 100644 index 000000000000..045b6e4100cb --- /dev/null +++ b/.github/workflows/omnibar-nightly-e2e-tests.yml @@ -0,0 +1,79 @@ +name: Omnibar End-to-End tests + +on: + schedule: + - cron: '0 7 * * *' # run at 7 AM UTC + workflow_dispatch: + +concurrency: + group: ${{ github.workflow }}-${{ github.ref }} + cancel-in-progress: true + +jobs: + instrumentation_tests: + runs-on: ubuntu-latest + name: End-to-End tests + + steps: + - name: Checkout repository + uses: actions/checkout@v4 + with: + submodules: recursive + + - name: Set up JDK version + uses: actions/setup-java@v4 + with: + java-version-file: .github/.java-version + distribution: 'adopt' + + - name: Create folder + if: always() + run: mkdir apk + + - name: Decode keys + uses: davidSchuppa/base64Secret-toFile-action@v2 + with: + secret: ${{ secrets.FAKE_RELEASE_PROPERTIES }} + fileName: ddg_android_build.properties + destination-path: $HOME/jenkins_static/com.duckduckgo.mobile.android/ + + - name: Decode key file + uses: davidSchuppa/base64Secret-toFile-action@v2 + with: + secret: ${{ secrets.FAKE_RELEASE_KEY }} + fileName: android + destination-path: $HOME/jenkins_static/com.duckduckgo.mobile.android/ + + - name: Setup Gradle + uses: gradle/actions/setup-gradle@v3 + + - name: Assemble the project + run: ./gradlew assemblePlayRelease -Pforce-default-variant -Pskip-onboarding -x lint + + - name: Move APK to new folder + if: always() + run: find . -name "*.apk" -exec mv '{}' apk/playRelease.apk \; + + - name: Omnibar Maestro run + uses: mobile-dev-inc/action-maestro-cloud@v1.9.8 + timeout-minutes: 120 + with: + api-key: ${{ secrets.ROBIN_API_KEY }} + project-id: ${{ vars.ROBIN_ANDROID_PROJECT_ID }} + name: omnibar_${{ github.sha }} + timeout: ${{ vars.ROBIN_TIMEOUT_MINUTES }} + app-file: apk/playRelease.apk + android-api-level: 34 + workspace: .maestro + include-tags: omnibarTest + + - name: Create Asana task when workflow failed + if: ${{ failure() }} + uses: honeycombio/gha-create-asana-task@main + with: + asana-secret: ${{ secrets.ASANA_ACCESS_TOKEN }} + asana-workspace-id: ${{ secrets.GH_ASANA_WORKSPACE_ID }} + asana-project-id: ${{ secrets.GH_ASANA_AOR_PROJECT_ID }} + asana-section-id: ${{ secrets.GH_ASANA_INCOMING_ID }} + asana-task-name: GH Workflow Failure - Omnibar E2E tests + asana-task-description: The Omnibar end-to-end workflow has failed. See https://github.com/duckduckgo/Android/actions/runs/${{ github.run_id }} \ No newline at end of file diff --git a/.maestro/omnibar/shared/omnibar_select_split_type.yaml b/.maestro/omnibar/shared/omnibar_select_split_type.yaml new file mode 100644 index 000000000000..9cb75b5bf465 --- /dev/null +++ b/.maestro/omnibar/shared/omnibar_select_split_type.yaml @@ -0,0 +1,8 @@ +appId: com.duckduckgo.mobile.android +--- +# enable the Split omnibar +- runFlow: ../../shared/open_appearance_settings_screen.yaml +- tapOn: + id: "splitOmnibarToggleImage" +- action: back +- action: back \ No newline at end of file diff --git a/.maestro/omnibar/split_omnibar_search.yaml b/.maestro/omnibar/split_omnibar_search.yaml new file mode 100644 index 000000000000..c5bbfaede227 --- /dev/null +++ b/.maestro/omnibar/split_omnibar_search.yaml @@ -0,0 +1,86 @@ +appId: com.duckduckgo.mobile.android +name: "Split omnibar test" +tags: + - omnibarTest +--- +- retry: + maxRetries: 3 + commands: + - launchApp: + clearState: true + stopApp: true + - runFlow: ../shared/skip_all_onboarding.yaml + - runFlow: ./shared/omnibar_select_split_type.yaml + +# verify that autocomplete suggestions are shown when typing +- tapOn: + id: "omnibarTextInput" + optional: true +- inputText: "reddit" +- assertVisible: + id: "autoCompleteSuggestionsList" +- assertVisible: + text: "reddit.com" + childOf: + id: "autoCompleteSuggestionsList" + +# verify that submitting a search query opens SERP +- pressKey: Enter +- assertVisible: + id: "browserLayout" +- assertVisible: + text: "reddit" + childOf: + id: "browserLayout" + +# add the page to favorites (for future assertions) +- runFlow: ../shared/browser_screen/click_on_menu_button.yaml +- tapOn: + text: "add bookmark" +- runFlow: ../shared/browser_screen/click_on_menu_button.yaml +- tapOn: + text: "bookmarks" +- tapOn: + id: "com.duckduckgo.mobile.android:id/trailingIcon" + index: 0 +- tapOn: + text: "add to favorites" +- action: back + +# verify that clearing the text and submitting a URL opens the web page +- tapOn: + id: "omnibarTextInput" +- tapOn: + id: "clearTextButton" +- inputText: "eff.org" +- pressKey: Enter +- assertVisible: + id: "browserLayout" +- extendedWaitUntil: + visible: + text: "https://www.eff.org/" + childOf: + id: "omnibarTextInput" + timeout: 10000 +- assertVisible: + text: "https://www.eff.org/" + childOf: + id: "omnibarTextInput" + +# verify that tapping on a favorite opens the web page (SERP in this case) +- tapOn: + id: "omnibarTextInput" +- tapOn: + id: "quickAccessFavicon" + childOf: + id: "focusedFavourites" +- assertVisible: + id: "browserLayout" +- assertVisible: + text: "reddit" + childOf: + id: "omnibarTextInput" +- assertVisible: + text: "reddit" + childOf: + id: "browserLayout" diff --git a/app/src/main/java/com/duckduckgo/app/browser/omnibar/OmnibarFeatureRepository.kt b/app/src/main/java/com/duckduckgo/app/browser/omnibar/OmnibarFeatureRepository.kt index 994b1e94f885..7baf2646162d 100644 --- a/app/src/main/java/com/duckduckgo/app/browser/omnibar/OmnibarFeatureRepository.kt +++ b/app/src/main/java/com/duckduckgo/app/browser/omnibar/OmnibarFeatureRepository.kt @@ -24,6 +24,7 @@ import com.duckduckgo.app.pixels.remoteconfig.AndroidBrowserConfigFeature import com.duckduckgo.app.settings.db.SettingsDataStore import com.duckduckgo.common.utils.DispatcherProvider import com.duckduckgo.di.scopes.AppScope +import com.duckduckgo.privacy.config.api.PrivacyConfigCallbackPlugin import com.squareup.anvil.annotations.ContributesBinding import com.squareup.anvil.annotations.ContributesMultibinding import dagger.SingleInstanceIn @@ -35,6 +36,10 @@ import javax.inject.Inject scope = AppScope::class, boundType = MainProcessLifecycleObserver::class, ) +@ContributesMultibinding( + scope = AppScope::class, + boundType = PrivacyConfigCallbackPlugin::class, +) @SingleInstanceIn(AppScope::class) @ContributesBinding(scope = AppScope::class, boundType = OmnibarRepository::class) open class OmnibarFeatureRepository @Inject constructor( @@ -42,7 +47,7 @@ open class OmnibarFeatureRepository @Inject constructor( private val browserFeatures: AndroidBrowserConfigFeature, private val dispatcherProvider: DispatcherProvider, @AppCoroutineScope private val coroutineScope: CoroutineScope, -) : OmnibarRepository, MainProcessLifecycleObserver { +) : OmnibarRepository, MainProcessLifecycleObserver, PrivacyConfigCallbackPlugin { private var isSplitOmnibarFlagEnabled: Boolean = false private var isNewCustomTabFlagEnabled: Boolean = false @@ -74,4 +79,13 @@ open class OmnibarFeatureRepository @Inject constructor( settingsDataStore.isSplitOmnibarSelected = false } } + + override fun onPrivacyConfigDownloaded() { + coroutineScope.launch(dispatcherProvider.io()) { + if (settingsDataStore.omnibarType != OmnibarType.SPLIT) { + isSplitOmnibarFlagEnabled = browserFeatures.splitOmnibar().isEnabled() + } + isNewCustomTabFlagEnabled = browserFeatures.newCustomTab().isEnabled() + } + } }