Skip to content

自动化关键数据记录

自动化关键数据记录

简介

在 Web 自动化测试中,关键性数据是指在测试执行期间用于判断测试过程是否成功或失败的重要信息。这些数据通常是在测试过程中获取,例如日志信息,截图以及页面源码等等。

使用场景

这些关键性数据帮助自动化测试脚本发现问题之后,用来辅助分析错误出现的原因。帮助更快速的定位和解决问题。

操作步骤

行为日志

Python:

  • 使用 python 自带的 logging 模块
import logging
import os
from logging.handlers import RotatingFileHandler

# 绑定绑定句柄到logger对象
logger = logging.getLogger(__name__)
# 获取当前工具文件所在的路径
root_path = os.path.dirname(os.path.abspath(__file__))
# 拼接当前要输出日志的路径
log_dir_path = os.sep.join([root_path, '..', f'/logs'])
if not os.path.isdir(log_dir_path):
    os.mkdir(log_dir_path)
# 创建日志记录器,指明日志保存路径,每个日志的大小,保存日志的上限
file_log_handler = RotatingFileHandler(
    os.sep.join([log_dir_path, 'log.txt']), 
    maxBytes=1024 * 1024, 
    backupCount=10, 
    encoding="utf-8"
)
# 设置日志的格式
date_string = '%Y-%m-%d %H:%M:%S'
formatter = logging.Formatter(
    '[%(asctime)s] [%(levelname)s] [%(filename)s]/[line: %(lineno)d]/[%(funcName)s] %(message)s ', 
    date_string
)
# 日志输出到控制台的句柄
stream_handler = logging.StreamHandler()
# 将日志记录器指定日志的格式
file_log_handler.setFormatter(formatter)
stream_handler.setFormatter(formatter)
# 为全局的日志工具对象添加日志记录器
# 绑定绑定句柄到logger对象
logger.addHandler(stream_handler)
logger.addHandler(file_log_handler)
# 设置日志输出级别
logger.setLevel(level=logging.INFO)
  • 使用 pytest.ini 配置日志开关与格式
  • 参考链接
[pytest]
;日志开关 true false
log_cli = true
;日志级别
log_cli_level = info
;打印详细日志,相当于命令行加 -vs
addopts = --capture=no
;日志格式
log_cli_format = %(asctime)s [%(levelname)s] %(message)s\
         (%(filename)s:%(lineno)s)
;日志时间格式
log_cli_date_format = %Y-%m-%d %H:%M:%S
;日志文件位置
log_file = ./log/test.log
;日志文件等级
log_file_level = info
;日志文件格式
log_file_format = %(asctime)s [%(levelname)s] %(message)s \
        (%(filename)s:%(lineno)s)
;日志文件日期格式
log_file_date_format = %Y-%m-%d %H:%M:%S

注意:Windows 系统需要把中文注释去掉。

Java:

Java 中对于简单的日志记录需求可以使用java.util.logging标准库,提供了基本的日志功能, 如果需要更复杂的功能,可以考虑使用第三方日志框架,如 log4j 等。

import java.io.IOException;
import java.nio.file.Files;
import java.nio.file.Paths;
import java.util.logging.FileHandler;
import java.util.logging.ConsoleHandler;
import java.util.logging.Level;
import java.util.logging.Logger;
import java.util.logging.SimpleFormatter;

public class LoggerExample {

    private static final Logger logger = Logger.getLogger(LoggerExample.class.getName());

    public static void setupLogger() {
        try {
            // 获取当前工具文件所在的路径
            String rootPath = Paths.get("").toAbsolutePath().toString();
            // 拼接当前要输出日志的路径
            String logDirPath = rootPath + "/logs";
            Files.createDirectories(Paths.get(logDirPath));

            // 创建文件日志记录器
            FileHandler fileHandler = new FileHandler(logDirPath + "/log.txt", 1024 * 1024, 10, true);
            fileHandler.setFormatter(new SimpleFormatter());

            // 创建控制台日志记录器
            ConsoleHandler consoleHandler = new ConsoleHandler();
            consoleHandler.setFormatter(new SimpleFormatter());

            // 绑定句柄到logger对象
            logger.addHandler(fileHandler);
            logger.addHandler(consoleHandler);

            // 设置日志级别
            logger.setLevel(Level.INFO);

        } catch (IOException e) {
            logger.log(Level.SEVERE, "Failed to setup logger", e);
        }
    }

    public static void main(String[] args) {
        setupLogger();
        logger.info("This is an info message.");
        logger.warning("This is a warning message.");
        logger.severe("This is a severe message.");
    }
}

截图

Python Java 描述
get_screenshot_as_file(filename) getScreenshotAs(OutputType.FILE) 保存图片为.png 格式,filename 图片路径
save_screenshot(filename) getScreenshotAs(OutputType.FILE) 保存图片为.png 格式,filename 图片路径
get_screenshot_as_png() getScreenshotAs(OutputType.BYTES) 保存图片为二进制格式
get_screenshot_as_base64() getScreenshotAs(OutputType.BASE64) 将图片保存为 base64 格式。通常用在 html 里添加截图

获取页面源码

  • 通过获取页面源码,分析页面的 dom 结构
  • Python: driver.page_source
  • Java: driver.getPageSource();

完整示例代码

以下为记录关键数据的完整示例:

Python:

import logging

import pytest
from appium import webdriver
from appium.options.android import UiAutomator2Options
from appium.options.common import AppiumOptions
from appium.webdriver.common.appiumby import AppiumBy


class TestXueqiu:

    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": "com.xueqiu.android",
            # 设置被测 app 启动页面的 Activity
            "appium:appActivity": ".view.WelcomeActivityAlias",
            # 不清空缓存信息,保存登录信息
            "appium:noReset": True,
            # 强制app重启,整个测试运行之前重启 app
            "appium:forceAppLaunch": True,
            # 跳过安装,权限设置等操作
            "appium:skipDeviceInitialization": True
        }

        # 初始化 driver
        self.driver = webdriver.Remote(
            "http://127.0.0.1:4723",
            # 也可以使用 AppiumOptions()
            options=AppiumOptions().load_capabilities(caps)
        )
        self.driver.implicitly_wait(15)

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

    def test_search(self):
        # 1. 判断搜索框的是否可用,并查看搜索框 name 属性值,并获取搜索框坐标,以及它的宽高
        search_key = "alibaba"
        searchbox_ele = self.driver.find_element(
            AppiumBy.ID, "com.xueqiu.android:id/home_search"
        )
        # 先判断一下搜索框是否可用
        if searchbox_ele.is_enabled():
            # 1. 查看搜索框属性
            searchbox_text = searchbox_ele.text
            logging.info(f"首页搜索框的 text:{searchbox_text}")
            searchbox_location = searchbox_ele.location
            logging.info(f"首页搜索框的 location坐标为:{searchbox_location}")
            searchbox_size = searchbox_ele.size
            logging.info(f"首页搜索框的 size 宽高:{searchbox_size}")
            # 2. 点击搜索框
            searchbox_ele.click()
            # 3. 向搜索框输入:alibaba
            self.driver.find_element(
                AppiumBy.ID,
                "com.xueqiu.android:id/search_input_text"
            ).send_keys(search_key)
            # 4. 判断【阿里巴巴】是否可见
            alibaba_element = self.driver.find_element(
                AppiumBy.XPATH,
                "//*[@text='阿里巴巴']"
            )
            result = alibaba_element.is_displayed()
            logging.info(result)
            # 如果可见,打印“搜索成功”
            if result == True:
                logging.info("搜索成功")
                # 截图
                self.driver.save_screenshot("./image/success_search_result.png")
                # 获取页面源码
                logging.info(self.driver.page_source)
            # 如果不可见,打印“搜索失败
            else:
                logging.info("搜索失败")
                # 截图
                self.driver.save_screenshot("./image/fail_search_result.png")
                # 获取页面源码
                logging.info(self.driver.page_source)
            assert result == True
        else:
            logging.info("搜索框不可用")
            assert False

Java 示例:

package org.example;

import io.appium.java_client.AppiumBy;
import io.appium.java_client.android.AndroidDriver;
import io.appium.java_client.remote.MobileCapabilityType;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.junit.jupiter.api.AfterAll;
import org.junit.jupiter.api.Assertions;
import org.junit.jupiter.api.BeforeAll;
import org.junit.jupiter.api.Test;
import org.openqa.selenium.WebElement;
import org.openqa.selenium.remote.DesiredCapabilities;


import java.io.File;
import java.io.IOException;
import java.net.MalformedURLException;
import java.net.URL;
import java.time.Duration;


public class TestXueqiu {

    public static AndroidDriver driver;
    private static final Logger logger = LoggerFactory.getLogger(TestXueqiu.class);

    @BeforeAll
    public static void setUp() 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", "com.xueqiu.android");
        // 设置 app 的启动页
        caps.setCapability("appActivity", ".view.WelcomeActivityAlias");
        // 设置 app 不清空缓存
        caps.setCapability("appium:noReset", true);
        // 设置 app 不重启
        caps.setCapability("appium:shouldTerminateApp", true);
        // 跳过安装,权限设置等操作
        caps.setCapability("appiumLskipDeviceInitialization", true);
        URL remoteUrl = new URL("http://127.0.0.1:4723");

        driver = new AndroidDriver(remoteUrl, caps);
        driver.manage().timeouts().implicitlyWait(Duration.ofSeconds(15));
    }

    @AfterAll
    public static void tearDown() {
        if (driver != null) {
            driver.quit();
        }
    }

    @Test
    public void testSearch() {
        String searchKey = "alibaba";
        WebElement searchBoxEle = driver.findElement(AppiumBy.id("com.xueqiu.android:id/home_search"));

        // 判断搜索框是否可用
        if (searchBoxEle.isEnabled()) {
            // 查看搜索框属性
            String searchBoxText = searchBoxEle.getText();
            logger.info("首页搜索框的 text: " + searchBoxText);
            org.openqa.selenium.Point searchboxLocation = searchBoxEle.getLocation();
            logger.info("首页搜索框的 location坐标为: " + searchboxLocation);
            org.openqa.selenium.Dimension searchboxSize = searchBoxEle.getSize();
            logger.info("首页搜索框的 size 宽高: " + searchboxSize);

            // 点击搜索框
            searchBoxEle.click();

            // 向搜索框输入
            driver.findElement(AppiumBy.id("com.xueqiu.android:id/search_input_text")).sendKeys(searchKey);

            // 判断【阿里巴巴】是否可见
            WebElement alibabaElement = driver.findElement(AppiumBy.xpath("//*[@text='阿里巴巴']"));
            boolean result = alibabaElement.isDisplayed();
            logger.info(String.valueOf(result));

            // 如果可见,打印“搜索成功”
            if (result) {
                logger.info("搜索成功");
                // 截图
                File screenshot = driver.getScreenshotAs(org.openqa.selenium.OutputType.FILE);
                // 检查并创建目录
                File imageDir = new File("./image");
                if (!imageDir.exists()) {
                    imageDir.mkdirs();
                }
                File destination = new File("./image/success_search_result.png");
                try {
                    org.openqa.selenium.io.FileHandler.copy(screenshot, destination);
                } catch (IOException e) {
                    e.printStackTrace();
                }
                // 获取页面源码
                logger.info(driver.getPageSource());
            } else {
                logger.info("搜索失败");
                // 截图
                File screenshot = driver.getScreenshotAs(org.openqa.selenium.OutputType.FILE);
                File imageDir = new File("./image");
                if (!imageDir.exists()) {
                    imageDir.mkdirs();
                }
                File destination = new File("./image/fail_search_result.png");
                try {
                    org.openqa.selenium.io.FileHandler.copy(screenshot, destination);
                } catch (IOException e) {
                    e.printStackTrace();
                }
                // 获取页面源码
                logger.info(driver.getPageSource());
            }
            Assertions.assertTrue(result);
        } else {
            logger.info("搜索框不可用");
            Assertions.fail("搜索框不可用");
        }
    }
}

注意:截图的目录需要提前创建好。

总结

  • 行为日志
  • 截图
  • 获取页面源码