其他测试框架 Espresso - 不入虎穴焉得虎子之 Webview 测试

非洲赵子龙 for 安卓Espresso菜刀队 · February 19, 2019 · 763 hits

现在的App一般都是Hybrid的,常见的情况是有一个Native的按钮,点了之后会打开一个Webview界面,那么在Espresso里如何测试呢?现在我们从0开始,来分析此类Webview该如何测试.

  • 环境准备之自己写个App

正所谓不入虎穴焉得虎子!一个好的测试人员应该知其然,更知其所以然!不要害怕,难道写一个简单的App这么难吗?在你下定决心的时候,就已经赢了一半! 你不开始,就永远没有深入了解它是如何运作的机会!Never!

  1. 新建项目

    打开Android Studio, File>New Project, Application name随便取,然后Next>Next,选择默认的Empty Activity, Next>Finish. 除了命名外,我们全部采用默认的配置,建了一个空的Activity。什么是Activity呢? 你可以简单的理解为一个页面,对,就是一个页面,就是你最常见到的页面,仅此而已,所有页面上的变化,不过是修改了部分显示控件的内容而已。就这么简单!我们建了一个Activity,就可以在这个页面上随便绘制想要的内容:按钮啦,输入框啦等等等等……建完Project之后,AS(Android Studio)会开始build你的项目(没错,AS就是这么严谨,哪怕是空的项目啥也没有,它也会默认帮你编译一遍,看是否缺什么依赖!Google棒棒哒!),不出意外,build完了之后大概是下面的样子(以下是Mac系统下的目录,Windows的请参照这个):

    大概每个目录的意思:

    1. .gradle: Gradle编译系统,版本由下面的gradle>wrapper指定
    2. .idea:IDE生成的专用工程配置文件,类似Eclipse的.project, VSCode的.vscode一样
    3. app:项目的所有代码和资源文件

      1. app/build:app模块编译输出的文件
      2. app/libs: 放置引用的类库文件
      3. app/src: 放置应用的主要文件目录
      4. app/src/androidTest:单元测试目录
      5. app/src/main:主要的项目目录和代码
      6. app/src/main/assets:放置原生文件,里面的文件会保留原有格式,文件的读取需要通过流
      7. app/src/main/java:项目的源代码
      8. app/src/main/res:项目的资源
      9. app/src/main/res/drawable:存放各种位图文件(.png,.jpg,.9png,.gif等)和drawable类型的XML文件
      10. app/src/main/res/drawable-v24:存放自定义Drawables类(Android API 24开始,可在XML中使用)
      11. app/src/main/res/layout:存放布局文件
      12. app/src/main/res/menu:存放菜单文件
      13. app/src/main/res/mipmap-hdpi:存放高分辨率图片资源
      14. app/src/main/res/mipmap-mdpi:存放中等分辨率图片资源
      15. app/src/main/res/mipmap-xdpi:存放超高分辨率图片资源
      16. app/src/main/res/mipmap-xxdpi:存放超超分辨率图片资源
      17. app/src/main/res/mipmap-xxxdpi:存放超超超高分辨率图片资源
      18. app/src/main/res/raw:存放各种原生资源(音频,视频,一些XML文件等)
      19. app/src/main/res/values: 存放各种配置资源(颜色,尺寸,样式,字符串等)
      20. app/src/main/res/values/attrs.xml:自定义控件时用的较多,自定义控件的属性
      21. app/src/main/res/values/arrays.xml:定义数组资源
      22. app/src/main/res/values/colors.xml:定义颜色资源
      23. app/src/main/res/values/dimens.xml:定义尺寸资源
      24. app/src/main/res/values/string.xml:定义字符串资源
      25. app/src/main/res/values/styles.xml:定义样式资源
      26. app/src/main/res/values-v11:在API 11+的设备上调用
      27. app/src/main/res/values-v14:在API 14+的设备上调用
      28. app/src/main/res/values-v21:在API 21+的设备上调用
      29. app/src/main/res/AndroidManifest.xml:项目的清单文件(名称、版本、SDK、权限等配置信息)
      30. app/src/.gitignore:忽略的文件或者目录
      31. app/app.iml:app模块的配置文件
      32. app/build.gradle:app模块的gradle编译文件
      33. app.*:app模块的代码混淆配置文件
    4. build:系统生成的文件目录

    5. gradle: wrapper的jar和配置文件所在的位置

    6. .gitignore: 忽略的文件或者目录

    7. build.gradle:项目的gradle编译文件

    8. gradle.properties: gradle相关的全局属性设置

    9. gradlew: 编译脚本,可以在命令行执行打包

    10. gradlew.bat:windows下的gradle wrapper可执行文件

    11. local.properties:配置SDK/NDK所在的路径

    12. settings.gradle:设置相关的gradle脚本

    13. webViewDemo.iml:项目模块的相关信息

    14. External Libraries:项目依赖的库,编译时自动下载

      这些信息仅供参考,我们大概了解下就行,当然,以后深入了,我们会对以上目录或文件会有更深的理解!

  2. 编写源码

app的源码在app/src/main/java目录下,复制如下代码替换MainActivity里的内容(注意替换为自己的包名,需要根据自己的情况进行替换):

MainActivity.java:

package com.example.pis.webviewdemo;

import android.support.v7.app.AppCompatActivity;
import android.os.Bundle;
import android.text.TextUtils;
import android.view.KeyEvent;
import android.view.View;
import android.view.ViewGroup;
import android.view.Window;
import android.webkit.WebSettings;
import android.webkit.WebView;
import android.widget.Button;
import android.widget.EditText;


public class MainActivity extends AppCompatActivity {

public static final String TAG_WEB_VIEW_EXAMPLE = "WEB_VIEW_EXAMPLE";

private EditText urlEditor;

private Button loadUrlButton;

private Button backButton;

private Button forwardButton;

private Button clearCacheButton;

private Button showSnippetButton;

private WebView webView;

private CustomWebviewClient customWebviewClient;

private CustomWebChromeClient customWebChromeClient;

@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);

// Request show progress bar in the activity window title.
Window activityWindow = this.getWindow();
activityWindow.requestFeature(Window.FEATURE_PROGRESS);

setContentView(R.layout.activity_main);

setTitle("dev2qa.com - Android WebView Example.");

// Initialize this activity used controls.
initControl();

// Click this button to load user specified web url page.
loadUrlButton.setOnClickListener(new View.OnClickListener() {
@Override
public void onClick(View view) {
String url = urlEditor.getText().toString();
if(!TextUtils.isEmpty(url))
{
webView.loadUrl(url);
}else {
webView.loadData("<font color=red><b>Please input web page url start with http or https, then click load button.</b></font>", "text/html", "utf-8");
}
}
});

// Click this button to go to previous page.
backButton.setOnClickListener(new View.OnClickListener() {
@Override
public void onClick(View v) {
if(webView.canGoBack())
{
webView.goBack();
}
}
});

// Click this button to go to next page.
forwardButton.setOnClickListener(new View.OnClickListener() {
@Override
public void onClick(View v) {
if(webView.canGoForward())
{
webView.goForward();
}
}
});

// Click this button to clear webview cache, history and html form data.
clearCacheButton.setOnClickListener(new View.OnClickListener() {
@Override
public void onClick(View v) {
// Clear cache page.
webView.clearCache(true);

// Clear webview history.
webView.clearHistory();

// Clear html form data.
webView.clearFormData();
}
});

showSnippetButton.setOnClickListener(new View.OnClickListener() {
@Override
public void onClick(View v) {
webView.loadData("<strong><font color=red>Hello WebView</font></strong>", "text/html", "utf-8");
}
});
}

// Initialize the edit text, buttons and WebView component.
private void initControl()
{

urlEditor = (EditText)findViewById(R.id.web_view_url_editor);

loadUrlButton = (Button)findViewById(R.id.web_view_load_button);

backButton = (Button)findViewById(R.id.web_view_back_button);

forwardButton = (Button)findViewById(R.id.web_view_forward_button);

clearCacheButton = (Button)findViewById(R.id.web_view_clear_cache_button);

showSnippetButton = (Button)findViewById(R.id.web_view_show_snippet_html);

webView = (WebView)findViewById(R.id.web_view_component);

WebSettings webSettings = webView.getSettings();

webSettings.setJavaScriptEnabled(true);

// Set custom webview client.
customWebviewClient = new CustomWebviewClient();
webView.setWebViewClient(customWebviewClient);

// Set custom web chrome client.
customWebChromeClient = new CustomWebChromeClient();
customWebChromeClient.setSourceActivity(this);
webView.setWebChromeClient(customWebChromeClient);

webView.setLayerType(WebView.LAYER_TYPE_SOFTWARE, null);
}


@Override
protected void onDestroy() {
// Destroy WebView object when activity is destroyed.
if(webView!=null)
{
ViewGroup viewGroup = (ViewGroup) webView.getParent();
viewGroup.removeView(webView);

webView.destroy();
webView = null;
}

super.onDestroy();
}

@Override
public boolean onKeyDown(int keyCode, KeyEvent event) {

if(keyCode == KeyEvent.KEYCODE_BACK)
{
// Process android device back menu
if(webView!=null && webView.canGoBack())
{
webView.goBack();
return true;
}
}
return super.onKeyDown(keyCode, event);
}
}

CustomWebviewClient.java:

package com.example.pis.webviewdemo;

/**
* Created by pis on 2019/2/19.
*/


import android.graphics.Bitmap;
import android.util.Log;
import android.webkit.WebResourceError;
import android.webkit.WebResourceRequest;
import android.webkit.WebView;
import android.webkit.WebViewClient;

/**
* Created by Jerry on 2/26/2018.
*/


public class CustomWebviewClient extends WebViewClient {

@Override
public void onReceivedError(WebView view, WebResourceRequest request, WebResourceError error) {
super.onReceivedError(view, request, error);
}

@Override
public void onPageStarted(WebView view, String url, Bitmap favicon) {
Log.d(MainActivity.TAG_WEB_VIEW_EXAMPLE, "onPageStarted url : " + url);
super.onPageStarted(view, url, favicon);
}

@Override
public void onPageFinished(WebView view, String url) {
Log.d(MainActivity.TAG_WEB_VIEW_EXAMPLE, "onPageFinished url : " + url);
super.onPageFinished(view, url);
}

@Override
public boolean shouldOverrideUrlLoading(WebView view, WebResourceRequest request) {
if(request!=null) {
Log.d(MainActivity.TAG_WEB_VIEW_EXAMPLE, "shouldOverrideUrlLoading request.toString() : " + request.toString());
Log.d(MainActivity.TAG_WEB_VIEW_EXAMPLE, "shouldOverrideUrlLoading request.getUrl().toString() : " + request.getUrl().toString());
view.loadUrl(request.getUrl().toString());
}else
{
Log.d(MainActivity.TAG_WEB_VIEW_EXAMPLE, "shouldOverrideUrlLoading request is null.");
}
return false;
}
}

CustomWebChromeClient:

package com.example.pis.webviewdemo;

import android.support.v7.app.AppCompatActivity;
import android.util.Log;
import android.webkit.JsPromptResult;
import android.webkit.JsResult;
import android.webkit.WebChromeClient;
import android.webkit.WebView;
/**
* Created by pis on 2019/2/19.
*/


public class CustomWebChromeClient extends WebChromeClient {

private AppCompatActivity sourceActivity;

private String webPageTitle = "";

public AppCompatActivity getSourceActivity() {
return sourceActivity;
}

public void setSourceActivity(AppCompatActivity sourceActivity) {
this.sourceActivity = sourceActivity;
}

// This method is invoked when webview load page.
@Override
public void onProgressChanged(WebView view, int newProgress) {

// set title and
sourceActivity.setTitle("Page Loading...... - dev2qa.com");
int showProgress = newProgress * 100;
sourceActivity.setProgress(showProgress);

if(newProgress == 100)
{
sourceActivity.setTitle(webPageTitle);
}

super.onProgressChanged(view, newProgress);
Log.d(MainActivity.TAG_WEB_VIEW_EXAMPLE, "onProgressChanged newProgress : " + newProgress);
}

@Override
public void onReceivedTitle(WebView view, String title) {
super.onReceivedTitle(view, title);
webPageTitle = title;
Log.d(MainActivity.TAG_WEB_VIEW_EXAMPLE, "onReceivedTitle title : " + title);
}

@Override
public boolean onJsAlert(WebView view, String url, String message, JsResult result) {
Log.d(MainActivity.TAG_WEB_VIEW_EXAMPLE, "onJsAlert url : " + url + " , message : " + message);
return super.onJsAlert(view, url, message, result);
}

@Override
public boolean onJsConfirm(WebView view, String url, String message, JsResult result) {
Log.d(MainActivity.TAG_WEB_VIEW_EXAMPLE, "onJsConfirm url : " + url + " , message : " + message);
return super.onJsConfirm(view, url, message, result);
}

@Override
public boolean onJsPrompt(WebView view, String url, String message, String defaultValue, JsPromptResult result) {
Log.d(MainActivity.TAG_WEB_VIEW_EXAMPLE, "onJsPrompt url : " + url + " , message : " + message);
return super.onJsPrompt(view, url, message, defaultValue, result);
}
}

以上三个文件都是放在app/src/main/java目录下。

下面,我们还需要简单的布局文件,在src>main>res下面,有一个layout目录,目录下面默认有一个activity_main.xml文件,复制以下内容替换自己的文件内容:

activity_main.xml

<?xml version="1.0" encoding="utf-8"?>
<android.support.constraint.ConstraintLayout xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:app="http://schemas.android.com/apk/res-auto"
xmlns:tools="http://schemas.android.com/tools"
android:layout_width="match_parent"
android:layout_height="match_parent"
tools:context="com.example.pis.webviewdemo.MainActivity">

<RelativeLayout
android:layout_width="match_parent"
android:layout_height="match_parent">

<EditText
android:id="@+id/web_view_url_editor"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:hint="Input a url to browse."/>

<LinearLayout
android:id="@+id/web_view_button_layout"
android:layout_below="@id/web_view_url_editor"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:orientation="horizontal">

<Button
android:id="@+id/web_view_load_button"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_weight="1"
android:text="Load"/>

<Button
android:id="@+id/web_view_back_button"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_weight="1"
android:text="Prev"/>

<Button
android:id="@+id/web_view_forward_button"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_weight="1"
android:text="Next"/>

<Button
android:id="@+id/web_view_clear_cache_button"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_weight="1"
android:text="Clear"/>

<Button
android:id="@+id/web_view_show_snippet_html"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_weight="1"
android:text="Hello" />

</LinearLayout>

<WebView
android:id="@+id/web_view_component"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:layout_below="@id/web_view_button_layout"
android:background="@color/colorPrimaryDark"></WebView>

</RelativeLayout>

</android.support.constraint.ConstraintLayout>

注意上面的tools:context行,声明了当前这个布局文件是给哪个Activity服务的,你需要替换为自己的对应的Activity名,其他的保持不变.
另外,我们的src>main下面的AndroidMainfest.xml如下供参考:

<?xml version="1.0" encoding="utf-8"?>
<manifest xmlns:android="http://schemas.android.com/apk/res/android"
package="com.example.pis.webviewdemo">

<!-- Play web url audio file required permission. -->
<uses-permission android:name="android.permission.INTERNET" />

<application
android:allowBackup="true"
android:icon="@mipmap/ic_launcher"
android:label="@string/app_name"
android:roundIcon="@mipmap/ic_launcher_round"
android:supportsRtl="true"
android:theme="@style/AppTheme">

<activity android:name="com.example.pis.webviewdemo.MainActivity">
<intent-filter>
<action android:name="android.intent.action.MAIN" />

<category android:name="android.intent.category.LAUNCHER" />
</intent-filter>
</activity>
</application>

</manifest>

现在,我们开始试着编译了!一般情况下,新手会遇到一些问题,都是一些简单的问题,相信你会很快修改完毕,并编译成功(比如你可能会遇到最低版本要求问题:代码里有个函数getUrl()是在sdk>21才能使用的,因此你可能需要改一下minSdkVersion为21,在哪儿改呢?相信你一定会找到~),有问题可以通过搜索引擎来搜索一下,以上问题都是基本的问题,很容易调试完成!调试成功运行的界面如下图:

输入一个url,比如http://www.bing.com就可以看到网址被load成功了,说明我们的webview起作用了!

  • 开始测试 发现测试Native<->Webview的方式不唯一,而且要根据具体场景,不能一概而论,在考虑要不要删掉此贴……越说深了,越觉得是在科普Android Webview实现的基础了😂!总之,Android Webview实现和嵌入的方式跟产品的需求有关,很难用一种实现完全阐明测试开发方法!
No Reply at the moment.
需要 Sign In 后方可回复, 如果你还没有账号请点击这里 Sign Up