Espresso Web

Espresso-Web 是用于处理 Android WebView UI 组件的入口点。Espresso-Web 重用来自流行的 WebDriver API 的 Atoms 来检查和控制 WebView 的行为。

何时使用 Espresso-Web

使用 Espresso-Web 测试您的混合应用,尤其是应用的原生 UI 组件与其 WebView UI 组件的集成。您可以将 Espresso-Web API 与其他 Espresso API 结合使用,以完全交互 WebView 对象内的 Web 元素。

如果您只需要测试 WebView 本身,而不是 WebView 与应用中原生组件之间的交互,请考虑使用 WebDriver 等框架编写通用 Web 测试。如果您使用 Web 测试框架,则无需使用 Android 设备或 Java 虚拟机,这使得您的测试运行得更快且更可靠。也就是说,Espresso-Web 允许您重用自定义 WebDriver atoms,这为您提供了很大的灵活性,尤其是在编写计划针对独立 Web 应用和包含 Android UI 的应用运行的测试时。

工作原理

类似于 Espresso 的 onData() 方法,WebView 交互包含多个 Atoms。 WebView 交互使用 Java 编程语言和 JavaScript 桥的组合来完成其工作。由于通过公开来自 JavaScript 环境的数据不会引入竞争条件的可能性——Espresso 在基于 Java 的一侧看到的所有内容都是一个孤立的副本——因此完全支持从 Web.WebInteraction 对象返回数据,允许您验证从请求返回的所有数据。

什么是 WebDriver Atom?

WebDriver 框架使用 Atoms 以编程方式查找和操作 Web 元素。Atoms 用于 WebDriver 允许浏览器操作。从概念上讲,Atom 类似于 ViewAction,这是一个独立的单元,可在您的 UI 中执行操作。您使用定义的方法列表(例如 findElement()getElement())公开 Atoms,以从用户的角度驱动浏览器。但是,如果您直接使用 WebDriver 框架,则需要适当地协调 Atoms,这需要非常冗长的逻辑。

在 Espresso 中,类 WebWeb.WebInteraction 包装了此样板代码,并为与 WebView 对象交互提供了类似 Espresso 的感觉。因此,在 WebView 的上下文中,Atoms 用作传统 Espresso ViewMatchersViewActions 的替代。

然后 API 看起来非常简单

Kotlin

onWebView()
    .withElement(Atom)
    .perform(Atom)
    .check(WebAssertion)

Java

onWebView()
    .withElement(Atom)
    .perform(Atom)
    .check(WebAssertion);

要了解更多信息,请阅读 Selenium 的 Atoms 文档

实现 WebView

请按照以下部分中的指南,在应用的测试中使用 WebView

要在您的项目中包含 Espresso-Web,请完成以下步骤

  1. 打开应用的 build.gradle 文件。通常这不是顶级 build.gradle 文件,而是 app/build.gradle
  2. 在 dependencies 内部添加以下行

    Groovy

        androidTestImplementation 'androidx.test.espresso:espresso-web:3.6.1'
        

    Kotlin

        androidTestImplementation('androidx.test.espresso:espresso-web:3.6.1')
        
  3. Espresso-Web 仅与 Espresso 2.2 或更高版本以及测试库的 0.3 或更高版本兼容,因此请确保也更新这些行。

    Groovy

        androidTestImplementation 'androidx.test:runner:1.6.1'
        androidTestImplementation 'androidx.test:rules:1.6.1'
        androidTestImplementation 'androidx.test.espresso:espresso-core:3.6.1'
        

    Kotlin

        androidTestImplementation('androidx.test:runner:1.6.1')
        androidTestImplementation('androidx.test:rules:1.6.1')
        androidTestImplementation('androidx.test.espresso:espresso-core:3.6.1')
        

常用 API 用法

当使用 Espresso 在 Android 上使用 WebView 时,onWebView() 方法是主要入口点。您可以使用此方法执行 Espresso-Web 测试,例如以下内容

Kotlin

onWebView()
    .withElement(findElement(Locator.ID, "link_2")) // similar to onView(withId(...))
    .perform(webClick()) // Similar to perform(click())

    // Similar to check(matches(...))
    .check(webMatches(getCurrentUrl(), containsString("navigation_2.html")))

Java

onWebView()
    .withElement(findElement(Locator.ID, "link_2")) // similar to onView(withId(...))
    .perform(webClick()) // Similar to perform(click())

    // Similar to check(matches(...))
    .check(webMatches(getCurrentUrl(), containsString("navigation_2.html")));

在此示例中,Espresso-Web 查找 ID 为 "link_2" 的 DOM 元素并单击它。然后,该工具验证 WebView 是否发送包含 "navigation_2.html" 字符串的 GET 请求。

JavaScript 支持

执行测试时,系统使用 JavaScript 执行所有 WebView 交互。因此,为了支持 JavaScript 评估,被测 WebView 必须启用 JavaScript。

您可以通过在被测 Activity 中将 forceJavascriptEnabled() 作为 操作 来强制启用 JavaScript,如下面的代码片段所示。

@RunWith(AndroidJUnit4::class)
class MyTestSuite {
    @get:Rule val activityScenarioRule =
        activityScenarioRule<MyWebViewActivity>()

    @Test fun testWebViewInteraction() {
        onWebView().forceJavascriptEnabled()
    }
}

常见 Web 交互

Web.WebInteraction 对象的常见交互包括以下内容

  • withElement() 引用 WebView 中的 DOM 元素。

    示例

    Kotlin

    onWebView().withElement(findElement(Locator.ID, "teacher"))

    Java

    onWebView().withElement(findElement(Locator.ID, "teacher"));
  • withContextualElement() 引用 WebView 中相对于另一个 DOM 元素的范围 DOM 元素。您应该首先调用 withElement() 来建立引用 Web.WebInteraction 对象(DOM 元素)。

    示例

    Kotlin

    .withElement(findElement(Locator.ID, "teacher"))
        .withContextualElement(findElement(Locator.ID, "person_name"))

    Java

    .withElement(findElement(Locator.ID, "teacher"))
        .withContextualElement(findElement(Locator.ID, "person_name"));
  • check() 评估条件,确保其解析为 true

    示例

    Kotlin

    onWebView()
        .withElement(findElement(Locator.ID, "teacher"))
        .withContextualElement(findElement(Locator.ID, "person_name"))
        .check(webMatches(getText(), containsString("Socrates")))

    Java

    onWebView()
        .withElement(findElement(Locator.ID, "teacher"))
        .withContextualElement(findElement(Locator.ID, "person_name"))
        .check(webMatches(getText(), containsString("Socrates")));
  • perform() 在 WebView 中执行操作,例如单击元素。

    示例

    Kotlin

    onWebView()
        .withElement(findElement(Locator.ID, "teacher"))
        .perform(webClick())

    Java

    onWebView()
        .withElement(findElement(Locator.ID, "teacher"))
        .perform(webClick());
  • reset() 将 WebView 恢复到其初始状态。当先前的操作(例如单击)引入导航更改导致 ElementReference 和 WindowReference 对象无法访问时,这很有必要。

    注意:虽然在对多页工作流(例如表单提交)进行断言时使用 reset() 很有用,但您的测试通常应范围有限,并侧重于单个页面。

    示例

    Kotlin

    onWebView()
        .withElement(...)
        .perform(...)
        .reset()

    Java

    onWebView()
        .withElement(...)
        .perform(...)
        .reset();

示例

以下示例测试是否在将文本输入 WebView 并选择“提交”按钮后,同一文本会出现在同一 WebView 中的不同元素内。

Kotlin

const val MACCHIATO = "Macchiato"

@RunWith(AndroidJUnit4::class)
class MyEspressoWebTestSuite {

    @Test fun typeTextInInput_clickButton_SubmitsForm() {
        // Create an intent that displays a web form.
        val webFormIntent = Intent()
        // ...

        // Lazily launch the Activity with a custom start Intent per test.
        ActivityScenario.launchActivity(webFormIntent)

        // Selects the WebView in your layout. If you have multiple WebView
        // objects, you can also use a matcher to select a given WebView,
        // onWebView(withId(R.id.web_view)).
        onWebView()
            // Find the input element by ID.
            .withElement(findElement(Locator.ID, "text_input"))

            // Clear previous input and enter new text into the input element.
            .perform(clearElement())
            .perform(DriverAtoms.webKeys(MACCHIATO))

            // Find the "Submit" button and simulate a click using JavaScript.
            .withElement(findElement(Locator.ID, "submitBtn"))
            .perform(webClick())

            // Find the response element by ID, and verify that it contains the
            // entered text.
            .withElement(findElement(Locator.ID, "response"))
            .check(webMatches(getText(), containsString(MACCHIATO)))
    }
}

Java

public static final String MACCHIATO = "Macchiato";

@Test
public void typeTextInInput_clickButton_SubmitsForm() {
    // Create an intent that displays a web form.
    Intent webFormIntent = new Intent();
    // ...

    // Lazily launch the Activity with a custom start Intent per test.
    ActivityScenario.launchActivity(webFormIntent);

    // Selects the WebView in your layout. If you have multiple WebView objects,
    // you can also use a matcher to select a given WebView,
    // onWebView(withId(R.id.web_view)).
    onWebView()
        // Find the input element by ID.
        .withElement(findElement(Locator.ID, "text_input"))

        // Clear previous input and enter new text into the input element.
        .perform(clearElement())
        .perform(DriverAtoms.webKeys(MACCHIATO))

        // Find the "Submit" button and simulate a click using JavaScript.
        .withElement(findElement(Locator.ID, "submitBtn"))
        .perform(webClick())

        // Find the response element by ID, and verify that it contains the
        // entered text.
        .withElement(findElement(Locator.ID, "response"))
        .check(webMatches(getText(), containsString(MACCHIATO)));
}

其他资源

有关在 Android 测试中使用 Espresso-Web 的更多信息,请参阅以下资源。

示例