Selenium 实战项目:电子商务网站自动化测试

Selenium 实战项目 | 菜鸟,建议你先看完前面这篇再来看下面这篇。

下面我将带你完成一个完整的 Selenium 实战项目,模拟用户在一个电子商务网站上的完整购物流程。这个项目将综合运用我们之前学到的所有知识。

项目概述

我们将创建一个自动化测试脚本,模拟用户在电子商务网站上完成以下操作:

  1. 访问网站首页

  2. 用户登录

  3. 浏览商品分类

  4. 搜索商品

  5. 将商品添加到购物车

  6. 查看购物车

  7. 进入结算流程

  8. 填写配送信息

  9. 选择支付方式

  10. 完成订单

项目结构

复制代码
ecommerce-automation/
├── pages/                 # 页面对象类
│   ├── __init__.py
│   ├── base_page.py       # 基础页面类
│   ├── home_page.py       # 首页
│   ├── login_page.py      # 登录页
│   ├── product_page.py    # 商品页
│   ├── cart_page.py       # 购物车页
│   └── checkout_page.py   # 结算页
├── tests/                 # 测试用例
│   ├── __init__.py
│   └── test_shopping_flow.py  # 主要测试流程
├── utils/                 # 工具类
│   ├── __init__.py
│   ├── config.py          # 配置文件
│   └── helpers.py         # 辅助函数
├── reports/               # 测试报告
├── screenshots/           # 测试截图
├── requirements.txt       # 项目依赖
└── run_tests.py          # 测试运行入口

环境准备

首先创建 requirements.txt 文件:

复制代码
selenium==4.15.0
pytest==7.4.3
pytest-html==4.1.1
pytest-xdist==3.5.0
allure-pytest==2.13.2
webdriver-manager==4.0.1

安装依赖:

bash 复制代码
pip install -r requirements.txt

配置文件

创建 utils/config.py:

python 复制代码
import os
from datetime import datetime

class Config:
    # 浏览器配置
    BROWSER = "chrome"  # chrome, firefox, edge
    HEADLESS = True
    WINDOW_SIZE = "1920,1080"
    IMPLICIT_WAIT = 10
    
    # 测试网站URL
    BASE_URL = "https://www.saucedemo.com/"
    
    # 测试用户凭证
    STANDARD_USER = "standard_user"
    LOCKED_USER = "locked_out_user"
    PROBLEM_USER = "problem_user"
    PERFORMANCE_USER = "performance_glitch_user"
    PASSWORD = "secret_sauce"
    
    # 路径配置
    PROJECT_ROOT = os.path.dirname(os.path.dirname(os.path.abspath(__file__)))
    SCREENSHOT_DIR = os.path.join(PROJECT_ROOT, "screenshots")
    REPORT_DIR = os.path.join(PROJECT_ROOT, "reports")
    
    # 测试数据
    TEST_PRODUCT = "Sauce Labs Backpack"
    TEST_FIRST_NAME = "Test"
    TEST_LAST_NAME = "User"
    TEST_ZIP_CODE = "12345"
    
    @staticmethod
    def get_timestamp():
        return datetime.now().strftime("%Y%m%d_%H%M%S")
    
    @staticmethod
    def setup_directories():
        os.makedirs(Config.SCREENSHOT_DIR, exist_ok=True)
        os.makedirs(Config.REPORT_DIR, exist_ok=True)

# 初始化目录
Config.setup_directories()

基础页面类

创建 pages/base_page.py:

python 复制代码
from selenium.webdriver.support.ui import WebDriverWait
from selenium.webdriver.support import expected_conditions as EC
from selenium.common.exceptions import TimeoutException, NoSuchElementException
from utils.config import Config
import logging
import allure

logger = logging.getLogger(__name__)

class BasePage:
    def __init__(self, driver):
        self.driver = driver
        self.wait = WebDriverWait(driver, Config.IMPLICIT_WAIT)
    
    def find_element(self, locator):
        """查找元素,带有显式等待"""
        try:
            return self.wait.until(EC.visibility_of_element_located(locator))
        except TimeoutException:
            logger.error(f"元素未找到: {locator}")
            raise
    
    def find_elements(self, locator):
        """查找多个元素"""
        try:
            return self.wait.until(EC.visibility_of_all_elements_located(locator))
        except TimeoutException:
            logger.error(f"元素未找到: {locator}")
            return []
    
    def click(self, locator):
        """点击元素"""
        element = self.find_element(locator)
        try:
            element.click()
            logger.info(f"点击元素: {locator}")
        except Exception as e:
            logger.error(f"点击元素失败: {locator}, 错误: {e}")
            raise
    
    def input_text(self, locator, text):
        """输入文本"""
        element = self.find_element(locator)
        try:
            element.clear()
            element.send_keys(text)
            logger.info(f"在元素 {locator} 中输入文本: {text}")
        except Exception as e:
            logger.error(f"输入文本失败: {locator}, 错误: {e}")
            raise
    
    def get_text(self, locator):
        """获取元素文本"""
        element = self.find_element(locator)
        return element.text
    
    def is_element_present(self, locator):
        """检查元素是否存在"""
        try:
            self.find_element(locator)
            return True
        except (TimeoutException, NoSuchElementException):
            return False
    
    def take_screenshot(self, name):
        """截图并附加到Allure报告"""
        screenshot_path = f"{Config.SCREENSHOT_DIR}/{name}_{Config.get_timestamp()}.png"
        self.driver.save_screenshot(screenshot_path)
        allure.attach(
            self.driver.get_screenshot_as_png(),
            name=name,
            attachment_type=allure.attachment_type.PNG
        )
        return screenshot_path
    
    def wait_for_page_load(self):
        """等待页面加载完成"""
        self.wait.until(
            lambda driver: driver.execute_script("return document.readyState") == "complete"
        )

页面对象类

创建 pages/home_page.py:

python 复制代码
from selenium.webdriver.common.by import By
from .base_page import BasePage

class HomePage(BasePage):
    # 定位器
    LOGO = (By.CLASS_NAME, "app_logo")
    BURGER_MENU = (By.ID, "react-burger-menu-btn")
    SHOPPING_CART = (By.CLASS_NAME, "shopping_cart_link")
    PRODUCTS_TITLE = (By.CLASS_NAME, "title")
    PRODUCT_ITEMS = (By.CLASS_NAME, "inventory_item")
    PRODUCT_NAMES = (By.CLASS_NAME, "inventory_item_name")
    ADD_TO_CART_BUTTONS = (By.XPATH, "//button[contains(text(), 'Add to cart')]")
    REMOVE_BUTTONS = (By.XPATH, "//button[contains(text(), 'Remove')]")
    SORT_DROPDOWN = (By.CLASS_NAME, "product_sort_container")
    
    def __init__(self, driver):
        super().__init__(driver)
    
    def is_home_page_loaded(self):
        """检查首页是否加载完成"""
        return self.is_element_present(self.PRODUCTS_TITLE)
    
    def get_product_count(self):
        """获取商品数量"""
        return len(self.find_elements(self.PRODUCT_ITEMS))
    
    def get_product_names(self):
        """获取所有商品名称"""
        return [product.text for product in self.find_elements(self.PRODUCT_NAMES)]
    
    def add_product_to_cart(self, product_name):
        """添加指定商品到购物车"""
        products = self.find_elements(self.PRODUCT_NAMES)
        add_buttons = self.find_elements(self.ADD_TO_CART_BUTTONS)
        
        for i, product in enumerate(products):
            if product.text == product_name:
                add_buttons[i].click()
                return True
        return False
    
    def go_to_cart(self):
        """前往购物车"""
        self.click(self.SHOPPING_CART)
        from .cart_page import CartPage
        return CartPage(self.driver)
    
    def sort_products(self, sort_by):
        """排序商品"""
        from selenium.webdriver.support.ui import Select
        dropdown = Select(self.find_element(self.SORT_DROPDOWN))
        dropdown.select_by_visible_text(sort_by)

创建 pages/login_page.py:

python 复制代码
from selenium.webdriver.common.by import By
from .base_page import BasePage
from utils.config import Config

class LoginPage(BasePage):
    # 定位器
    USERNAME_FIELD = (By.ID, "user-name")
    PASSWORD_FIELD = (By.ID, "password")
    LOGIN_BUTTON = (By.ID, "login-button")
    ERROR_MESSAGE = (By.CSS_SELECTOR, "[data-test='error']")
    
    def __init__(self, driver):
        super().__init__(driver)
        self.driver.get(Config.BASE_URL)
    
    def login(self, username, password):
        """登录操作"""
        self.input_text(self.USERNAME_FIELD, username)
        self.input_text(self.PASSWORD_FIELD, password)
        self.click(self.LOGIN_BUTTON)
        
        # 返回首页对象
        from .home_page import HomePage
        return HomePage(self.driver)
    
    def get_error_message(self):
        """获取错误消息"""
        if self.is_element_present(self.ERROR_MESSAGE):
            return self.get_text(self.ERROR_MESSAGE)
        return None
    
    def is_login_page_loaded(self):
        """检查登录页是否加载完成"""
        return self.is_element_present(self.LOGIN_BUTTON)

创建 pages/product_page.py:

python 复制代码
from selenium.webdriver.common.by import By
from .base_page import BasePage

class ProductPage(BasePage):
    # 定位器
    PRODUCT_NAME = (By.CLASS_NAME, "inventory_details_name")
    PRODUCT_DESCRIPTION = (By.CLASS_NAME, "inventory_details_desc")
    PRODUCT_PRICE = (By.CLASS_NAME, "inventory_details_price")
    ADD_TO_CART_BUTTON = (By.XPATH, "//button[contains(text(), 'Add to cart')]")
    REMOVE_BUTTON = (By.XPATH, "//button[contains(text(), 'Remove')]")
    BACK_BUTTON = (By.ID, "back-to-products")
    
    def __init__(self, driver):
        super().__init__(driver)
    
    def get_product_name(self):
        """获取商品名称"""
        return self.get_text(self.PRODUCT_NAME)
    
    def get_product_price(self):
        """获取商品价格"""
        return self.get_text(self.PRODUCT_PRICE)
    
    def add_to_cart(self):
        """添加到购物车"""
        self.click(self.ADD_TO_CART_BUTTON)
    
    def remove_from_cart(self):
        """从购物车移除"""
        self.click(self.REMOVE_BUTTON)
    
    def back_to_products(self):
        """返回商品列表"""
        self.click(self.BACK_BUTTON)
        from .home_page import HomePage
        return HomePage(self.driver)

创建 pages/cart_page.py:

python 复制代码
from selenium.webdriver.common.by import By
from .base_page import BasePage

class CartPage(BasePage):
    # 定位器
    CART_ITEMS = (By.CLASS_NAME, "cart_item")
    ITEM_NAMES = (By.CLASS_NAME, "inventory_item_name")
    ITEM_PRICES = (By.CLASS_NAME, "inventory_item_price")
    REMOVE_BUTTONS = (By.XPATH, "//button[contains(text(), 'Remove')]")
    CONTINUE_SHOPPING_BUTTON = (By.ID, "continue-shopping")
    CHECKOUT_BUTTON = (By.ID, "checkout")
    
    def __init__(self, driver):
        super().__init__(driver)
    
    def get_cart_items_count(self):
        """获取购物车中商品数量"""
        return len(self.find_elements(self.CART_ITEMS))
    
    def get_item_names(self):
        """获取购物车中所有商品名称"""
        return [item.text for item in self.find_elements(self.ITEM_NAMES)]
    
    def get_item_prices(self):
        """获取购物车中所有商品价格"""
        return [price.text for price in self.find_elements(self.ITEM_PRICES)]
    
    def remove_item(self, item_name):
        """移除指定商品"""
        items = self.find_elements(self.ITEM_NAMES)
        remove_buttons = self.find_elements(self.REMOVE_BUTTONS)
        
        for i, item in enumerate(items):
            if item.text == item_name:
                remove_buttons[i].click()
                return True
        return False
    
    def continue_shopping(self):
        """继续购物"""
        self.click(self.CONTINUE_SHOPPING_BUTTON)
        from .home_page import HomePage
        return HomePage(self.driver)
    
    def checkout(self):
        """结算"""
        self.click(self.CHECKOUT_BUTTON)
        from .checkout_page import CheckoutPage
        return CheckoutPage(self.driver)

创建 pages/checkout_page.py:

python 复制代码
from selenium.webdriver.common.by import By
from .base_page import BasePage
from utils.config import Config

class CheckoutPage(BasePage):
    # 定位器
    FIRST_NAME_FIELD = (By.ID, "first-name")
    LAST_NAME_FIELD = (By.ID, "last-name")
    ZIP_CODE_FIELD = (By.ID, "postal-code")
    CONTINUE_BUTTON = (By.ID, "continue")
    CANCEL_BUTTON = (By.ID, "cancel")
    ERROR_MESSAGE = (By.CSS_SELECTOR, "[data-test='error']")
    
    # 第二步定位器
    ITEM_TOTAL = (By.CLASS_NAME, "summary_subtotal_label")
    TAX = (By.CLASS_NAME, "summary_tax_label")
    TOTAL = (By.CLASS_NAME, "summary_total_label")
    FINISH_BUTTON = (By.ID, "finish")
    
    # 完成页定位器
    COMPLETE_HEADER = (By.CLASS_NAME, "complete-header")
    COMPLETE_TEXT = (By.CLASS_NAME, "complete-text")
    BACK_HOME_BUTTON = (By.ID, "back-to-products")
    
    def __init__(self, driver):
        super().__init__(driver)
    
    def fill_shipping_info(self, first_name=None, last_name=None, zip_code=None):
        """填写配送信息"""
        first_name = first_name or Config.TEST_FIRST_NAME
        last_name = last_name or Config.TEST_LAST_NAME
        zip_code = zip_code or Config.TEST_ZIP_CODE
        
        self.input_text(self.FIRST_NAME_FIELD, first_name)
        self.input_text(self.LAST_NAME_FIELD, last_name)
        self.input_text(self.ZIP_CODE_FIELD, zip_code)
    
    def continue_to_overview(self):
        """继续到订单概览"""
        self.click(self.CONTINUE_BUTTON)
    
    def cancel_checkout(self):
        """取消结算"""
        self.click(self.CANCEL_BUTTON)
        from .cart_page import CartPage
        return CartPage(self.driver)
    
    def get_error_message(self):
        """获取错误消息"""
        if self.is_element_present(self.ERROR_MESSAGE):
            return self.get_text(self.ERROR_MESSAGE)
        return None
    
    def get_item_total(self):
        """获取商品总额"""
        return self.get_text(self.ITEM_TOTAL)
    
    def get_tax(self):
        """获取税费"""
        return self.get_text(self.TAX)
    
    def get_total(self):
        """获取总计"""
        return self.get_text(self.TOTAL)
    
    def finish_order(self):
        """完成订单"""
        self.click(self.FINISH_BUTTON)
    
    def is_order_complete(self):
        """检查订单是否完成"""
        return self.is_element_present(self.COMPLETE_HEADER)
    
    def get_complete_message(self):
        """获取完成消息"""
        return self.get_text(self.COMPLETE_HEADER)
    
    def back_to_home(self):
        """返回首页"""
        self.click(self.BACK_HOME_BUTTON)
        from .home_page import HomePage
        return HomePage(self.driver)

辅助工具类

创建 utils/helpers.py:

python 复制代码
from selenium import webdriver
from selenium.webdriver.chrome.options import Options
from selenium.webdriver.firefox.options import Options as FirefoxOptions
from selenium.webdriver.edge.options import Options as EdgeOptions
from webdriver_manager.chrome import ChromeDriverManager
from webdriver_manager.firefox import GeckoDriverManager
from webdriver_manager.microsoft import EdgeChromiumDriverManager
from utils.config import Config
import logging

logger = logging.getLogger(__name__)

def create_driver(browser_name=None, headless=None):
    """创建WebDriver实例"""
    browser_name = browser_name or Config.BROWSER
    headless = headless if headless is not None else Config.HEADLESS
    
    if browser_name.lower() == "chrome":
        options = Options()
        if headless:
            options.add_argument("--headless=new")
        options.add_argument("--no-sandbox")
        options.add_argument("--disable-dev-shm-usage")
        options.add_argument(f"--window-size={Config.WINDOW_SIZE}")
        
        driver = webdriver.Chrome(
            service=webdriver.chrome.service.Service(ChromeDriverManager().install()),
            options=options
        )
    
    elif browser_name.lower() == "firefox":
        options = FirefoxOptions()
        if headless:
            options.add_argument("-headless")
        options.add_argument(f"--width={Config.WINDOW_SIZE.split(',')[0]}")
        options.add_argument(f"--height={Config.WINDOW_SIZE.split(',')[1]}")
        
        driver = webdriver.Firefox(
            service=webdriver.firefox.service.Service(GeckoDriverManager().install()),
            options=options
        )
    
    elif browser_name.lower() == "edge":
        options = EdgeOptions()
        if headless:
            options.add_argument("--headless")
        options.add_argument(f"--window-size={Config.WINDOW_SIZE}")
        
        driver = webdriver.Edge(
            service=webdriver.edge.service.Service(EdgeChromiumDriverManager().install()),
            options=options
        )
    
    else:
        raise ValueError(f"不支持的浏览器: {browser_name}")
    
    driver.implicitly_wait(Config.IMPLICIT_WAIT)
    logger.info(f"创建 {browser_name} 浏览器实例,无头模式: {headless}")
    return driver

def setup_logging():
    """配置日志"""
    logging.basicConfig(
        level=logging.INFO,
        format='%(asctime)s - %(name)s - %(levelname)s - %(message)s',
        handlers=[
            logging.FileHandler(f"{Config.PROJECT_ROOT}/automation.log"),
            logging.StreamHandler()
        ]
    )

测试用例

创建 tests/test_shopping_flow.py:

python 复制代码
import pytest
import allure
from utils.helpers import create_driver
from utils.config import Config
from pages.login_page import LoginPage

@allure.feature("电子商务购物流程")
@allure.story("完整购物流程测试")
class TestShoppingFlow:
    @pytest.fixture(autouse=True)
    def setup(self):
        """测试 setup"""
        self.driver = create_driver()
        yield
        self.driver.quit()
    
    @allure.title("测试完整购物流程")
    @allure.severity(allure.severity_level.CRITICAL)
    def test_complete_shopping_flow(self):
        """测试完整购物流程"""
        with allure.step("1. 登录网站"):
            login_page = LoginPage(self.driver)
            assert login_page.is_login_page_loaded(), "登录页面未正确加载"
            
            home_page = login_page.login(Config.STANDARD_USER, Config.PASSWORD)
            assert home_page.is_home_page_loaded(), "首页未正确加载"
            home_page.take_screenshot("登录成功")
        
        with allure.step("2. 浏览商品"):
            product_count = home_page.get_product_count()
            assert product_count > 0, "商品列表为空"
            
            product_names = home_page.get_product_names()
            allure.attach(str(product_names), "商品列表", allure.attachment_type.TEXT)
        
        with allure.step("3. 添加商品到购物车"):
            result = home_page.add_product_to_cart(Config.TEST_PRODUCT)
            assert result, f"未找到商品: {Config.TEST_PRODUCT}"
            home_page.take_screenshot("添加商品到购物车")
        
        with allure.step("4. 查看购物车"):
            cart_page = home_page.go_to_cart()
            assert cart_page.get_cart_items_count() == 1, "购物车商品数量不正确"
            
            cart_items = cart_page.get_item_names()
            assert Config.TEST_PRODUCT in cart_items, "添加的商品不在购物车中"
            cart_page.take_screenshot("购物车页面")
        
        with allure.step("5. 进入结算流程"):
            checkout_page = cart_page.checkout()
            checkout_page.take_screenshot("结算页面第一步")
        
        with allure.step("6. 填写配送信息"):
            checkout_page.fill_shipping_info()
            checkout_page.continue_to_overview()
            checkout_page.take_screenshot("订单概览")
        
        with allure.step("7. 验证订单信息"):
            item_total = checkout_page.get_item_total()
            tax = checkout_page.get_tax()
            total = checkout_page.get_total()
            
            allure.attach(f"商品总额: {item_total}\n税费: {tax}\n总计: {total}", 
                         "订单金额信息", allure.attachment_type.TEXT)
        
        with allure.step("8. 完成订单"):
            checkout_page.finish_order()
            assert checkout_page.is_order_complete(), "订单未成功完成"
            
            complete_message = checkout_page.get_complete_message()
            assert "thank you for your order" in complete_message.lower(), "订单完成消息不正确"
            checkout_page.take_screenshot("订单完成")
    
    @allure.title("测试无效登录")
    @allure.severity(allure.severity_level.NORMAL)
    def test_invalid_login(self):
        """测试无效登录"""
        with allure.step("使用错误凭证登录"):
            login_page = LoginPage(self.driver)
            login_page.login("invalid_user", "wrong_password")
            
            error_message = login_page.get_error_message()
            assert error_message is not None, "未显示错误消息"
            assert "username and password do not match" in error_message.lower()
            
            login_page.take_screenshot("登录错误")
    
    @allure.title("测试购物车操作")
    @allure.severity(allure.severity_level.NORMAL)
    def test_cart_operations(self):
        """测试购物车操作"""
        with allure.step("登录并添加多个商品"):
            login_page = LoginPage(self.driver)
            home_page = login_page.login(Config.STANDARD_USER, Config.PASSWORD)
            
            # 添加两个商品
            home_page.add_product_to_cart(Config.TEST_PRODUCT)
            home_page.add_product_to_cart("Sauce Labs Bike Light")
            
            cart_page = home_page.go_to_cart()
            assert cart_page.get_cart_items_count() == 2, "购物车商品数量不正确"
            cart_page.take_screenshot("购物车中有两个商品")
        
        with allure.step("从购物车移除一个商品"):
            cart_page.remove_item(Config.TEST_PRODUCT)
            assert cart_page.get_cart_items_count() == 1, "商品移除失败"
            cart_page.take_screenshot("移除一个商品后")
        
        with allure.step("继续购物并添加另一个商品"):
            home_page = cart_page.continue_shopping()
            home_page.add_product_to_cart("Sauce Labs Bolt T-Shirt")
            
            cart_page = home_page.go_to_cart()
            assert cart_page.get_cart_items_count() == 2, "继续购物后商品数量不正确"
            cart_page.take_screenshot("继续购物后")

测试运行入口

创建 run_tests.py:

python 复制代码
import pytest
import os
import shutil
from utils.helpers import setup_logging
from utils.config import Config

def run_tests():
    """运行测试"""
    # 设置日志
    setup_logging()
    
    # 清理之前的报告和截图
    if os.path.exists(Config.REPORT_DIR):
        shutil.rmtree(Config.REPORT_DIR)
    if os.path.exists(Config.SCREENSHOT_DIR):
        shutil.rmtree(Config.SCREENSHOT_DIR)
    
    # 重新创建目录
    Config.setup_directories()
    
    # 运行测试
    pytest_args = [
        "-v",
        "--tb=short",
        f"--html={Config.REPORT_DIR}/report.html",
        "--self-contained-html",
        "--alluredir", f"{Config.REPORT_DIR}/allure-results",
        "-n", "2"  # 并行运行2个测试
    ]
    
    pytest.main(pytest_args)
    
    # 生成Allure报告
    if shutil.which("allure"):
        os.system(f"allure generate {Config.REPORT_DIR}/allure-results -o {Config.REPORT_DIR}/allure-report --clean")
        print(f"Allure报告已生成: {Config.REPORT_DIR}/allure-report/index.html")
    
    print(f"HTML报告已生成: {Config.REPORT_DIR}/report.html")

if __name__ == "__main__":
    run_tests()

运行测试

在项目根目录下运行:

python 复制代码
python run_tests.py

或者直接使用pytest:

python 复制代码
pytest tests/test_shopping_flow.py -v --html=reports/report.html --self-contained-html

项目扩展建议

这个实战项目可以进一步扩展:

  1. 数据驱动测试:使用CSV或JSON文件管理测试数据

  2. API集成:结合API测试验证前后端一致性

  3. 性能测试:添加性能监控和断言

  4. 跨浏览器测试:扩展支持更多浏览器

  5. 移动端测试:添加移动端浏览器测试

  6. 可视化测试:集成视觉回归测试

  7. CI/CD集成:配置GitHub Actions或Jenkins流水线

  8. 测试报告优化:集成更丰富的报告系统

这个实战项目涵盖了Selenium自动化测试的核心概念和最佳实践,包括页面对象模式、等待策略、异常处理、报告生成等。你可以根据实际需求进一步扩展和优化这个项目。

相关推荐
Meta Luo1 天前
页面改版后自动化用例大量失败,应该怎么维护?
运维·功能测试·测试工具·自动化
释槐鸟~2 天前
还不知道RIDE工具怎么装吗
测试工具·自动化
有技巧搬砖3 天前
为什么锂电池组的电池老化测试很重要?
测试工具·锂电池·电池老化
蒲公英内测分发3 天前
智能手表卖出去后,包装、说明书和官网里的 App 下载入口怎么统一?
前端·人工智能·测试工具·智能家居·智能硬件·智能手表
蒲公英内测分发3 天前
智能手环出海测试,海外合作方分散在不同国家,Android 测试包怎么持续分发和更新?
android·前端·人工智能·测试工具·智能手表
无语的小歪歪3 天前
ATF 智测 1.0
python·测试工具·集成测试
SHENGWEI胜为3 天前
交换机端口显示正常,终端却获取不到 IP 地址?从 DHCP 流程定位故障
服务器·网络·测试工具·缓存·通信工程
xie881018024 天前
长表格跨页重复表头:排版上的做法
测试工具
老孟说禅4 天前
测试工具示波器的使用
测试工具
步行cgn5 天前
为什么使用接口测试工具?
测试工具