Skip to content

【实战练习】用户端App自动化测试实战

【实战练习】用户端 App 自动化测试实战

实战需求

点击查看实战详情

实现思路

uml diagram

优化点

返回的实现

在自动化测试过程中,由于真实执行的过程,一定是有多条用例一起执行的。所以在用例执行完成之后,需要将 App 的状态设置为初始页面,以免影响后续的用例的执行。

Python 实现
    def back_to_main_page(self, num):
        # 返回主界面
        for i in range(num):
            self.driver.back()
    def setup(self):
        print("setup")
        # 点击通讯录按钮
        self.driver.activate_app("com.tencent.wework")
Java 实现

通过显示等待的机制,实现回到首页的操作。

@AfterEach
public void tearDown() {
    WebDriverWait wait = new WebDriverWait(driver, Duration.ofSeconds(15), Duration.ofMillis(500));
    // 条件检查函数
    ExpectedCondition<WebElement> condition = input -> {
        driver.navigate().back();
        return driver.findElement(AppiumBy.xpath("//*[@text='消息']"));
    };
    // 使用 until 方法并传递条件检查函数
    WebElement element = wait.until(condition);
    element.click();
}

成员添加失败自动化测试用例场景

Python 实现

root_path = os.path.dirname(os.path.dirname(os.path.abspath(__file__)))

class TestAddDuplicate:

    def setup_class(self):
        # 设置启动参数
        print("setup_class")
        # 定义一个字典
        caps = {}
        # 设置 app 安装平台
        caps["appium:platformName"] = "Android"
        # 设置 app 安装平台的版本
        caps["appium:platformVersion"] = "6"
        # 设置driver
        caps["appium:automationName"] = "UiAutomator2"
        # 设备的名字
        caps["appium:deviceName"] = "MuMu"
        # 设置 app 的包名
        caps["appium:appPackage"] = "com.tencent.wework"
        # 设置 app 启动页
        caps["appium:appActivity"] = ".launch.LaunchSplashActivity"
        # 不清空缓存
        caps["appium:noReset"] = True
        # 实例化 appiumOptions 对象
        options = AppiumOptions().load_capabilities(caps)
        # 连接 appium server
        self.driver = webdriver.Remote("http://localhost:4723/wd/hub", options=options)
        # 设置全局的隐式等待
        self.driver.implicitly_wait(10)
        # 实例化 faker 对象
        faker = Faker("zh_CN")
        # 生成姓名
        self.name = faker.name()
        # 生成手机号
        self.phonenum = faker.phone_number()

    def back_to_main_page(self, num):
        # 返回主界面
        for i in range(num):
            self.driver.back()


    def setup(self):
        print("setup")
        # 点击通讯录按钮
        self.driver.activate_app("com.tencent.wework")

    def teardown(self):
        print("teardown")
        # 返回上一个界面
        self.back_to_main_page(2)

    def teardown_class(self):
        print("teardown_class")
        # 退出 app
        self.driver.quit()

    def swipe_window(self):
        '''
        滑动界面
        '''
        # 滑动操作
        # 获取设备的尺寸
        size = self.driver.get_window_size()
        # {"width": xx, "height": xx}
        print(f"设备尺寸为 {size}")
        width = size.get("width")
        height = size.get('height')
        # # 获取滑动操作的坐标值
        start_x = width / 2
        start_y = height * 0.8
        end_x = start_x
        end_y = height * 0.2
        # swipe(起始x坐标,起始y坐标,结束x坐标,结束y坐标,滑动时间(单位毫秒))
        self.driver.swipe(start_x, start_y, end_x, end_y, 2000)

    def swipe_find(self, text, max_num=5):
        '''
        滑动查找
        通过文本来查找元素,如果没有找到元素,就滑动,
        如果找到了,就返回元素
        '''
        # 为了滑动操作更快速,不用等待隐式等待设置的时间
        self.driver.implicitly_wait(1)
        for num in range(max_num):
            try:
                # 正常通过文本查找元素
                ele = self.driver.find_element(AppiumBy.XPATH, f"//*[@text='{text}']")
                print("找到元素")
                # 能找到则把隐式等待恢复原来的时间
                self.driver.implicitly_wait(10)
                # 返回找到的元素对象
                return ele
            except Exception:
                # 当查找元素发生异常时
                print(f"没有找到元素,开始滑动")
                print(f"滑动第{num + 1}次")
                # 滑动操作
                self.swipe_window()
        # 把隐式等待恢复原来的时间
        self.driver.implicitly_wait(10)
        # 抛出找不到元素的异常
        raise NoSuchElementException(f"滑动之后,未找到 {text} 元素")

    def enter_contact_page(self):
        """
        进入通讯录页面
        :return:
        """
        # 点击通讯录按钮
        self.driver.find_element(AppiumBy.XPATH, "//*[@text='通讯录']").click()
        # 点击添加成员按钮
        self.swipe_find("添加成员").click()
        # 点击手动输入添加按钮
        self.driver.find_element(AppiumBy.XPATH, "//*[@text='手动输入添加']").click()

    def screenshot(self):
        '''
        截图
        :param path: 截图保存路径
        '''
        # 获取当前时间
        cur_time = time.strftime("%Y-%m-%d-%H-%M-%S")
        # 截图文件名
        file_path = cur_time + ".png"
        # 截图保存目录
        dir_path = os.sep.join([root_path, "screenshot"])
        # 资源目录如果不存在则新创建一个
        if not os.path.isdir(dir_path):
            os.mkdir(dir_path)
        # 截图保存路径
        source_path = os.sep.join([dir_path, file_path])
        # 截图
        self.driver.save_screenshot(source_path)
        # 返回保存图片的路径
        return source_path

    # 填写通讯录信息
    def input_contact_info(self, name, phonenum):
        """
        填写通讯录信息
        :param name:
        :param phonenum:
        :return:
        """
        # 输入姓名
        self.driver.find_element(AppiumBy.XPATH, '//*[contains(@text,"姓名")]/../*[@text="必填"]').send_keys(name)
        # 输入手机号
        self.driver.find_element(AppiumBy.XPATH, '//*[contains(@text,"手机")]/..//*[@text="必填"]').send_keys(phonenum)

    def test_add_contact_duplicate(self):
        """
        1.打开企业微信
        2.点击通讯录
        3.点击添加成员
        4.点击手动输入添加
        5.输入姓名、手机号
        6.点击保存
        7.断言保存成功
        8.返回通讯录页面
        9.点击搜索按钮
        10.输入搜索内容
        11.断言搜索结果
        12.返回通讯录页面
        13.再次进行添加操作
        14.断言保存失败
        :return:
        """
        with allure.step("步骤1:进入企业微信"):
            # 点击通讯录按钮
            self.enter_contact_page()
        with allure.step("步骤2:输入姓名、手机号"):
            # 输入姓名
            self.input_contact_info(self.name, self.phonenum)
        with allure.step("步骤3:点击保存"):
            # 点击保存按钮
            self.driver.find_element(AppiumBy.XPATH, "//*[@text='保存']").click()
            # 断言保存成功
            # 获取toast提示信息
            toast_text = self.driver.find_element(AppiumBy.XPATH, "//*[@class='android.widget.Toast']").text
            image_file = self.screenshot()
            allure.attach.file(image_file, name="toast提示信息", attachment_type=allure.attachment_type.PNG)
            assert toast_text == "添加成功"

        with allure.step("步骤4:返回通讯录页面"):
            self.back_to_main_page(1)
        with allure.step("步骤5:搜索联系人"):
            # 点击搜索按钮
            self.driver.find_element(AppiumBy.XPATH, '//*[@text="悠然科技"]/../../../following-sibling::*/*[1]').click()
            # 输入搜索内容
            self.driver.find_element(AppiumBy.XPATH, "//*[@text='搜索']").send_keys(self.name)
            # 断言搜索结果
            eles = self.driver.find_elements(AppiumBy.XPATH,
                                             '//*[@class="android.widget.ListView"]//*[@class="android.widget.ImageView"]/../../following-sibling::*/*[1]/*[1]')
            image_file = self.screenshot()
            allure.attach.file(image_file, name="列表数据", attachment_type=allure.attachment_type.PNG)
            assert self.name in [ele.text for ele in eles]
        with allure.step("步骤6:返回通讯录页面"):
            self.back_to_main_page(2)
        with allure.step("步骤7:再次进行添加操作"):
            # 点击通讯录按钮
            self.enter_contact_page()
            # 输入信息
            self.input_contact_info(self.name, self.phonenum)
            # 点击保存按钮
            self.driver.find_element(AppiumBy.XPATH, "//*[@text='保存']").click()
        with allure.step("步骤7:断言保存失败"):
            # 获取提示信息
            notice_text = self.driver.find_element(AppiumBy.XPATH, '//*[@text="确定"]/../preceding-sibling::*/*/*').text
            image_file = self.screenshot()
            allure.attach.file(image_file, name="失败提示", attachment_type=allure.attachment_type.PNG)
            assert "手机已存在于通讯录,无法添加" in notice_text

Java 实现
// 成员模块
public class TestMemberFail {
    // AppiumServer版本 2.3.0
    // 前置条件: 登录成功
    public static AndroidDriver driver;
    @BeforeAll
    public static void setUpClass() {
        //配置信息
        UiAutomator2Options uiAutomator2Options = new UiAutomator2Options()
                .setPlatformName("Android")
                // 2.x 版本呢需要添加, 1.x 版本则不需要
                .setAutomationName("uiautomator2")
                // 不会重置app信息,解决登录问题
                .setNoReset(true)
                // 配置 app的package
                .amend("appium:appPackage", "com.tencent.wework")
                // 配置 app的appActivity 注意 企业微信必须用 .launch.LaunchSplashActivity
                .amend("appium:appActivity", ".launch.LaunchSplashActivity")
                // 强制app启动,解决server 掉不起来 应用的问题
                .amend("appium:forceAppLaunch", true)
                // 可以关闭app
                .amend("appium:shouldTerminateApp", true);
        //初始化
        try {
            // appium 1.x 版本 url 换成这个 http://127.0.0.1:4723/wd/hub
            // 配置 server 的地址, 添加应用的配置信息
            driver = new AndroidDriver(new URL("http://127.0.0.1:4723"), uiAutomator2Options);
            // 显示等待
            driver.manage().timeouts().implicitlyWait(Duration.ofSeconds(5));
        } catch (MalformedURLException e) {
            throw new RuntimeException(e);
        }

    }
    // 退回到首页操作
    @AfterEach
    public void tearDown() {
        WebDriverWait wait = new WebDriverWait(driver, Duration.ofSeconds(15), Duration.ofMillis(500));
        // 条件检查函数
        ExpectedCondition<WebElement> condition = input -> {
            driver.navigate().back();
            return driver.findElement(AppiumBy.xpath("//*[@text='消息']"));
        };
        // 使用 until 方法并传递条件检查函数
        WebElement element = wait.until(condition);
        element.click();
    }
    @Test
    public void addMemberFail() throws IOException {
        driver.findElement(AppiumBy.xpath("//*[@text='通讯录']")).click();
        for (int i = 1; i < 10; i++) {
            // 如果没找到,则会出现找不到元素的异常,
            try {
                driver.findElement(AppiumBy.xpath("//*[@text='添加成员']")).click();
                break;
            } catch (Exception e) {
                // 通过try 捕获这个异常,如果出现异常,则说明位置还没到,则做滑动操作
                scrollDown();
            }
        }
        driver.findElement(AppiumBy.xpath("//*[@text='手动输入添加']")).click();
        driver.findElement(AppiumBy.xpath("//*[@text='姓名']/../*[@text='必填']")).sendKeys("东东");
        driver.findElement(AppiumBy.xpath("//*[@text='手机']/..//*[@text='必填']")).sendKeys("15818591232");
        driver.findElement(AppiumBy.xpath("//*[@text='保存']")).click();
        // 获取toast 提示信息,为后续做断言
        String toastMessage = driver.findElement(AppiumBy.xpath("//*[@text='手机已存在于通讯录,无法添加']")).getText();
        ElementScreenBase("添加成员失败");
        assertEquals("手机已存在于通讯录,无法添加", toastMessage);
    }
    // 截图方法
    private void ElementScreenBase(String message) throws IOException {
        // 调用截图方法
        File screenshot = ((TakesScreenshot) driver).getScreenshotAs(OutputType.FILE);
        // 将截图的结果贴到allure报告中
        Allure.addAttachment(message, "image/png", new FileInputStream(screenshot), ".png");
    }
    // 通过 touchAction实现滑动操作
    public static void scrollDown() {
        // 获取屏幕高度和宽度
        int height = driver.manage().window().getSize().getHeight();
        int width = driver.manage().window().getSize().getWidth();
        // 设置滑动起始和结束坐标
        int startY = (int) (height * 0.8);
        int startX = width / 2;
        int endY = (int) (height * 0.2);
        TouchAction touchAction = new TouchAction(driver);
        //  longPress -> 长按  moveTo -》移动到 某个位置
        touchAction.longPress(ElementOption.point(startX, startY))
                .moveTo(ElementOption.point(startX, endY))
                .release()
                .perform();
    }
}