diff --git a/README.md b/README.md index ff084aa..863c340 100644 --- a/README.md +++ b/README.md @@ -1,6 +1,6 @@ # Android Sample App for axe DevTools Mobile by Deque -A sample application built solely to showcase axe DevTools Android automated espresso test implementation. It is non-functional and made inaccessibly by design. +A sample application built solely to showcase axe DevTools Android automated espresso test implementation. It is non-functional and made inaccessible by design. ## Helpful Links - Library documentation and more information on automated or manual testing can be found at [docs.deque.com](https://docs.deque.com/devtools-mobile/). @@ -8,29 +8,70 @@ A sample application built solely to showcase axe DevTools Android automated esp ------ -## Getting Started: +## Getting Started +We currently support two ways to scan your app for accessibility issues, both delivered through our Gradle Plugin. -1. Clone the repository +#### Auto Scan +Users looking to keep the amount of code needed to implement our library to a minimum should look at +Auto Scan. Enabling it lets our Gradle Plugin take on the boilerplate for you. With a few lines of +configuration in your `build.gradle` file, a report is generated for you that covers the accessibility +violations in your app, with no test code required. We prefer this implementation in our own use of this library, so please try it out! + +#### Targeted Scan +For those who would like more control over when and what gets scanned, you can run targeted scans by +calling `axe.scan()` directly in your tests. This lets you scan a specific screen or state exactly when +you want to. + +### Setting Up Auto Scan +1. Clone the repository. 2. Set up a new project on [axe Developer Hub](https://axe.deque.com/axe-watcher) to set up a Project ID and API Key. - - Or Grab an API key from the [settings page](https://axe.deque.com/settings) -3. In `app/build.gradle`, add your API Key in the `AXE_DEVTOOLS_APIKEY` variable -4. If you set up a project add your Project ID in the `app/build.gradle` under the `AXE_DEVTOOLS_PROJECT_ID` variable +3. In `app/build.gradle`, add `id 'com.deque.android' version '1.+'` to your plugins block, then add your API key and project ID to the axeDevTools block and set `axeAutoScanMode = true` as shown in the example below. ```groovy -android { - def AXE_DEVTOOLS_APIKEY = "YOUR_API_KEY_HERE" - def AXE_DEVTOOLS_PROJECT_ID = "AXE_PROJECT_ID_HERE" +axeDevTools { + axeMobileApiKey = axe_apikey + axeProjectId = axe_project_id + axeAutoScanMode = true +} +``` +If you would like to customize the implementation of this further the following example shows all the configuration options we have available to you: +```groovy +axeDevTools { + axeMobileApiKey = axe_apikey + axeProjectId = axe_project_id + axeAutoScanMode = true // to enable auto scan mode (default false) + axeUploadResults = false // if you want to only look at results locally (default true) + axeAccountUrl = "https://www.custom-url.com" // found in your project settings on axe Developer Hub (default https://axe.deque.com) + axeHtmlReportPath = "/path/to/desired/folder" // customize where your accessibility reports are saved (defaults to your project's build/reports directory) } ``` +### Setting Up Targeted Scan +Targeted scans use the same Gradle Plugin, but with Auto Scan turned off so you control when scans run. Call `axe.scan()` in your tests wherever you want to capture results — see `InstrumentationRegistryExampleTest` and `AxeTestClass` for a working example. + +1. Clone the repository. +2. Set up a new project on [axe Developer Hub](https://axe.deque.com/axe-watcher) to set up a Project ID and API Key. +3. In `app/build.gradle`, add `id 'com.deque.android' version '1.+'` to your plugins block and add your API key and project ID to the axeDevTools block (leave `axeAutoScanMode` omitted or set to `false`): + + ```groovy + axeDevTools { + axeMobileApiKey = axe_apikey + axeProjectId = axe_project_id + } + ``` +4. Call `axe.scan()` in your test code where you want to run a scan, as shown in `AxeTestClass`. + +> **Prefer to manage the dependency yourself?** Instead of applying the Gradle Plugin, you can add our base library directly with `androidTestImplementation 'com.deque.android:axe-devtools-android:current-release'` in your `dependencies` block. Note that Auto Scan is not available when using this approach. + +## Running the Tests -Once you add your variables you are ready to start scanning the application using the espresso tests. +Once you have set up either Auto Scan or a Targeted Scan, you are ready to start scanning the application using the Espresso tests. -You can see accessibility testing in action through the `ExampleEndToEndAccessibilityTest` Espresso test or any other test in the `androidTest` folder. -The `androidTest` folder contains examples of `Jeptpack Compose`, `XML` and `UiAutomator`. +You can see accessibility testing in action through the `AutoScanDemoTest` and `InstrumentationRegistryExampleTest` tests, or any other test in the `androidTest` folder. +These include examples using `Jetpack Compose` and `UiAutomator`. You can kick it off from Android Studio, or through an automated service such as Perfecto, Sauce Labs, etc. _Note:_ -- This project is set to use the Android Gradle plugin version 8.11.2. Running the project with newer, or older, JDK versions may require you to update the Gradle plugin version accordingly. Please refer to [Android's Updating the Gradle Plugin](https://developer.android.com/studio/releases/gradle-plugin#updating-plugin) documentation for more. +- This project is set to use the Android Gradle plugin version 8.10.1. Running the project with newer, or older, JDK versions may require you to update the Gradle plugin version accordingly. Please refer to [Android's Updating the Gradle Plugin](https://developer.android.com/studio/releases/gradle-plugin#updating-plugin) documentation for more. ### Perfecto diff --git a/app/build.gradle b/app/build.gradle index 1034c63..5644fb7 100644 --- a/app/build.gradle +++ b/app/build.gradle @@ -4,6 +4,15 @@ plugins { id 'com.perfectomobile.instrumentedtest.gradleplugin' id 'org.jetbrains.kotlin.plugin.serialization' version '2.2.20' id("org.jetbrains.kotlin.plugin.compose") version "2.2.20" + id 'com.deque.android' version '1.1.0' +} + +axeDevTools { + axeMobileApiKey = project.findProperty('axe_apikey') ? axe_apikey : "" + axeProjectId = project.findProperty('axe_project_id') ? axe_project_id : "" + axeAutoScanMode = true + axeAccountUrl = "https://axe.deque.com" + axeUploadResults = false } android { @@ -157,11 +166,6 @@ dependencies { implementation 'com.google.android.material:material:1.10.0' implementation 'androidx.lifecycle:lifecycle-runtime-ktx:2.6.2' - androidTestImplementation 'com.deque.android:axe-devtools-android:8.3.0' - - // MLKit implementation for use with ADT Library - implementation 'com.google.mlkit:text-recognition:16.0.1' - implementation 'androidx.activity:activity-compose:1.8.0' implementation platform('androidx.compose:compose-bom:2023.03.00') implementation 'androidx.compose.ui:ui' diff --git a/app/src/androidTest/java/com/deque/mobile/axedevtoolssampleapp/AutoScanDemoTest.kt b/app/src/androidTest/java/com/deque/mobile/axedevtoolssampleapp/AutoScanDemoTest.kt new file mode 100644 index 0000000..bf85487 --- /dev/null +++ b/app/src/androidTest/java/com/deque/mobile/axedevtoolssampleapp/AutoScanDemoTest.kt @@ -0,0 +1,64 @@ +package com.deque.mobile.axedevtoolssampleapp + +import androidx.compose.ui.test.assertIsDisplayed +import androidx.compose.ui.test.junit4.createAndroidComposeRule +import androidx.compose.ui.test.onNodeWithText +import androidx.test.espresso.Espresso.onView +import androidx.test.espresso.action.ViewActions.click +import androidx.test.espresso.assertion.ViewAssertions.matches +import androidx.test.espresso.matcher.ViewMatchers.isDisplayed +import androidx.test.espresso.matcher.ViewMatchers.withId +import androidx.test.espresso.matcher.ViewMatchers.withText +import androidx.test.ext.junit.runners.AndroidJUnit4 +import org.junit.Rule +import org.junit.Test +import org.junit.runner.RunWith + +/** + * Auto Scan Sample Test Suite: + * + * The purpose of this test class is to provide a sample test suite that emulates what a typical user + * experience might be like while using our Auto Scan feature. + * + * Please follow the README to ensure that you have properly set up the build.gradle file for this feature. + * If the project is properly set up for Auto Scan you should see an HTML report show up in your build/reports + * directory as well as a result uploaded to your scan result dashboard. + */ +@RunWith(AndroidJUnit4::class) +class AutoScanDemoTest { + + @get:Rule + val composeTestRule = createAndroidComposeRule() + + private fun openTab(menuItemId: Int) = onView(withId(menuItemId)).perform(click()) + + private fun getString(resId: Int): String = + composeTestRule.activity.getString(resId) + + @Test + fun catalogTabIsShownOnLaunch() { + // Catalog is the start destination, so its heading should be visible immediately. + onView(withId(R.id.catalog_heading)) + .check(matches(isDisplayed())) + .check(matches(withText(R.string.catalog))) + } + + @Test + fun navigatingToMenuShowsComposeContent() { + openTab(R.id.menu) + + // The Menu screen is rendered with Compose, so assert against the Compose tree. + composeTestRule.waitForIdle() + composeTestRule.onNodeWithText(getString(R.string.customer)).assertIsDisplayed() + composeTestRule.onNodeWithText(getString(R.string.james_anderson)).assertIsDisplayed() + composeTestRule.onNodeWithText(getString(R.string.log_out)).assertIsDisplayed() + } + + @Test + fun tabSelectionStateIsRestoredWhenReturningToCatalog() { + openTab(R.id.catalog) + onView(withId(R.id.catalog_heading)) + .check(matches(isDisplayed())) + .check(matches(withText(R.string.catalog))) + } +} \ No newline at end of file diff --git a/app/src/androidTest/java/com/deque/mobile/axedevtoolssampleapp/AxeTestClass.kt b/app/src/androidTest/java/com/deque/mobile/axedevtoolssampleapp/AxeTestClass.kt new file mode 100644 index 0000000..23954f9 --- /dev/null +++ b/app/src/androidTest/java/com/deque/mobile/axedevtoolssampleapp/AxeTestClass.kt @@ -0,0 +1,37 @@ +package com.deque.mobile.axedevtoolssampleapp + +import com.deque.mobile.axedevtoolssampleapp.test.BuildConfig +import com.deque.mobile.devtools.AxeDevTools +import org.junit.AfterClass + +abstract class AxeTestClass { + + init { + /** + * Start a session on axe Developer Hub with a valid API key and your project ID. If + * a session has already been started for this test suite, that session will be used instead + * of creating a new session. + * + * You can find and create projects here: https://axe.deque.com/axe-watcher + */ + + axe.startScanSession( + apiKey = BuildConfig.AXE_DEVTOOLS_APIKEY, + projectId = BuildConfig.AXE_DEVTOOLS_PROJECT_ID + ) + } + + companion object { + val axe = AxeDevTools() + + @JvmStatic + @AfterClass + fun afterClass() { + // Generates a report of all scans in a given test class to a report that + // tells you which accessibility violations we found. + axe.generateHtmlReportAndSummary("axe") + + axe.tearDown() + } + } +} \ No newline at end of file diff --git a/app/src/androidTest/java/com/deque/mobile/axedevtoolssampleapp/InstrumentationRegistryExampleTest.kt b/app/src/androidTest/java/com/deque/mobile/axedevtoolssampleapp/InstrumentationRegistryExampleTest.kt index 3969191..0487822 100644 --- a/app/src/androidTest/java/com/deque/mobile/axedevtoolssampleapp/InstrumentationRegistryExampleTest.kt +++ b/app/src/androidTest/java/com/deque/mobile/axedevtoolssampleapp/InstrumentationRegistryExampleTest.kt @@ -5,35 +5,29 @@ import androidx.test.platform.app.InstrumentationRegistry import androidx.test.uiautomator.By import androidx.test.uiautomator.UiDevice import androidx.test.uiautomator.Until -import com.deque.mobile.axedevtoolssampleapp.test.BuildConfig -import com.deque.mobile.devtools.AxeDevTools -import org.junit.After import org.junit.Before import org.junit.Rule import org.junit.Test -class InstrumentationRegistryExampleTest { +/** + * Instrumentation Registry Sample Test Suite: + * + * The purpose of this test class is to provide a sample test suite that emulates what a typical user + * experience might be like while running a targeted scan via the InstrumentationRegistry. + * + * Please follow the README to ensure that you have properly set up the build.gradle file for this feature. + * If the project is properly set up you should see an HTML report show up in your build/reports + * directory as well as a result uploaded to your scan result dashboard. + * + * Please also check out the AxeTestClass for an example of how to set up our main interface. + */ + +class InstrumentationRegistryExampleTest : AxeTestClass() { @Rule @JvmField val rule: ActivityScenarioRule = ActivityScenarioRule(MainActivity::class.java) - - private val axe = AxeDevTools() - - init { - /** - * Start a session on axe Developer Hub with a valid API key and your project ID. - * - * You can find and create a projects here : https://axe.deque.com/axe-watcher - */ - - axe.startSession( - apiKey = BuildConfig.AXE_DEVTOOLS_APIKEY, - projectId = BuildConfig.AXE_DEVTOOLS_PROJECT_ID - ) - } - @Before fun setupAxeDevTools() { val instrumentation = InstrumentationRegistry.getInstrumentation() @@ -75,12 +69,7 @@ class InstrumentationRegistryExampleTest { // } // assertEquals(6, fails) -// 3. Save the result JSON to a local file for later use +// 3. Save the AxeResult JSON to a local file for later use scanResultHandler?.saveResultToLocalStorage("axe") } - - @After - fun tearDown() { - axe.tearDown() - } } \ No newline at end of file