Home

Awesome

<h1 align="center" style="text-align:center;"> UIGO </h1> <p align="center">📱 零代码快准稳 UI 智能录制回放平台 🚀</p> <p align="center">3 像素内自动精准定位,2 毫秒内自动精准等待,录制回放快、准、稳!</p> <p align="center" > <a href="https://github.com/TommyLemon/UIGO/tree/master/UIAuto-Android"><img src="https://img.shields.io/badge/Demo-Android26%2B-brightgreen.svg?style=flat"></a> <a href="https://github.com/TommyLemon/UIGO/tree/master/APIJSONApp"><img src="https://img.shields.io/badge/App-Android26%2B-brightgreen.svg?style=flat"></a> <a href="https://github.com/APIJSON/APIJSON-Demo/tree/master/APIJSON-Java-Server/APIJSONBoot"><img src="https://img.shields.io/badge/Server-Java1.8%2B-brightgreen.svg?style=flat"></a> <a href="https://github.com/TommyLemon/UIGO/tree/master/MySQL"><img src="https://img.shields.io/badge/MySQL-5.7%2B-brightgreen.svg?style=flat"></a> </p> <p align="center" > <a href="https://github.com/TommyLemon/UIGO#%E7%A4%BA%E4%BE%8B%E9%A1%B9%E7%9B%AE">录制回放</a> <a href="https://github.com/TommyLemon/UIGO#%E5%BF%AB%E9%80%9F%E4%B8%8A%E6%89%8B">快速上手</a> <a href="http://apijson.cn/ui">在线工具</a> </p> <p align="center" > <img src="https://raw.githubusercontent.com/TommyLemon/UIGO/master/logo.png" /> </p>
<br /> <p align="center" > <img width="720" src="https://github.com/TommyLemon/UIGO/assets/5738175/49edbcdb-2bc6-4dfb-bd30-4f9aaafe4e25" /> </p> <img width="1280" src="https://github.com/TommyLemon/UIGO/assets/5738175/54cf82d0-99b0-4085-ab89-d54ff95ef4c4" href="https://www.bilibili.com/video/BV1wA4m137ha" /> <img width="1280" src="https://github.com/TommyLemon/UIGO/assets/5738175/032de745-e49a-43f3-b368-84fdbd6a97d7" href="https://search.bilibili.com/all?keyword=UIGO" />

UIGO - 📱 零代码快准稳 UI 智能录制回放平台 🚀

3 像素内自动精准定位,2 毫秒内自动精准等待,录制回放快、准、稳!<br /> 适用于 一次录制到处回放、反复回归界面操作、App UI/功能 自动化测试、<br /> 帮助开发快速复现和排查 bug、方便判断 bug 原因出在前端还是后端 等,<br /> 大量减少耗时费力又无聊的重复手工操作,大幅提高手工和自动化测试效率,<br /> 强力杜绝 测试和开发、前端和后端 关于缺陷单踢皮球等各种低效扯皮内耗!<br />

用户包含腾讯,应微信团队邀请分享了 零代码测试工具与实践(API•单元•UI)

支持功能

特点优势

相比各种 UI 录制回放/自动化测试 的 其它平台/工具/框架:<br /><br /> 1.它们录制过程各种别扭难用反人类,甚至还需要开发/维护用例脚本、每个用例都写一大堆代码频繁部署等;<br /> UIGO 不需要写任何代码,录制几乎是按和人正常操作完全一样的方式,操作简单易用,录制回放快、准、稳!<br /> <br /> 2.它们很难兼容各种不同宽高比分辨率屏幕,720P, 1080P 等 16:9 屏幕录制最多只能较好地在 16:9 屏幕回放,<br />即便手写代码或图像比对等也很难在列表项 View id/图标 重复控件精准定位,经常点错位置导致大量回放失败;<br /> UIGO 则能很好地支持 16:9, 19.5:9 等各种不同屏幕录制,然后在 720P, 1080P, 2K, 1080X2340, 1440X3200 <br />等各种 不同机型、不同系统、不同屏幕 基本都能很好地精准回放,偏差基本仅在相当于一根头发丝的 3 像素内!<br /> <br /> 3.它们要到处人为设置/调整操作步骤等待时间,还总是要么等太久、要么还没返回就过早执行下一步导致出错,<br />因为几乎无法保证网络请求在精准时间内返回,所以总是界面没加载完就滑动、弹窗没显示就点了"确定"位置等;<br /> UIGO 则会自动精准等待 App 发送的各种 HTTP API 网络请求,偏差基本在 2 毫秒内,比眨眼一次还要快 50 倍,<br />像专业的测试工程师一样精准高效地等待数据和 UI 都加载好并执行 点击、长按、滑动、缩放 等每一步对应操作!<br /> <br />

原理说明

被测项目不需要写任何用例脚本代码(逻辑代码、注解代码、配置代码等全都不要),<br /> UIGO 会自动录制 UI 触屏操作、虚拟+实体按键操作、HTTP API 网络请求与响应、<br /> Activity, Fragment, Dialog, PopupWindow 等各种组件(控件)元素的生命周期 等,<br /> 回放时根据录制触摸点所在被分割球划分的 上、下、左、右、居中、等比 等区域<br /> 以及 屏幕分辨率、状态栏高度、导航栏高度、键盘高度 等来自动计算出回放触摸点,<br /> 再加上 id(如果有) 相同且距离最近的 View 区域来辅助微调,高度精准回放触屏操作!<br /> 对 返回按键、键盘按键 甚至 输入框编辑过程的每个变化的字符 也都能精准无误地还原!<br />

<br />

示例项目

UIGO Android 简单测试 App 直接 下载 (第一次可能失败,返回报错 JSON,一般重试一次就可以)<br /> UIGO Android 复杂客户端 App 直接 下载 (第一次可能失败,返回报错 JSON,一般重试一次就可以)

安装 App 必须授权 显示悬浮窗、读写文件存储 这两个权限

其它申请的权限也尽可能都勾选授权,如果不能提前授权,则在使用时弹出是否申请权限弹窗后再确认授权 <br /> https://github.com/TommyLemon/APIAuto/issues/61#issuecomment-1997047600

<br />

早期零代码单机录制不同分辨率双机同时回放视频

https://www.bilibili.com/video/BV1CK4218788 <br /> <img width="1280" src="https://github.com/TommyLemon/UIGO/assets/5738175/3bb97384-72d9-4b45-ab2c-b0916291ef9f" href="https://www.bilibili.com/video/BV1CK4218788" />

早期管理端网页工具零代码远程控制手机录制回放视频

https://www.bilibili.com/video/BV1wA4m137ha <img width="1280" src="https://github.com/TommyLemon/UIGO/assets/5738175/e50d00a1-22e8-4908-9d88-579a178965f1" href="https://www.bilibili.com/video/BV1wA4m137ha" />

早期仿微信朋友圈复杂 App 录制回放,弹窗、输入、网页、滑动、点击等

https://www.bilibili.com/video/BV1fH4y1E7gD <img width="1280" src="https://github.com/TommyLemon/UIGO/assets/5738175/bed421fa-f1a9-47ea-a265-e34853b2d1c8" href="https://www.bilibili.com/video/BV1fH4y1E7gD" />

零代码录制回放 H5 移动端网页输入、滑动、点击等操作

https://www.bilibili.com/video/BV1TK421C7y4 <img width="1280" src="https://github.com/TommyLemon/UIGO/assets/5738175/5c29bec6-2e21-4230-907c-f4ccb1faa4ef" href="https://www.bilibili.com/video/BV1TK421C7y4" />

录制用例

1.按业务 App 提供的方式打开 UIAuto 管理首页,例如 APIJSONApp 是登录后点击首页标题,UIAuto-Android 是点击首页 [自动 UI 测试] 按钮 <br />

2.点击 Record 录制按钮 > 点击顶部悬浮长条中间的 Record 录制 按钮开始录制 > 正常操作 App > 完成一个用例过程后,点击半透明圆形 〇 悬浮球完成录制 <br />

3.(可选)点击右下角 post 按钮上传录制的操作和数据等到后端数据库,可先编辑底部的后端服务器 HTTP URL Host 地址为你自己部署的 APIJSON 后端服务 <br />

录制注意事项

1.按贴靠方式调整分割球位置及贴靠方式 <br /> 按下(MotionEvent.ACTION_DOWN 事件)对应 View 的屏幕 [X, Y] 坐标位置前,必须先完成这步(如果已经配置正确则跳过) <br />

分割球对应横纵两条分割线把屏幕分成了 左上、右上、左下、右下 4 个区域,对应数学上按坐标轴划分的 第 二、一、三、四 象限; <br /> 点击顶部控制条左上角 # 按钮可切换单双分割球,相比单分割球可更精细地设置高级属性 <br />

双分割球中间会出现一个长方形触控区域,默认点击位置按居中处理,可点击切换 center 居中, ratio 等比, top 靠上, bottom 靠下, left 靠左, right 靠右;<br /> 分割球本身贴靠方式可以点击分割球来切换 top_left 左上, top_right 右上, bottom_left 左下, bottom_right 右下, ratio 等比, <br /> ratio_top 靠上并垂直纵向等比, ratio_bottom 靠下并垂直纵向等比, ratio_left 靠左并水平横向等比, ratio_right 靠右并水平横向等比 <br />

2.滑动要该快则快、该慢则慢 <br /> 分页列表/网格等滑动尽量快速到底,保证不同宽高比分辨率屏幕上都能在同侧(↓ 从上往下、↑ 从下往上、→ 从左往右、← 从右往左)上显示一致; <br /> 点按内部 View 前可缓慢滑动保证目标出现在屏幕内,且手离开屏幕前先在同一位置稳住别动,保证不继续自动滚动(不同机型/系统对惯性滚动处理不一致导致偏差) <br />

3.触控的 View 尽量都有 id,且尽量不要改动 <br /> UIGO 会对有 id 的被触控 View 在回放时进行微调触控位置(不改变 UI 布局,仅微调触屏输入坐标位置),灵活适配不同屏幕尤其是不同品牌机型的 UI 差异 <br />

4.最好开启托管服务器代理来录制 HTTP API 接口请求与响应流量,用于回放操作时同时回放数据 <br /> 保证回放到某个步骤和录制到同一步骤时布局一致,不会触控点按错位<br />

5.不要跳出 App,例如跳转外部浏览器或微信、支付宝等其它 App 来上网、登录、分享、支付等 <br /> 目前不支持跨 App 录制回放,当前 App 外的对应事件录制不到也回放不了,打算后续支持对接支持跨 App 录制回放/自动化测试 的项目,<br /> 目前碰到这个问题要么避开,要么等待或手动点 》按钮跳过等待来直接继续执行下一步 <br />

<br />

回放用例

1.参考 录制用例 1 来打开 UIAuto 管理首页 > 点击左下角 Remote 共享列表 按钮 > 点击打开其中一个和 App 及账号对应的用例,或者 录制用例 后直接进入用例详情界面 <br />

2.点击用例详情界面左下角 Replay 回放按钮,返回到用例对应开始录制的业务 App 界面,保证状态一致(都是刚进入后没有操作的界面,或者分页列表都下拉刷新过等) <br />

3.点击顶部悬浮长条中间的 Replay 回放 按钮开始回放,观察每步操作前后,App 的 UI 展示、界面数据、界面跳转、弹窗显示、键盘输入等是否符合预期(和录制时表现一样) <br />

回放注意事项

1.回放开始时 App 初始状态必须和录制时一致 <br /> 包括所在页面(最好都在首页)、列表/网格滚动距离(最好都是没滚动的初始值 0)、中英文等语言设置、字体大小和样式设置、缓存/调试等自定义配置 等 <br />

2.系统的中英文等语言、字体大小和样式 等设置尽可能保持一致 <br />

3.最好开启托管服务器代理来回放录制时的 HTTP API 接口请求与响应流量数据 <br />

<br />

快速上手

可先跳过这个步骤,先下载体验 App 安装包,安装后 按以下 录制用例、回放用例 文档来操作

集成到被测项目 Android 客户端 App

1.依赖 UnitAuto-Apk

UnitAuto-Apk 导入到你项目 app moudule 所在目录settings.gradle

include ':UnitAuto-Apk'

app moudule 目录build.gradle

dependencies {
    api project(':UnitAuto-Apk')
}
<br />
2.依赖 UIAuto

UIAuto 导入到你项目 app moudule 所在目录settings.gradle

include ':UIAuto'

app moudule 目录build.gradle

dependencies {
    api project(':UIAuto')
}
<br />
2.初始化 UIAuto

Application onCreate 方法 中初始化

    @Override
    public void onCreate() {
        super.onCreate();
        UIAutoApp.STEP_TIMEOUT = 30*1000; // 一般为 HTTP API 网络请求超时时间 ms 毫秒值
        UIAutoApp.getInstance().initUIAuto(this);
    }
<br />
3.提供 UIAuto 管理界面入口

AndroidManifest.xml 中注册 UIAutoActivity

<manifest ... >
    <application ... >
      
        <activity
            android:name="uiauto.UIAutoActivity"
            android:label="@string/ui"
            android:windowSoftInputMode="adjustPan"
            android:configChanges="orientation|screenSize"
            android:screenOrientation="portrait"
            />
        <activity
            android:name="uiauto.UIAutoListActivity"
            android:label="@string/ui"
            android:windowSoftInputMode="adjustPan"
            android:screenOrientation="portrait"
            />

        <activity
            android:name="unitauto.apk.UnitAutoActivity"
            android:label="@string/unit"
            android:windowSoftInputMode="adjustPan"
            android:configChanges="orientation|screenSize"
            android:screenOrientation="userLandscape"
            />
      
     </application>
</manifest>

可在你项目的任何界面新增一个按钮或其它形式的入口,仅 DEBUG 模式下展示

    <Button
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:onClick="onClickUI"
        android:text="UIAutoActivity"
        android:textAllCaps="false"
        />

参考 layout/main_tab_activity <br /> <br /> 点击这个入口跳转到 UIAutoActivity

    public void onClickUI(View v) {
        startActivity(UIAutoActivity.createIntent(this));
    }

参考 MainTabActivity <br /> <br />

4.通知 HTTP API 请求与相应

在 HTTP API 发起请求处加上

		UIAutoApp.getInstance().post(new Runnable() {
			@Override
			public void run() {
				UIAutoApp.getInstance().onHTTPEvent(
					action, type, method, host, url, headerStr, reqBodyStr, null, activity, fragment
				);
			}
		});

参考:<br /> https://github.com/TommyLemon/UIGO/blob/master/APIJSONApp/app/src/main/java/apijson/demo/manager/HttpManager.java#L137-L147

在 HTTP API 响应结果处加上

		UIAutoApp.getInstance().post(new Runnable() {
			@Override
			public void run() {
				UIAutoApp.getInstance().onHTTPEvent(
					- action, responseCode, method, host, url, headerStr, reqBodyStr, resBodyStr, null, activity, fragment
				);
			}
		});

参考:<br /> https://github.com/TommyLemon/UIGO/blob/master/APIJSONApp/app/src/main/java/apijson/demo/manager/HttpManager.java#L168-L179

<br />
5.通知 Dialog, PopupWindow 显示和隐藏

业务代码中如果使用了 android.app.AlertDialog,且录制回放用例涉及,则可以换成 uiauto.AlertDialog, <br /> 最简单的方式是顶部菜单 Edit > Find > Replace in files 全局搜索 import android.app.AlertDialog, <br /> 然后点搜索弹窗右下角 Replace All 按钮批量改为 import uiauto.AlertDialog。 <br /> 如果因为用了自定义或第三方通用 Base Alert Dialog 不方便替换,则可以 extends uiauto.AlertDialog 或在里面加上:

	@Override
	public void show() {
		super.show();
		UIAutoApp.getInstance().onUIAutoDialogShow(this); // 通知已显示
	}

	// 拦截隐藏事件监听
	private OnDismissListener listener;
	@Override
	public void setOnDismissListener(OnDismissListener listener) {
		this.listener = listener;
	}

	private Activity context;
	private void init(Context ctx) {
		this.context = (Activity) ctx;

		super.setOnDismissListener(new OnDismissListener() {
			@Override
			public void onDismiss(DialogInterface dialog) {
				if (listener != null) {
					listener.onDismiss(dialog);
				}

				UIAutoApp.getInstance().onUIAutoDialogDismiss(Dialog.this); // 通知已隐藏
			}
		});
	}

	// 每个可重写的构造方法都保证调用到 init 方法
	public AlertDialog(Context context) {
		super(context);
		init(context);
	}

	public AlertDialog(Context context, @StyleRes int themeResId) {
		super(context, themeResId);
		init(context);
	}

	public AlertDialog(Context context, boolean cancelable, OnCancelListener cancelListener) {
		super(context, cancelable, cancelListener);
		init(context);
	}

所有 Dialog, DatePickerDialog, TimePickerDialog, ProgressDialog, CharacterPickerDialog 等也同上处理。 <br />

<br />

业务代码中如果使用了 android.widget.PopupWindow,且录制回放用例涉及,则可以换成 uiauto.PopupWindow, <br /> 最简单的方式是顶部菜单 Edit > Find > Replace in files 全局搜索 import android.widget.PopupWindow, <br /> 然后点搜索弹窗右下角 Replace All 按钮批量改为 import uiauto.PopupWindow。 <br /> 如果因为用了自定义或第三方通用 Base Popup Window 不方便替换,则可以 extends uiauto.PopupWindow 或在里面加上:

    private android.widget.PopupWindow popupWindow;
    private View view;

    @Override
    public void showAsDropDown(View anchor, int xoff, int yoff, int gravity) {
        super.showAsDropDown(anchor, xoff, yoff, gravity);

        if (view == null) {
            try {
                Field field = android.widget.ListPopupWindow.class.getDeclaredField("mPopup");
                field.setAccessible(true);
                popupWindow = (android.widget.PopupWindow) field.get(this);
                //  popupWindow.setOutsideTouchable(false);

                Field dvField = android.widget.PopupWindow.class.getDeclaredField("mDecorView");
                dvField.setAccessible(true);
                view = (View) dvField.get(popupWindow);

                if (view == null) {
                    View cv = popupWindow.getContentView();
                    view = cv == null ? null : cv.getRootView();
                }
            } catch (Throwable e) {
                e.printStackTrace();
            }
        }

        if (view == null) {
            View cv = getContentView();
            view = cv == null ? null : cv.getRootView();
        }

        Window w = getWindow();

        // 通知已显示
        UIAutoApp app = UIAutoApp.getInstance();
        app.onUIAutoWindowCreate(w.getCallback(), w);
        app.setCurrentPopupWindow(popupWindow, view, null, context, null);
    }


    // 拦截隐藏事件监听
    private android.widget.PopupWindow.OnDismissListener listener;
    @Override
    public void setOnDismissListener(android.widget.PopupWindow.OnDismissListener listener) {
        this.listener = listener;
    }

    public Window getWindow() {
        Activity ctx = view == null ? null : (Activity) view.getContext();
        if (ctx == null) {
            ctx = context;
        }
        return ctx.getWindow();
    }

    private Activity context;
    private void init(Context ctx) {
        this.context = (Activity) ctx;

        super.setOnDismissListener(new android.widget.PopupWindow.OnDismissListener() {
            @Override
            public void onDismiss() {
                if (listener != null) {
                    listener.onDismiss();
                }

                Window w = getWindow();

                // 通知已隐藏
                UIAutoApp app = UIAutoApp.getInstance();
                app.onUIAutoWindowDestroy(w.getCallback(), w);
                app.setCurrentPopupWindow(null, null, null, context, null);
            }
        });
    }

    // 每个可重写的构造方法都保证调用到 init 方法
    public PopupWindow(Context context) {
        super(context);
        init(context);
    }

    public PopupWindow(Context context, AttributeSet attrs) {
        super(context, attrs);
        init(context);
    }

    public PopupWindow(Context context, AttributeSet attrs, @AttrRes int defStyleAttr) {
        super(context, attrs, defStyleAttr);
        init(context);
    }

    public PopupWindow(Context context, AttributeSet attrs, @AttrRes int defStyleAttr, @StyleRes int defStyleRes) {
        super(context, attrs, defStyleAttr, defStyleRes);
        init(context);
    }

    public PopupWindow(Context context, View contentView) {
        super(contentView);
        init(context);
    }

    public PopupWindow(Context context, int width, int height) {
        super(width, height);
        init(context);
    }

    public PopupWindow(Context context, View contentView, int width, int height) {
        super(contentView, width, height);
        init(context);
    }

    public PopupWindow(Context context, View contentView, int width, int height, boolean focusable) {
        super(contentView, width, height, focusable);
        init(context);
    }

所有 ListPopupWindow 等也同上处理。 <br /> <br />

Java 后端 Server

可先跳过,使用 http://apijson.cn:8080http://apijson.cn:9090 代替 <br />

见 APIJSON-Demo 后端上手 <br /> https://github.com/APIJSON/APIJSON-Demo?tab=readme-ov-file#1%E5%90%8E%E7%AB%AF%E4%B8%8A%E6%89%8B

<br />

录制、回放用例

见以上 录制用例回放用例 的说明。

<br /><br />

常见问题

1.apijson.cn 访问不了

托管服务地址改为 http://47.98.196.224:8080 <br /> https://github.com/TommyLemon/APIAuto/issues/13

<br />

更多常见问题 <br /> https://github.com/TommyLemon/APIAuto/issues

<br />

技术交流

关于作者

https://github.com/TommyLemon<br /> <img width="1280" src="https://github.com/TommyLemon/UIGO/assets/5738175/ec77df98-ff9b-43aa-b2f1-2fce2549d276">

如果有什么问题或建议可以 去 APIAuto 提 issue,交流技术,分享经验。<br > 如果你解决了某些 bug,或者新增了一些功能,欢迎 提 PR 贡献代码,感激不尽。 <br /> <br />

其它项目

APIJSON 🚀 腾讯零代码、全功能、强安全 ORM 库 🏆 后端接口和文档零代码,前端(客户端) 定制返回 JSON 的数据和结构

APIAuto 敏捷开发最强大易用的 HTTP 接口工具,机器学习零代码测试、生成代码与静态检查、生成文档与光标悬浮注释,集 文档、测试、Mock、调试、管理 于一体的一站式体验

UnitAuto 机器学习零代码单元测试平台,零代码、全方位、自动化 测试 方法/函数 的正确性、可用性和性能

SQLAuto 智能零代码自动化测试 SQL 语句执行结果的数据库工具,任意增删改查、任意 SQL 模板变量、一键批量生成参数组合、快速构造大量测试数据

Android-ZBLibrary Android MVP 快速开发框架,Demo 全面,注释详细,使用简单,代码严谨

持续更新

https://github.com/TommyLemon/UIGO/commits/master

我要赞赏

UIGO 从私有仓库默默开发了 3 年多到现在终于开源了,Apache 证书对商用和非商用都很友好。<br /> 创作不易、坚持更难,右上角点亮 ⭐ Star 支持/收藏下本项目吧,谢谢 ^_^ <br /> https://github.com/TommyLemon/UIGO <br /> <br />