Skip to content

强制等待与隐式等待

强制等待与隐式等待

简介

添加等待是为了确保自动化脚本在执行过程中与应用程序之间的同步和稳定性。

应用程序的响应时间是不确定的,可能存在网络延迟、加载时间、动画效果等因素。如果在执行自动化脚本时没有适当的等待机制,脚本可能会在应用程序还未完成相应操作或加载完成之前继续执行下一步,导致测试失败或产生不稳定的结果。

通过添加适当的等待操作,可以使脚本在关键操作后等待一段时间,以确保应用程序完成相关任务或操作。这可以包括显式等待(例如等待特定元素出现、消失或可点击),或隐式等待(在整个脚本执行过程中设置一个全局的等待时间)。

强制等待

  • 解决方案:在报错的元素操作之前添加等待。
  • 原理:线程休眠一定时间。
  • Python :time.sleep(3)
  • Java:Thread.sleep(3000)

隐式等待

  • 问题:难以确定元素加载的具体等待时间。
  • 解决方案:针对于寻找元素的这个动作,使用隐式等待添加配置。
  • 演练环境:雪球 app。
  • 原理:隐式等待是一种全局的等待方式,设置一个等待时间,轮询查找(默认 0.5 秒)元素是否出现,如果没出现就抛出异常。

Python 代码示例

#设置一个等待时间,轮询查找(默认0.5秒)元素是否出现,如果没出现就抛出异常
driver.implicitly_wait(3)
Java 代码示例

// 设置等待
driver.manage().timeouts().implicitlyWait(Duration.ofSeconds(3));

隐式等待无法解决的问题

  • 元素可以找到,使用点击等操作,出现报错。
  • 原因:

    - 页面元素加载是异步加载过程,通常 xml 会先加载完成,相应的元素属性后加载。 - 元素存在与否是由 xml 决定,元素的交互是由属性决定。 - 隐式等待只关注元素能不能找到,不关注元素能否点击或者进行其他的交互。

  • 解决方案:使用显式等待。

显式等待

  • 示例: WebDriverWait(driver实例, 最长等待时间, 轮询时间).until(结束条件)
  • 原理:在最长等待时间内,轮询,是否满足结束条件。

注意:在初级时期,先关注使用。

示例

使用 appium 官方 Demo apk 进行练习,apk 网盘地址

Python 代码示例

import time
from appium import webdriver
from appium.options.android import UiAutomator2Options
from appium.webdriver.common.appiumby import AppiumBy
from selenium.webdriver.support import expected_conditions
from selenium.webdriver.support.wait import WebDriverWait


class TestWait:

    def setup_class(self):
        '''
        完成 capability 设置
        初始化 driver
        :return:
        '''
        # 设置 cpability
        caps = {
            # 设置 app 安装的平台(Android,iOS)
            "platformName": "Android",
            # 设置 appium 驱动
            "appium:automationName": "uiautomator2",
            # 设置设备名称
            "appium:deviceName": "emulator-5554",
            # 设置被测 app 的包名
            "appium:appPackage": "io.appium.android.apis",
            # 设置被测 app 启动页面的 Activity
            "appium:appActivity": ".ApiDemos"
        }

        # 初始化 driver
        self.driver = webdriver.Remote(
            "http://127.0.0.1:4723",
            options=UiAutomator2Options().load_capabilities(caps)
        )
        # 设置全局隐式等待
        self.driver.implicitly_wait(5)

    def teardown_class(self):
        '''
        关闭 driver
        :return:
        '''
        self.driver.quit()

    def test_wait(self):
        '''
        点击 OS 按钮后等待 3 秒
        显示等待 Morse Code 元素可点击
        输入框输入内容后等待 2 秒
        点击返回按钮后等待 2 秒
        :return:
        '''
        # 测试步骤
        # 找到 OS 元素
        el1 = self.driver.find_element(by=AppiumBy.ACCESSIBILITY_ID, value="OS")
        # 点击 OS 元素
        el1.click()
        # 等待 3 秒
        time.sleep(3)
        # 显示等待 Morse Code 元素可点击
        el2 = WebDriverWait(self.driver, 10).until(
            expected_conditions.element_to_be_clickable(
                (AppiumBy.ACCESSIBILITY_ID, "Morse Code")
            )
        )
        # 找到 Morse Code 元素
        el2.click()
        # 等待 3 秒
        time.sleep(3)
        # 找到输入框元素
        el3 = self.driver.find_element(AppiumBy.ID, "io.appium.android.apis:id/text")
        # 在输入框中输入内容
        el3.send_keys("ceshiren.com")
        # 等待 2 秒
        time.sleep(2)
        # 点击返回按钮
        self.driver.back()
        # 等待 2 秒
        time.sleep(2)
        # 点击返回按钮
        self.driver.back()
        # 断言:判断首页中第一个元素的文本内容是 Access'ibility
        result = self.driver.find_element(
            AppiumBy.XPATH,
            "//*[@resource-id='android:id/text1'][1]"
        )
        print(result.text)
        assert result.text == "Access'ibility"

Java 代码示例

package org.example;

import io.appium.java_client.android.AndroidDriver;
import io.appium.java_client.AppiumBy;
import io.appium.java_client.remote.MobileCapabilityType;
import org.junit.jupiter.api.BeforeAll;
import org.junit.jupiter.api.Test;
import org.openqa.selenium.WebElement;
import org.openqa.selenium.remote.DesiredCapabilities;
import org.openqa.selenium.support.ui.ExpectedConditions;
import org.openqa.selenium.support.ui.WebDriverWait;

import java.net.MalformedURLException;
import java.net.URL;
import java.time.Duration;

import static org.junit.jupiter.api.Assertions.assertEquals;

public class TestWait {

    private static AndroidDriver driver;

    @BeforeAll
    public static void setUpClass() throws MalformedURLException {
        // 初始化capability
        DesiredCapabilities caps = new DesiredCapabilities();
        // 设置 app 安装的平台(Android、iOS)
        caps.setCapability(MobileCapabilityType.PLATFORM_NAME, "Android");
        // 设置 appium 驱动
        caps.setCapability(MobileCapabilityType.AUTOMATION_NAME, "UiAutomator2");
        // 设置设备名称
        caps.setCapability(MobileCapabilityType.DEVICE_NAME, "emulator-5554");
        // 设置 app 的包名
        caps.setCapability("appPackage", "io.appium.android.apis");
        // 设置 app 的启动页
        caps.setCapability("appActivity", ".ApiDemos");

        // 设置启动url
        URL remoteUrl = new URL("http://127.0.0.1:4723");
        // 初始化driver
        driver = new AndroidDriver(remoteUrl, caps);

    }

    @Test
    public void testWait() throws InterruptedException {
        // 找到 OS 元素
        WebElement el1 = driver.findElement(AppiumBy.accessibilityId("OS"));
        // 点击 OS 元素
        el1.click();
        // 等待 3 秒
        Thread.sleep(3000);
        // 显示等待 Morse Code 元素可点击
        WebDriverWait wait = new WebDriverWait(driver, Duration.ofSeconds(10));
        WebElement el2 = (WebElement) wait.until(
            ExpectedConditions.elementToBeClickable(AppiumBy.accessibilityId("Morse Code"))
        );
        // 找到 Morse Code 元素
        el2.click();
        // 等待 3 秒
        Thread.sleep(3000);
        // 找到输入框元素
        WebElement el3 = driver.findElement(AppiumBy.id("io.appium.android.apis:id/text"));
        // 在输入框中输入内容
        el3.sendKeys("ceshiren.com");
        // 等待 2 秒
        Thread.sleep(2000);
        // 点击返回按钮
        driver.navigate().back();
        // 等待 2 秒
        Thread.sleep(2000);
        // 点击返回按钮
        driver.navigate().back();
        // 断言:判断首页中第一个元素的文本内容是 Access'ibility
        WebElement result = driver.findElement(
            AppiumBy.xpath("//*[@resource-id='android:id/text1'][1]")
        );
        System.out.println(result.getText());
        assertEquals("Access'ibility", result.getText());
    }
}

总结

  • 强制等待
  • 隐式等待
  • 显式等待