乐闻世界logo
搜索文章和话题

前端面试题手册

什么是 Appium,它有哪些核心特性?

Appium 是一个开源的、跨平台的移动应用自动化测试框架,它遵循 WebDriver 协议,允许测试人员使用标准的 WebDriver API 来自动化移动应用。Appium 的核心优势在于其跨平台特性和对多种编程语言的支持。Appium 的核心特性跨平台支持:支持 iOS、Android 和 Windows 平台使用统一的 API 接口无需为不同平台学习不同的工具多语言支持:Java、Python、JavaScript (Node.js)Ruby、C#、PHP 等测试人员可以使用熟悉的语言编写测试开源免费:完全开源,社区活跃免费使用,无商业限制持续更新和改进WebDriver 协议:遵循 W3C WebDriver 标准与 Selenium 兼容标准化的 API 接口Appium 的架构Appium 采用客户端-服务器架构:Appium Server:接收来自客户端的命令将命令转换为特定平台的操作与移动设备或模拟器通信Appium Client:各种语言的客户端库提供语言特定的 API封装 WebDriver 协议自动化引擎:iOS:使用 XCUITest(iOS 9.3+)或 UIAutomation(iOS 9.2-)Android:使用 UiAutomator2(Android 5.0+)或 UiAutomator(Android 4.2-)Windows:使用 WinAppDriverAppium 的工作原理会话创建:客户端发送创建会话请求服务器根据 desired capabilities 配置启动应用建立与设备的连接命令执行:客户端发送 WebDriver 命令服务器将命令转换为平台特定的操作自动化引擎执行操作并返回结果元素定位:支持多种定位策略ID、XPath、CSS Selector、Accessibility ID 等跨平台统一的定位方式Appium 的优势无需重新编译应用:可以直接测试原生应用无需修改应用代码支持测试商店应用支持混合应用:可以自动化 WebView在原生和 WebView 上下文间切换支持 Cordova、Ionic、React Native 等支持移动 Web:可以自动化移动浏览器支持 Safari、Chrome 等类似于 Selenium 的测试方式丰富的生态系统:大量的插件和工具活跃的社区支持丰富的文档和教程Appium 的应用场景原生应用测试:iOS 和 Android 原生应用功能测试、回归测试兼容性测试混合应用测试:WebView 混合应用跨平台框架应用复杂交互测试移动 Web 测试:移动浏览器应用响应式设计测试跨浏览器测试持续集成:与 CI/CD 工具集成自动化测试流程云测试平台集成Appium 与其他工具的对比Appium vs CalabashAppium:跨平台、WebDriver 标准、多语言支持Calabash:Ruby 为主、Cucumber 集成、学习曲线陡峭Appium vs EspressoAppium:跨平台、无需修改应用、黑盒测试Espresso:Android 专用、白盒测试、性能更好Appium vs XCUITestAppium:跨平台、WebDriver 标准、多语言支持XCUITest:iOS 专用、性能更好、Swift/Objective-CAppium 的版本Appium 1.x:基于 JSON Wire ProtocolAppium 2.0:基于 W3C WebDriver 标准更好的标准化和兼容性改进的性能和稳定性Appium 作为移动应用自动化测试的首选工具,为测试人员提供了强大而灵活的测试能力,通过合理使用 Appium,可以构建高效、稳定的自动化测试体系。
阅读 0·2月21日 16:20

Appium 与 Selenium 有什么区别?

Appium 与 Selenium 是两个不同的自动化测试工具,虽然它们都基于 WebDriver 协议,但在应用场景、架构设计和功能特性上存在显著差异。以下是 Appium 与 Selenium 的详细对比:基本概念SeleniumSelenium 是一个用于 Web 应用程序自动化测试的工具集,主要用于:浏览器自动化测试Web 应用功能测试跨浏览器测试AppiumAppium 是一个用于移动应用程序自动化测试的工具,主要用于:移动应用自动化测试原生应用、混合应用和移动 Web 测试跨平台移动测试主要区别1. 应用场景// Selenium - Web 浏览器测试const { Builder, By, until } = require('selenium-webdriver');const driver = await new Builder() .forBrowser('chrome') .build();await driver.get('https://example.com');const element = await driver.findElement(By.id('submit_button'));await element.click();// Appium - 移动应用测试const { Builder, By, until } = require('selenium-webdriver');const capabilities = { platformName: 'Android', deviceName: 'Pixel 5', app: '/path/to/app.apk'};const driver = await new Builder() .withCapabilities(capabilities) .build();const element = await driver.findElement(By.id('submit_button'));await element.click();区别:Selenium:专注于 Web 浏览器自动化Appium:专注于移动应用自动化2. 支持的平台| 特性 | Selenium | Appium ||------|----------|--------|| Web 浏览器 | ✅ 支持 | ✅ 支持(移动 Web) || Android 原生应用 | ❌ 不支持 | ✅ 支持 || iOS 原生应用 | ❌ 不支持 | ✅ 支持 || Windows 桌面应用 | ❌ 不支持 | ✅ 支持 || 混合应用 | ❌ 不支持 | ✅ 支持 |3. 架构设计Selenium 架构:Test Script → Selenium WebDriver → Browser Driver → BrowserAppium 架构:Test Script → Appium Client → Appium Server → Automation Engine → Mobile Device区别:Selenium:直接与浏览器驱动通信Appium:通过 Appium Server 与设备通信4. 自动化引擎Selenium:使用浏览器内置的自动化引擎每个浏览器有特定的驱动(ChromeDriver, GeckoDriver 等)直接与浏览器 API 交互Appium:使用平台特定的自动化引擎Android:UiAutomator2, EspressoiOS:XCUITestWindows:WinAppDriver5. 元素定位策略Selenium:// Selenium 支持的定位策略By.id('element_id')By.className('element_class')By.tagName('button')By.cssSelector('#submit-button')By.xpath('//button[@id="submit"]')By.name('element_name')By.linkText('Submit')By.partialLinkText('Sub')Appium:// Appium 支持的定位策略(包含 Selenium 的所有策略)By.id('element_id')By.className('element_class')By.xpath('//android.widget.Button[@text="Submit"]')By.accessibilityId('submit_button')By.androidUIAutomator('new UiSelector().text("Submit")')By.iOSNsPredicateString('name == "Submit"')By.iOSClassChain('**/XCUIElementTypeButton[`name == "Submit"`]')区别:Appium 继承了 Selenium 的所有定位策略Appium 增加了移动应用特有的定位策略6. 手势操作Selenium:// Selenium 手势操作有限await element.click();await element.sendKeys('text');await element.clear();Appium:// Appium 支持丰富的手势操作await element.click();await element.sendKeys('text');await element.clear();// 触摸操作await driver.touchActions([ { action: 'press', x: 100, y: 200 }, { action: 'moveTo', x: 100, y: 100 }, { action: 'release' }]);// 多点触控const actions = driver.actions({ async: true });await actions.move({ origin: element1 }).press() .move({ origin: element2 }).press() .pause(100) .move({ origin: element1 }).release() .move({ origin: element2 }).release() .perform();区别:Selenium:手势操作有限Appium:支持丰富的手势和多点触控7. 上下文切换Selenium:// Selenium 不需要上下文切换// 直接操作浏览器元素const element = await driver.findElement(By.id('submit_button'));await element.click();Appium:// Appium 需要处理上下文切换// 获取所有上下文const contexts = await driver.getContexts();console.log('Available contexts:', contexts);// ['NATIVE_APP', 'WEBVIEW_com.example.app']// 切换到 WebViewawait driver.context('WEBVIEW_com.example.app');// 操作 WebView 元素const element = await driver.findElement(By.id('submit_button'));await element.click();// 切换回原生应用await driver.context('NATIVE_APP');区别:Selenium:不需要上下文切换Appium:需要在原生应用和 WebView 之间切换8. 设备能力Selenium:// Selenium 设备能力有限const capabilities = { browserName: 'chrome', platformName: 'Windows', version: 'latest'};Appium:// Appium 支持丰富的设备能力const capabilities = { platformName: 'Android', platformVersion: '11.0', deviceName: 'Pixel 5', udid: 'emulator-5554', app: '/path/to/app.apk', appPackage: 'com.example.app', appActivity: '.MainActivity', autoGrantPermissions: true, noReset: true, fullReset: false, automationName: 'UiAutomator2', language: 'zh-CN', locale: 'zh_CN'};区别:Selenium:设备能力有限Appium:支持丰富的设备配置9. 测试框架集成Selenium:// Selenium 与测试框架集成const { describe, it, before, after } = require('mocha');const { Builder, By, until } = require('selenium-webdriver');describe('Web Application Test', () => { let driver; before(async () => { driver = await new Builder().forBrowser('chrome').build(); }); it('should submit form', async () => { await driver.get('https://example.com'); const element = await driver.findElement(By.id('submit_button')); await element.click(); }); after(async () => { await driver.quit(); });});Appium:// Appium 与测试框架集成const { describe, it, before, after } = require('mocha');const { Builder, By, until } = require('selenium-webdriver');describe('Mobile Application Test', () => { let driver; before(async () => { const capabilities = { platformName: 'Android', deviceName: 'Pixel 5', app: '/path/to/app.apk' }; driver = await new Builder().withCapabilities(capabilities).build(); }); it('should submit form', async () => { const element = await driver.findElement(By.id('submit_button')); await element.click(); }); after(async () => { await driver.quit(); });});区别:两者都可以与测试框架集成Appium 需要配置移动设备能力10. 性能考虑Selenium:运行在浏览器中性能主要取决于浏览器和网络相对稳定和可预测Appium:运行在移动设备上性能取决于设备性能和网络受设备状态和系统资源影响选择建议使用 Selenium 的场景Web 应用测试:测试 Web 应用程序跨浏览器测试响应式设计测试回归测试:Web 应用回归测试持续集成测试性能测试:Web 应用性能测试页面加载时间测试使用 Appium 的场景移动应用测试:原生应用测试混合应用测试移动 Web 测试跨平台测试:Android 和 iOS 应用测试跨设备兼容性测试功能测试:移动应用功能测试用户体验测试总结| 特性 | Selenium | Appium ||------|----------|--------|| 主要用途 | Web 应用测试 | 移动应用测试 || 支持平台 | Web 浏览器 | Android, iOS, Windows || 架构 | 直接与浏览器驱动通信 | 通过 Appium Server 与设备通信 || 自动化引擎 | 浏览器内置引擎 | 平台特定引擎 || 元素定位 | Web 元素定位 | 移动应用元素定位 || 手势操作 | 有限 | 丰富 || 上下文切换 | 不需要 | 需要 || 设备能力 | 有限 | 丰富 || 学习曲线 | 相对简单 | 相对复杂 |Selenium 和 Appium 都是强大的自动化测试工具,选择哪个取决于你的测试需求。如果需要测试 Web 应用,选择 Selenium;如果需要测试移动应用,选择 Appium。
阅读 0·2月21日 16:20

Appium 的工作原理是什么?

Appium 的工作原理基于客户端-服务器架构和 WebDriver 协议,通过自动化引擎与移动设备进行交互。以下是 Appium 工作原理的详细说明:架构组件1. Appium ServerAppium Server 是核心组件,负责:接收来自客户端的 HTTP 请求解析 WebDriver 命令将命令转换为平台特定的操作与移动设备或模拟器通信返回执行结果给客户端2. Appium ClientAppium Client 是各种语言的客户端库:提供语言特定的 API封装 HTTP 请求简化测试代码编写支持多种编程语言3. 自动化引擎不同平台使用不同的自动化引擎:iOS:XCUITest(iOS 9.3+)、UIAutomation(iOS 9.2-)Android:UiAutomator2(Android 5.0+)、UiAutomator(Android 4.2-)Windows:WinAppDriver工作流程1. 会话创建// 客户端代码const { Builder } = require('selenium-webdriver');const capabilities = { platformName: 'Android', deviceName: 'emulator-5554', app: '/path/to/app.apk', automationName: 'UiAutomator2'};const driver = await new Builder() .withCapabilities(capabilities) .usingServer('http://localhost:4723/wd/hub') .build();步骤:客户端发送 POST /session 请求服务器解析 desired capabilities根据平台选择合适的自动化引擎启动应用并建立会话返回会话 ID 给客户端2. 元素定位// 通过 ID 定位const element = await driver.findElement(By.id('com.example.app:id/button'));// 通过 XPath 定位const element = await driver.findElement(By.xpath('//android.widget.Button[@text="Submit"]'));// 通过 Accessibility ID 定位const element = await driver.findElement(By.accessibilityId('submit-button'));定位过程:客户端发送元素定位请求服务器将定位策略转换为平台特定的查询自动化引擎在设备上执行查询返回匹配的元素3. 元素操作// 点击元素await element.click();// 输入文本await element.sendKeys('Hello World');// 获取属性const text = await element.getText();操作过程:客户端发送操作命令服务器将命令转换为平台特定的操作自动化引擎在设备上执行操作返回操作结果WebDriver 协议Appium 遵循 W3C WebDriver 标准:HTTP 端点POST /session # 创建新会话DELETE /session/:id # 删除会话GET /session/:id/element # 查找元素POST /session/:id/element/:id/click # 点击元素JSON Wire Protocol请求和响应都使用 JSON 格式:// 请求{ "desiredCapabilities": { "platformName": "Android", "deviceName": "emulator-5554" }}// 响应{ "value": { "element-6066-11e4-a52e-4f735466cecf": "0.123456789" }, "status": 0}平台特定实现Android 实现const capabilities = { platformName: 'Android', automationName: 'UiAutomator2', appPackage: 'com.example.app', appActivity: '.MainActivity', deviceName: 'Android Emulator', platformVersion: '11.0'};工作原理:Appium Server 启动 UiAutomator2 服务器在设备上安装测试 APK通过 ADB 与设备通信使用 UiAutomator2 API 执行操作iOS 实现const capabilities = { platformName: 'iOS', automationName: 'XCUITest', bundleId: 'com.example.app', deviceName: 'iPhone 14', platformVersion: '16.0', udid: 'auto'};工作原理:Appium Server 使用 XCUITest 框架通过 WebDriverAgent 与设备通信使用 XCUITest API 执行操作支持真机和模拟器混合应用处理上下文切换// 获取所有上下文const contexts = await driver.getContexts();console.log(contexts); // ['NATIVE_APP', 'WEBVIEW_com.example.app']// 切换到 WebViewawait driver.context('WEBVIEW_com.example.app');// 在 WebView 中操作const element = await driver.findElement(By.css('#submit-button'));await element.click();// 切换回原生上下文await driver.context('NATIVE_APP');处理流程:检测应用中的 WebView获取所有可用的上下文切换到 WebView 上下文使用 WebDriver API 操作 WebView切换回原生上下文Desired CapabilitiesDesired Capabilities 是配置会话的关键参数:const capabilities = { // 平台相关 platformName: 'Android', platformVersion: '11.0', deviceName: 'Pixel 5', // 应用相关 app: '/path/to/app.apk', appPackage: 'com.example.app', appActivity: '.MainActivity', bundleId: 'com.example.app', // 自动化相关 automationName: 'UiAutomator2', noReset: true, fullReset: false, // 其他配置 newCommandTimeout: 60, autoGrantPermissions: true};通信机制HTTP 通信Appium 使用 HTTP 协议进行通信:Client (HTTP Request) → Appium Server → Automation Engine → DeviceClient (HTTP Response) ← Appium Server ← Automation Engine ← DeviceWebSocket 通信Appium 2.0 支持 WebSocket,提供更好的性能:const { Builder } = require('selenium-webdriver');const driver = await new Builder() .usingServer('ws://localhost:4723') .withCapabilities(capabilities) .build();最佳实践合理使用 Desired Capabilities:只配置必要的参数使用默认值减少配置根据平台调整配置优化元素定位:优先使用稳定的定位策略避免使用脆弱的 XPath使用 Accessibility ID 提高可维护性处理异步操作:使用显式等待避免硬编码等待时间处理加载状态错误处理:捕获和处理异常提供清晰的错误信息实现重试机制Appium 的工作原理通过标准化的 WebDriver 协议和平台特定的自动化引擎,为移动应用自动化测试提供了强大而灵活的解决方案。
阅读 0·2月21日 16:19

Appium 的等待机制有哪些?

Appium 的等待机制是处理异步操作和动态加载的关键功能,确保测试脚本的稳定性和可靠性。以下是 Appium 等待机制的详细说明:等待类型Appium 提供了三种主要的等待机制:1. 隐式等待(Implicit Wait)设置全局等待时间,在查找元素时自动应用:// 设置隐式等待await driver.manage().timeouts().implicitlyWait(10000); // 10秒// 查找元素时会自动等待const element = await driver.findElement(By.id('submit_button'));特点:全局生效,影响所有元素查找设置一次,持续有效可能导致不必要的等待2. 显式等待(Explicit Wait)针对特定条件进行等待:const { until } = require('selenium-webdriver');// 等待元素出现const element = await driver.wait( until.elementLocated(By.id('submit_button')), 10000);// 等待元素可见await driver.wait( until.elementIsVisible(element), 5000);// 等待元素可点击await driver.wait( until.elementIsClickable(element), 5000);特点:针对特定条件更精确的等待推荐使用3. 流畅等待(Fluent Wait)提供更灵活的等待方式:// 使用流畅等待const element = await driver.wait( async () => { const el = await driver.findElement(By.id('submit_button')); if (el) { return el; } return false; }, 10000, 'Element not found');常用等待条件1. 元素存在// 等待元素存在于 DOM 中const element = await driver.wait( until.elementLocated(By.id('submit_button')), 10000);2. 元素可见// 等待元素可见const element = await driver.findElement(By.id('submit_button'));await driver.wait( until.elementIsVisible(element), 5000);3. 元素可点击// 等待元素可点击const element = await driver.findElement(By.id('submit_button'));await driver.wait( until.elementIsClickable(element), 5000);4. 元素包含文本// 等待元素包含特定文本await driver.wait( until.elementTextContains(element, 'Submit'), 5000);5. 元素属性包含值// 等待元素属性包含特定值await driver.wait( until.elementAttributeContains(element, 'class', 'active'), 5000);6. 标题包含文本// 等待页面标题包含特定文本await driver.wait( until.titleContains('Dashboard'), 5000);自定义等待条件1. 基本自定义等待// 自定义等待条件async function waitForElementToBeEnabled(driver, locator, timeout = 10000) { const startTime = Date.now(); while (Date.now() - startTime < timeout) { try { const element = await driver.findElement(locator); const isEnabled = await element.isEnabled(); if (isEnabled) { return element; } } catch (error) { // 元素未找到,继续等待 } await driver.sleep(500); // 等待 500ms } throw new Error(`Element not enabled within ${timeout}ms`);}// 使用自定义等待const element = await waitForElementToBeEnabled( driver, By.id('submit_button'), 10000);2. 复杂自定义等待// 等待多个元素async function waitForMultipleElements(driver, locators, timeout = 10000) { const startTime = Date.now(); const elements = {}; while (Date.now() - startTime < timeout) { let allFound = true; for (const [name, locator] of Object.entries(locators)) { if (!elements[name]) { try { elements[name] = await driver.findElement(locator); } catch (error) { allFound = false; } } } if (allFound) { return elements; } await driver.sleep(500); } throw new Error('Not all elements found within timeout');}// 使用自定义等待const elements = await waitForMultipleElements(driver, { submitButton: By.id('submit_button'), cancelButton: By.id('cancel_button')});等待最佳实践1. 优先使用显式等待// ✅ 推荐:使用显式等待const element = await driver.wait( until.elementLocated(By.id('submit_button')), 10000);// ❌ 不推荐:使用硬编码等待await driver.sleep(10000);const element = await driver.findElement(By.id('submit_button'));2. 合理设置超时时间// 根据网络和设备性能调整超时const timeout = process.env.SLOW_NETWORK ? 20000 : 10000;const element = await driver.wait( until.elementLocated(By.id('submit_button')), timeout);3. 提供清晰的错误信息// 自定义错误信息const element = await driver.wait( until.elementLocated(By.id('submit_button')), 10000, 'Submit button not found within 10 seconds');4. 组合多个等待条件// 等待元素可见且可点击const element = await driver.findElement(By.id('submit_button'));await driver.wait( until.elementIsVisible(element), 5000);await driver.wait( until.elementIsClickable(element), 5000);等待常见问题1. 等待超时原因:超时时间设置过短元素定位策略不正确元素在另一个上下文中解决方案:// 增加超时时间const element = await driver.wait( until.elementLocated(By.id('submit_button')), 20000);// 检查上下文const contexts = await driver.getContexts();console.log('Available contexts:', contexts);// 切换上下文await driver.context('WEBVIEW_com.example.app');2. 不必要的等待原因:使用了隐式等待硬编码了等待时间解决方案:// 避免使用隐式等待// await driver.manage().timeouts().implicitlyWait(10000);// 使用显式等待const element = await driver.wait( until.elementLocated(By.id('submit_button')), 10000);3. 等待条件不明确原因:等待条件不够具体没有验证元素状态解决方案:// ❌ 不够具体const element = await driver.wait( until.elementLocated(By.id('submit_button')), 10000);// ✅ 更具体const element = await driver.findElement(By.id('submit_button'));await driver.wait( until.elementIsVisible(element), 5000);await driver.wait( until.elementIsClickable(element), 5000);等待性能优化1. 减少等待时间// 使用更精确的等待条件const element = await driver.wait( until.elementIsVisible(await driver.findElement(By.id('submit_button'))), 5000);2. 并行等待// 并行等待多个元素const [element1, element2] = await Promise.all([ driver.wait(until.elementLocated(By.id('button1')), 5000), driver.wait(until.elementLocated(By.id('button2')), 5000)]);3. 使用轮询间隔// 设置轮询间隔const element = await driver.wait( until.elementLocated(By.id('submit_button')), 10000, 'Element not found', 500 // 轮询间隔 500ms);最佳实践优先使用显式等待:更精确的等待更好的性能更清晰的错误信息合理设置超时:根据实际情况调整避免过短或过长考虑网络和设备性能避免硬编码等待:不使用 sleep()使用条件等待提高测试稳定性处理等待超时:提供清晰的错误信息实现重试机制记录超时原因Appium 的等待机制为测试人员提供了强大的异步操作处理能力,通过合理使用各种等待策略,可以构建稳定、可靠的自动化测试。
阅读 0·2月21日 16:19

Appium 如何与测试框架集成?

Appium 的测试框架集成是构建完整自动化测试体系的关键环节,支持与多种测试框架和工具链集成。以下是 Appium 测试框架集成的详细说明:支持的测试框架1. MochaMocha 是一个流行的 JavaScript 测试框架:const { describe, it, before, after, beforeEach, afterEach } = require('mocha');const { Builder, By, until } = require('selenium-webdriver');const assert = require('assert');describe('Appium Test with Mocha', () => { let driver; before(async () => { const capabilities = { platformName: 'Android', deviceName: 'Pixel 5', app: '/path/to/app.apk' }; driver = await new Builder().withCapabilities(capabilities).build(); }); after(async () => { await driver.quit(); }); beforeEach(async () => { // 每个测试前的准备工作 }); afterEach(async () => { // 每个测试后的清理工作 }); it('should submit form successfully', async () => { const element = await driver.findElement(By.id('submit_button')); await element.click(); const result = await driver.findElement(By.id('result_message')); const text = await result.getText(); assert.strictEqual(text, 'Success'); }); it('should display error message', async () => { const element = await driver.findElement(By.id('submit_button')); await element.click(); const result = await driver.findElement(By.id('error_message')); const text = await result.getText(); assert.strictEqual(text, 'Error'); });});2. JestJest 是 Facebook 开发的 JavaScript 测试框架:const { Builder, By, until } = require('selenium-webdriver');describe('Appium Test with Jest', () => { let driver; beforeAll(async () => { const capabilities = { platformName: 'Android', deviceName: 'Pixel 5', app: '/path/to/app.apk' }; driver = await new Builder().withCapabilities(capabilities).build(); }); afterAll(async () => { await driver.quit(); }); test('should submit form successfully', async () => { const element = await driver.findElement(By.id('submit_button')); await element.click(); const result = await driver.findElement(By.id('result_message')); const text = await result.getText(); expect(text).toBe('Success'); });});3. JasmineJasmine 是一个行为驱动开发(BDD)测试框架:const { Builder, By, until } = require('selenium-webdriver');describe('Appium Test with Jasmine', () => { let driver; beforeAll(async () => { const capabilities = { platformName: 'Android', deviceName: 'Pixel 5', app: '/path/to/app.apk' }; driver = await new Builder().withCapabilities(capabilities).build(); }); afterAll(async () => { await driver.quit(); }); it('should submit form successfully', async () => { const element = await driver.findElement(By.id('submit_button')); await element.click(); const result = await driver.findElement(By.id('result_message')); const text = await result.getText(); expect(text).toBe('Success'); });});4. TestNG (Java)TestNG 是一个流行的 Java 测试框架:import org.testng.annotations.*;import org.openqa.selenium.*;import org.openqa.selenium.remote.DesiredCapabilities;import io.appium.java_client.AppiumDriver;import io.appium.java_client.MobileElement;public class AppiumTestWithTestNG { private AppiumDriver<MobileElement> driver; @BeforeClass public void setUp() throws Exception { DesiredCapabilities capabilities = new DesiredCapabilities(); capabilities.setCapability("platformName", "Android"); capabilities.setCapability("deviceName", "Pixel 5"); capabilities.setCapability("app", "/path/to/app.apk"); driver = new AppiumDriver<>(new URL("http://localhost:4723/wd/hub"), capabilities); } @AfterClass public void tearDown() { if (driver != null) { driver.quit(); } } @Test public void testSubmitForm() throws Exception { MobileElement element = driver.findElement(By.id("submit_button")); element.click(); MobileElement result = driver.findElement(By.id("result_message")); String text = result.getText(); assertEquals(text, "Success"); }}5. PyTest (Python)PyTest 是一个流行的 Python 测试框架:import pytestfrom appium import webdriverfrom selenium.webdriver.common.by import By@pytest.fixturedef driver(): capabilities = { 'platformName': 'Android', 'deviceName': 'Pixel 5', 'app': '/path/to/app.apk' } driver = webdriver.Remote('http://localhost:4723/wd/hub', capabilities) yield driver driver.quit()def test_submit_form(driver): element = driver.find_element(By.ID, 'submit_button') element.click() result = driver.find_element(By.ID, 'result_message') text = result.text assert text == 'Success'持续集成集成1. Jenkinspipeline { agent any stages { stage('Install Dependencies') { steps { sh 'npm install' } } stage('Run Appium Tests') { steps { sh 'npm run test:appium' } } stage('Generate Reports') { steps { sh 'npm run test:report' } } } post { always { junit 'test-results/**/*.xml' publishHTML([ allowMissing: false, alwaysLinkToLastBuild: true, keepAll: true, reportDir: 'test-results/html', reportFiles: 'index.html', reportName: 'Appium Test Report' ]) } }}2. GitHub Actionsname: Appium Testson: push: branches: [ main ] pull_request: branches: [ main ]jobs: test: runs-on: macos-latest steps: - uses: actions/checkout@v2 - name: Set up Node.js uses: actions/setup-node@v2 with: node-version: '16' - name: Install dependencies run: npm install - name: Start Appium Server run: npx appium & - name: Run Appium tests run: npm run test:appium - name: Upload test results uses: actions/upload-artifact@v2 if: always() with: name: test-results path: test-results/3. GitLab CIstages: - testappium_tests: stage: test image: node:16 before_script: - npm install script: - npx appium & - npm run test:appium artifacts: when: always paths: - test-results/ reports: junit: test-results/**/*.xml测试报告1. Allure Reportconst { Builder, By, until } = require('selenium-webdriver');const allure = require('allure-commandline');describe('Appium Test with Allure', () => { let driver; before(async () => { const capabilities = { platformName: 'Android', deviceName: 'Pixel 5', app: '/path/to/app.apk' }; driver = await new Builder().withCapabilities(capabilities).build(); }); after(async () => { await driver.quit(); }); it('should submit form successfully', async () => { allure.step('Click submit button', async () => { const element = await driver.findElement(By.id('submit_button')); await element.click(); }); allure.step('Verify result', async () => { const result = await driver.findElement(By.id('result_message')); const text = await result.getText(); assert.strictEqual(text, 'Success'); }); });});2. Mochawesomeconst { Builder, By, until } = require('selenium-webdriver');describe('Appium Test with Mochawesome', () => { let driver; before(async () => { const capabilities = { platformName: 'Android', deviceName: 'Pixel 5', app: '/path/to/app.apk' }; driver = await new Builder().withCapabilities(capabilities).build(); }); after(async () => { await driver.quit(); }); it('should submit form successfully', async () => { const element = await driver.findElement(By.id('submit_button')); await element.click(); const result = await driver.findElement(By.id('result_message')); const text = await result.getText(); assert.strictEqual(text, 'Success'); });});数据驱动测试1. 使用 JSON 数据const { Builder, By, until } = require('selenium-webdriver');const testData = require('./test-data.json');describe('Data-Driven Tests', () => { let driver; before(async () => { const capabilities = { platformName: 'Android', deviceName: 'Pixel 5', app: '/path/to/app.apk' }; driver = await new Builder().withCapabilities(capabilities).build(); }); after(async () => { await driver.quit(); }); testData.forEach((data, index) => { it(`Test case ${index + 1}: ${data.description}`, async () => { const input = await driver.findElement(By.id('input_field')); await input.sendKeys(data.input); const button = await driver.findElement(By.id('submit_button')); await button.click(); const result = await driver.findElement(By.id('result_message')); const text = await result.getText(); assert.strictEqual(text, data.expected); }); });});2. 使用 Excel 数据const { Builder, By, until } = require('selenium-webdriver');const xlsx = require('xlsx');describe('Data-Driven Tests with Excel', () => { let driver; let testData; before(async () => { const capabilities = { platformName: 'Android', deviceName: 'Pixel 5', app: '/path/to/app.apk' }; driver = await new Builder().withCapabilities(capabilities).build(); // 读取 Excel 数据 const workbook = xlsx.readFile('./test-data.xlsx'); const sheet = workbook.Sheets['Sheet1']; testData = xlsx.utils.sheet_to_json(sheet); }); after(async () => { await driver.quit(); }); testData.forEach((data, index) => { it(`Test case ${index + 1}: ${data.description}`, async () => { const input = await driver.findElement(By.id('input_field')); await input.sendKeys(data.input); const button = await driver.findElement(By.id('submit_button')); await button.click(); const result = await driver.findElement(By.id('result_message')); const text = await result.getText(); assert.strictEqual(text, data.expected); }); });});并行测试1. 使用 Mocha 并行测试const { Builder, By, until } = require('selenium-webdriver');describe('Parallel Tests', () => { it('Test 1', async () => { const capabilities = { platformName: 'Android', deviceName: 'Pixel 5', app: '/path/to/app.apk' }; const driver = await new Builder().withCapabilities(capabilities).build(); try { const element = await driver.findElement(By.id('submit_button')); await element.click(); } finally { await driver.quit(); } }); it('Test 2', async () => { const capabilities = { platformName: 'Android', deviceName: 'Pixel 6', app: '/path/to/app.apk' }; const driver = await new Builder().withCapabilities(capabilities).build(); try { const element = await driver.findElement(By.id('submit_button')); await element.click(); } finally { await driver.quit(); } });});2. 使用 TestNG 并行测试import org.testng.annotations.*;import org.openqa.selenium.*;import org.openqa.selenium.remote.DesiredCapabilities;import io.appium.java_client.AppiumDriver;import io.appium.java_client.MobileElement;@Test(threadPoolSize = 3, invocationCount = 3)public class ParallelAppiumTests { @Test(dataProvider = "devices") public void testOnDevice(String deviceName) throws Exception { DesiredCapabilities capabilities = new DesiredCapabilities(); capabilities.setCapability("platformName", "Android"); capabilities.setCapability("deviceName", deviceName); capabilities.setCapability("app", "/path/to/app.apk"); AppiumDriver<MobileElement> driver = new AppiumDriver<>( new URL("http://localhost:4723/wd/hub"), capabilities ); try { MobileElement element = driver.findElement(By.id("submit_button")); element.click(); } finally { driver.quit(); } } @DataProvider(name = "devices") public Object[][] getDevices() { return new Object[][] { {"Pixel 5"}, {"Pixel 6"}, {"Pixel 7"} }; }}最佳实践选择合适的测试框架:根据团队技术栈选择考虑框架的生态系统评估学习成本配置持续集成:自动化测试执行生成测试报告及时反馈测试结果实现并行测试:提高测试效率缩短测试时间充分利用资源使用数据驱动:提高测试覆盖率简化测试维护支持多场景测试生成详细报告:记录测试结果分析测试趋势改进测试质量Appium 的测试框架集成为测试人员提供了灵活的测试解决方案,通过合理配置和优化,可以构建高效、稳定的自动化测试体系。
阅读 0·2月21日 16:19

Appium 如何进行手势操作?

Appium 的手势操作是模拟用户交互的重要功能,支持各种触摸和手势操作。以下是 Appium 手势操作的详细说明:基本手势操作1. 点击(Tap)// 单击await element.click();// 点击坐标await driver.touchActions([ { action: 'tap', x: 100, y: 200 }]);// 多次点击await element.click();await element.click();await element.click();2. 长按(Long Press)// 长按元素const actions = driver.actions({ async: true });await actions.move({ origin: element }).press().pause(2000).release().perform();// 长按坐标await driver.touchActions([ { action: 'press', x: 100, y: 200 }, { action: 'wait', ms: 2000 }, { action: 'release' }]);3. 双击(Double Tap)// 双击元素const actions = driver.actions({ async: true });await actions.move({ origin: element }).doubleClick().perform();// 使用 TouchActionconst touchAction = new TouchAction(driver);touchAction.tap({ x: 100, y: 200 }).tap({ x: 100, y: 200 });await touchAction.perform();4. 滑动(Swipe)// 滑动元素await driver.touchActions([ { action: 'press', x: 100, y: 500 }, { action: 'moveTo', x: 100, y: 100 }, { action: 'release' }]);// 使用 TouchActionconst touchAction = new TouchAction(driver);touchAction.press({ x: 100, y: 500 }).moveTo({ x: 100, y: 100 }).release();await touchAction.perform();高级手势操作1. 滚动(Scroll)// 滚动到元素await element.sendKeys('Hello');// 滚动页面const size = await driver.manage().window().getRect();const startX = size.width / 2;const startY = size.height * 0.8;const endY = size.height * 0.2;await driver.touchActions([ { action: 'press', x: startX, y: startY }, { action: 'moveTo', x: startX, y: endY }, { action: 'release' }]);2. 拖拽(Drag and Drop)// 拖拽元素const actions = driver.actions({ async: true });await actions.dragAndDrop(sourceElement, targetElement).perform();// 使用 TouchActionconst touchAction = new TouchAction(driver);touchAction.press({ el: sourceElement }) .moveTo({ el: targetElement }) .release();await touchAction.perform();3. 缩放(Pinch)// 缩放操作const actions = driver.actions({ async: true });await actions .move({ origin: element }) .press() .move({ origin: element, x: 50, y: 0 }) .release() .perform();4. 旋转(Rotate)// 旋转操作const actions = driver.actions({ async: true });await actions .move({ origin: element }) .press() .move({ origin: element, x: 0, y: 50 }) .release() .perform();多点触控1. 多点触控(Multi-touch)// 多点触控操作const actions = driver.actions({ async: true });const finger1 = actions.move({ origin: element1 });const finger2 = actions.move({ origin: element2 });await actions .clear() .move({ origin: finger1 }).press() .move({ origin: finger2 }).press() .pause(100) .move({ origin: finger1 }).release() .move({ origin: finger2 }).release() .perform();2. 捏合(Pinch and Spread)// 捏合操作const actions = driver.actions({ async: true });const center = { x: 200, y: 200 };await actions .move({ origin: center, x: -50, y: 0 }).press() .move({ origin: center, x: 50, y: 0 }).press() .pause(500) .move({ origin: center, x: -25, y: 0 }).release() .move({ origin: center, x: 25, y: 0 }).release() .perform();手势操作最佳实践1. 使用显式等待// 等待元素可交互await driver.wait( until.elementIsClickable(element), 5000);// 执行手势操作await element.click();2. 处理动画// 等待动画完成await driver.sleep(500);// 执行手势操作await element.click();3. 验证操作结果// 执行手势操作await element.click();// 验证结果const result = await driver.findElement(By.id('result_message'));const text = await result.getText();assert.strictEqual(text, 'Success');手势操作优化1. 减少手势操作// ❌ 不推荐:多次点击await element.click();await element.click();await element.click();// ✅ 推荐:使用双击const actions = driver.actions({ async: true });await actions.move({ origin: element }).doubleClick().perform();2. 使用相对坐标// 使用元素相对坐标const rect = await element.getRect();const centerX = rect.x + rect.width / 2;const centerY = rect.y + rect.height / 2;await driver.touchActions([ { action: 'press', x: centerX, y: centerY }, { action: 'release' }]);3. 处理不同屏幕尺寸// 获取屏幕尺寸const size = await driver.manage().window().getRect();// 计算相对坐标const x = size.width * 0.5;const y = size.height * 0.5;await driver.touchActions([ { action: 'press', x: x, y: y }, { action: 'release' }]);手势操作常见问题1. 手势操作失败原因:元素不可见或不可点击手势操作被其他元素阻挡动画未完成解决方案:// 等待元素可点击await driver.wait( until.elementIsClickable(element), 5000);// 滚动到元素await driver.executeScript('arguments[0].scrollIntoView(true);', element);// 执行手势操作await element.click();2. 手势操作不准确原因:坐标计算错误屏幕尺寸变化元素位置变化解决方案:// 使用元素定位而非坐标await element.click();// 动态计算坐标const rect = await element.getRect();const x = rect.x + rect.width / 2;const y = rect.y + rect.height / 2;3. 多点触控不支持原因:设备不支持多点触控Appium 版本不支持解决方案:// 检查多点触控支持const capabilities = await driver.getCapabilities();const supportsMultiTouch = capabilities.supportsMultiTouch;if (!supportsMultiTouch) { console.warn('Multi-touch not supported');}手势操作工具1. Appium InspectorAppium Inspector 提供手势操作录制功能:录制手势操作生成代码测试手势操作2. 自定义手势库// 创建自定义手势库class GestureHelper { constructor(driver) { this.driver = driver; } async swipe(startX, startY, endX, endY, duration = 1000) { await this.driver.touchActions([ { action: 'press', x: startX, y: startY }, { action: 'wait', ms: duration }, { action: 'moveTo', x: endX, y: endY }, { action: 'release' } ]); } async longPress(element, duration = 2000) { const actions = this.driver.actions({ async: true }); await actions.move({ origin: element }).press().pause(duration).release().perform(); }}// 使用自定义手势库const gestures = new GestureHelper(driver);await gestures.swipe(100, 500, 100, 100);await gestures.longPress(element, 2000);最佳实践优先使用元素操作:使用 element.click() 而非坐标点击更稳定和可维护适应屏幕尺寸变化合理使用等待:等待元素可交互处理动画和加载避免硬编码等待验证操作结果:检查操作后的状态验证预期结果提供清晰的错误信息处理异常情况:捕获手势操作异常实现重试机制记录失败原因Appium 的手势操作为测试人员提供了强大的用户交互模拟能力,通过合理使用各种手势操作,可以构建真实、可靠的自动化测试。
阅读 0·2月21日 16:19

Appium 如何进行元素定位?

Appium 的元素定位是自动化测试的核心功能,提供了多种定位策略来查找移动应用中的元素。以下是 Appium 元素定位的详细说明:定位策略Appium 支持多种元素定位策略,可以通过 By 类使用:1. ID 定位// 通过 resource-id 定位(Android)const element = await driver.findElement(By.id('com.example.app:id/submit_button'));// 通过 name 定位(iOS)const element = await driver.findElement(By.id('submit_button'));特点:最快、最稳定的定位方式推荐优先使用需要应用有明确的 ID2. XPath 定位// 基本 XPathconst element = await driver.findElement(By.xpath('//android.widget.Button'));// 带属性的 XPathconst element = await driver.findElement(By.xpath('//android.widget.Button[@text="Submit"]'));// 复杂 XPathconst element = await driver.findElement(By.xpath('//android.widget.Button[contains(@text, "Sub")]'));特点:功能强大,支持复杂查询性能相对较慢容易因 UI 变化而失效3. Class Name 定位// Androidconst element = await driver.findElement(By.className('android.widget.Button'));// iOSconst element = await driver.findElement(By.className('XCUIElementTypeButton'));特点:定位所有相同类型的元素通常需要进一步筛选跨平台不统一4. Accessibility ID 定位const element = await driver.findElement(By.accessibilityId('submit_button'));特点:跨平台统一推荐用于可访问性测试需要开发人员设置5. CSS Selector 定位// 在 WebView 中使用const element = await driver.findElement(By.css('#submit-button'));const element = await driver.findElement(By.css('.submit-btn'));特点:主要用于 WebView类似于 Web 开发不支持原生视图6. Android UIAutomator 定位// UiAutomator 定位const element = await driver.findElement( By.androidUIAutomator('new UiSelector().text("Submit").className("android.widget.Button")'));特点:Android 专用功能强大性能较好7. iOS Predicate 定位// Predicate String 定位const element = await driver.findElement( By.iOSNsPredicateString('name == "Submit" AND type == "XCUIElementTypeButton"'));特点:iOS 专用功能强大性能较好8. iOS Class Chain 定位// Class Chain 定位const element = await driver.findElement( By.iOSClassChain('**/XCUIElementTypeButton[`name == "Submit"`]'));特点:iOS 专用比 Predicate 更简洁性能较好元素定位最佳实践1. 优先使用稳定的定位策略// ✅ 推荐:使用 ID 或 Accessibility IDconst element = await driver.findElement(By.id('submit_button'));const element = await driver.findElement(By.accessibilityId('submit_button'));// ❌ 不推荐:使用复杂的 XPathconst element = await driver.findElement(By.xpath('//android.widget.Button[@text="Submit"]'));2. 使用相对定位// 先定位父元素const parent = await driver.findElement(By.id('form_container'));// 在父元素中定位子元素const button = await parent.findElement(By.id('submit_button'));3. 使用多个定位策略// 尝试多个定位策略async function findElement(locators) { for (const locator of locators) { try { const element = await driver.findElement(locator); return element; } catch (error) { continue; } } throw new Error('Element not found');}const element = await findElement([ By.id('submit_button'), By.accessibilityId('submit_button'), By.xpath('//android.widget.Button[@text="Submit"]')]);元素定位优化1. 减少定位范围// ❌ 不推荐:在整个页面中搜索const element = await driver.findElement(By.id('submit_button'));// ✅ 推荐:在特定容器中搜索const container = await driver.findElement(By.id('form_container'));const element = await container.findElement(By.id('submit_button'));2. 使用显式等待// 等待元素出现const element = await driver.wait( until.elementLocated(By.id('submit_button')), 10000);// 等待元素可点击await driver.wait( until.elementIsVisible(element), 5000);3. 缓存元素引用// ❌ 不推荐:重复定位await driver.findElement(By.id('submit_button')).click();await driver.findElement(By.id('submit_button')).sendKeys('text');// ✅ 推荐:缓存元素引用const button = await driver.findElement(By.id('submit_button'));await button.click();await button.sendKeys('text');跨平台元素定位1. 使用统一的定位策略// 使用 Accessibility ID 实现跨平台定位const element = await driver.findElement(By.accessibilityId('submit_button'));2. 平台特定定位// 根据平台选择定位策略const platform = await driver.getCapabilities().then(caps => caps.platformName);let element;if (platform === 'Android') { element = await driver.findElement(By.id('com.example.app:id/submit_button'));} else if (platform === 'iOS') { element = await driver.findElement(By.id('submit_button'));}元素定位常见问题1. 元素找不到原因:定位策略不正确元素尚未加载元素在另一个上下文中解决方案:// 使用显式等待const element = await driver.wait( until.elementLocated(By.id('submit_button')), 10000);// 检查上下文const contexts = await driver.getContexts();console.log('Available contexts:', contexts);// 切换上下文await driver.context('WEBVIEW_com.example.app');2. 定位到多个元素原因:定位策略匹配多个元素需要更精确的定位解决方案:// 使用 findElements 查找所有匹配元素const elements = await driver.findElements(By.className('android.widget.Button'));console.log('Found elements:', elements.length);// 使用更精确的定位策略const element = await driver.findElement( By.xpath('//android.widget.Button[@text="Submit" and @index="0"]'));3. 元素定位不稳定原因:使用了脆弱的定位策略UI 结构频繁变化解决方案:// 使用稳定的定位策略const element = await driver.findElement(By.id('submit_button'));// 使用相对定位const container = await driver.findElement(By.id('form_container'));const element = await container.findElement(By.id('submit_button'));// 使用 Accessibility IDconst element = await driver.findElement(By.accessibilityId('submit_button'));元素定位工具1. Appium InspectorAppium Inspector 是一个可视化工具,用于:查看应用 UI 结构获取元素属性测试元素定位策略2. uiautomatorviewerAndroid 专用工具,用于:查看应用 UI 结构获取元素属性生成定位策略3. Accessibility InspectoriOS 专用工具,用于:检查可访问性获取 Accessibility ID优化元素定位最佳实践优先使用稳定的定位策略:ID 和 Accessibility ID避免使用复杂的 XPath使用相对定位合理使用等待:使用显式等待避免硬编码等待时间处理加载状态优化定位性能:减少定位范围缓存元素引用使用平台特定的定位策略处理定位失败:提供清晰的错误信息实现重试机制记录定位失败的原因Appium 的元素定位为测试人员提供了强大而灵活的功能,通过合理使用各种定位策略,可以构建稳定、高效的自动化测试。
阅读 0·2月21日 16:19

Appium 的 Desired Capabilities 是什么?

Appium 的 Desired Capabilities 是配置测试会话的关键参数,用于指定测试环境、应用和自动化行为。以下是 Appium Desired Capabilities 的详细说明:基本概念Desired Capabilities 是一组键值对,用于告诉 Appium Server:在哪个平台上运行测试使用哪个设备或模拟器启动哪个应用如何配置自动化行为通用 Capabilities平台相关const capabilities = { platformName: 'Android', // 平台名称 platformVersion: '11.0', // 平台版本 deviceName: 'Pixel 5', // 设备名称 automationName: 'UiAutomator2' // 自动化引擎};说明:platformName:必需,指定平台(Android、iOS、Windows)platformVersion:可选,指定操作系统版本deviceName:必需,指定设备或模拟器名称automationName:可选,指定自动化引擎应用相关const capabilities = { app: '/path/to/app.apk', // 应用文件路径 appPackage: 'com.example.app', // Android 包名 appActivity: '.MainActivity', // Android Activity bundleId: 'com.example.app', // iOS Bundle ID autoLaunch: true, // 自动启动应用 noReset: true, // 不重置应用状态 fullReset: false // 不完全重置};说明:app:应用文件的本地路径或 URLappPackage:Android 应用的包名appActivity:Android 应用的启动 ActivitybundleId:iOS 应用的 Bundle IDautoLaunch:是否自动启动应用noReset:测试前不重置应用状态fullReset:测试前是否完全重置Android 特定 Capabilitiesconst capabilities = { platformName: 'Android', automationName: 'UiAutomator2', // 应用配置 appPackage: 'com.example.app', appActivity: '.MainActivity', appWaitPackage: 'com.example.app', appWaitActivity: '.MainActivity', // 设备配置 deviceName: 'Android Emulator', platformVersion: '11.0', udid: 'emulator-5554', // 浏览器测试 browserName: 'Chrome', // 权限配置 autoGrantPermissions: true, autoWebview: false, // 其他配置 ignoreUnimportantViews: false, disableWindowAnimation: false, skipServerInstallation: false, skipDeviceInitialization: false};关键参数:udid:设备的唯一标识符autoGrantPermissions:自动授予应用权限ignoreUnimportantViews:忽略不重要的视图disableWindowAnimation:禁用窗口动画iOS 特定 Capabilitiesconst capabilities = { platformName: 'iOS', automationName: 'XCUITest', // 应用配置 bundleId: 'com.example.app', app: '/path/to/app.ipa', // 设备配置 deviceName: 'iPhone 14', platformVersion: '16.0', udid: 'auto', // 模拟器配置 wdaStartupRetries: 4, wdaLocalPort: 8100, usePrebuiltWDA: false, wdaConnectionTimeout: 60000, // 其他配置 showXcodeLog: false, xcodeOrgId: 'TEAM_ID', xcodeSigningId: 'iPhone Developer', updatedWDABundleId: 'com.example.app.WebDriverAgentRunner'};关键参数:udid:设备的唯一标识符(真机)或 'auto'(模拟器)wdaStartupRetries:WebDriverAgent 启动重试次数wdaLocalPort:WebDriverAgent 本地端口usePrebuiltWDA:使用预构建的 WebDriverAgentxcodeOrgId:Xcode 团队 ID(真机测试)xcodeSigningId:Xcode 签名 ID(真机测试)会话配置 Capabilitiesconst capabilities = { // 超时配置 newCommandTimeout: 60, // 新命令超时时间(秒) commandTimeouts: { implicit: 0, // 隐式等待超时 pageLoad: 300000, // 页面加载超时 script: 30000 // 脚本执行超时 }, // 语言和区域 language: 'zh-CN', locale: 'zh_CN', // 时区 timezone: 'Asia/Shanghai', // 其他配置 clearSystemFiles: true, eventTimings: true, enableMultiWindows: false};性能优化 Capabilitiesconst capabilities = { // 跳过不必要的步骤 skipServerInstallation: false, skipDeviceInitialization: false, skipUninstall: false, // 禁用动画 disableWindowAnimation: true, ignoreUnimportantViews: true, // 优化性能 nativeWebScreenshot: true, screenshotQuality: 0, mjpegScreenshotUrl: 'http://localhost:8080/stream'};调试 Capabilitiesconst capabilities = { // 日志配置 showXcodeLog: true, androidInstallTimeout: 90000, uiautomator2ServerInstallTimeout: 200000, // 调试模式 debugLogSpacing: true, suppressKillServer: false, // 详细日志 systemPort: 8200, mjpegServerPort: 7810};最佳实践1. 合理配置 Capabilities// 使用环境变量const capabilities = { platformName: process.env.PLATFORM_NAME || 'Android', deviceName: process.env.DEVICE_NAME || 'emulator-5554', app: process.env.APP_PATH || '/path/to/app.apk'};2. 分离平台配置// android.config.jsmodule.exports = { platformName: 'Android', automationName: 'UiAutomator2', appPackage: 'com.example.app', appActivity: '.MainActivity'};// ios.config.jsmodule.exports = { platformName: 'iOS', automationName: 'XCUITest', bundleId: 'com.example.app'};// 根据平台选择配置const platform = process.env.PLATFORM || 'Android';const capabilities = require(`./${platform.toLowerCase()}.config.js`);3. 使用默认值const defaultCapabilities = { newCommandTimeout: 60, autoLaunch: true, noReset: false, fullReset: false};const capabilities = { ...defaultCapabilities, platformName: 'Android', deviceName: 'Pixel 5'};4. 验证 Capabilitiesfunction validateCapabilities(capabilities) { const required = ['platformName', 'deviceName', 'automationName']; const missing = required.filter(key => !capabilities[key]); if (missing.length > 0) { throw new Error(`Missing required capabilities: ${missing.join(', ')}`); } return capabilities;}const capabilities = validateCapabilities({ platformName: 'Android', deviceName: 'Pixel 5', automationName: 'UiAutomator2'});常见问题找不到元素:检查 automationName 是否正确确认应用已正确启动验证元素定位策略应用启动失败:检查 appPackage 和 appActivity 是否正确确认应用文件路径正确查看日志了解详细错误权限问题:设置 autoGrantPermissions: true手动授予必要权限检查应用权限配置Appium 的 Desired Capabilities 为测试人员提供了灵活的配置方式,通过合理配置,可以满足各种测试场景的需求。
阅读 0·2月21日 16:19

Appium 如何进行移动 Web 测试?

Appium 的移动 Web 测试是移动应用自动化测试的重要组成部分,用于测试移动浏览器中的 Web 应用。以下是 Appium 移动 Web 测试的详细说明:移动 Web 测试概述什么是移动 Web 测试移动 Web 测试是指使用移动设备的浏览器来测试 Web 应用程序:使用移动设备的原生浏览器(如 Chrome、Safari)测试响应式设计和移动优化验证 Web 应用在移动设备上的功能和性能移动 Web 测试场景// 移动 Web 测试常见场景{ "testScenarios": [ "响应式布局测试", "触摸交互测试", "移动性能测试", "跨浏览器兼容性测试", "移动端用户体验测试" ]}浏览器配置1. Android Chrome 浏览器// Android Chrome 浏览器配置const capabilities = { platformName: 'Android', deviceName: 'Pixel 5', browserName: 'Chrome', platformVersion: '11.0', udid: 'emulator-5554'};const driver = await new Builder() .withCapabilities(capabilities) .build();// 访问 Web 应用await driver.get('https://example.com');2. iOS Safari 浏览器// iOS Safari 浏览器配置const capabilities = { platformName: 'iOS', deviceName: 'iPhone 14', browserName: 'Safari', platformVersion: '16.0', udid: 'auto'};const driver = await new Builder() .withCapabilities(capabilities) .build();// 访问 Web 应用await driver.get('https://example.com');3. 高级浏览器配置// Android Chrome 高级配置const capabilities = { platformName: 'Android', deviceName: 'Pixel 5', browserName: 'Chrome', platformVersion: '11.0', // Chrome 选项 chromeOptions: { args: [ '--disable-popup-blocking', '--disable-infobars', '--start-maximized' ], prefs: { 'profile.default_content_setting_values.notifications': 2 } }, // 性能优化 nativeWebScreenshot: true, screenshotQuality: 0};// iOS Safari 高级配置const capabilities = { platformName: 'iOS', deviceName: 'iPhone 14', browserName: 'Safari', platformVersion: '16.0', // Safari 选项 safariIgnoreFraudWarning: true, safariInitialUrl: 'https://example.com', safariAllowPopups: true, safariOpenLinksInBackground: false};元素定位1. 标准 WebDriver 定位策略// 使用 ID 定位const element = await driver.findElement(By.id('submit_button'));await element.click();// 使用 CSS 选择器const element = await driver.findElement(By.css('.submit-btn'));await element.click();// 使用 XPathconst element = await driver.findElement(By.xpath('//button[@id="submit_button"]'));await element.click();// 使用类名const element = await driver.findElement(By.className('btn-primary'));await element.click();// 使用标签名const element = await driver.findElement(By.tagName('button'));await element.click();// 使用名称const element = await driver.findElement(By.name('submit'));await element.click();// 使用链接文本const element = await driver.findElement(By.linkText('Submit'));await element.click();// 使用部分链接文本const element = await driver.findElement(By.partialLinkText('Sub'));await element.click();2. 移动端特定定位// 使用触摸元素定位const element = await driver.findElement(By.css('[touch-action="manipulation"]'));// 使用响应式元素定位const element = await driver.findElement(By.css('@media (max-width: 768px) .mobile-button'));移动端交互1. 触摸操作// 点击操作const element = await driver.findElement(By.id('submit_button'));await element.click();// 双击操作const actions = driver.actions({ async: true });await actions.move({ origin: element }).doubleClick().perform();// 长按操作const actions = driver.actions({ async: true });await actions.move({ origin: element }).press().pause(2000).release().perform();2. 滑动操作// 滑动操作const size = await driver.manage().window().getRect();const startX = size.width / 2;const startY = size.height * 0.8;const endY = size.height * 0.2;await driver.touchActions([ { action: 'press', x: startX, y: startY }, { action: 'moveTo', x: startX, y: endY }, { action: 'release' }]);// 使用 JavaScript 滚动await driver.executeScript('window.scrollTo(0, 1000);');3. 缩放操作// 缩放操作const actions = driver.actions({ async: true });await actions .move({ origin: element }) .press() .move({ origin: element, x: 50, y: 0 }) .release() .perform();响应式设计测试1. 测试不同屏幕尺寸// 测试不同屏幕尺寸const screenSizes = [ { width: 375, height: 667 }, // iPhone SE { width: 414, height: 896 }, // iPhone 11 { width: 360, height: 640 }, // Android 小屏 { width: 412, height: 915 } // Android 大屏];for (const size of screenSizes) { // 设置窗口大小 await driver.manage().window().setRect({ width: size.width, height: size.height }); // 测试响应式布局 const element = await driver.findElement(By.css('.responsive-element')); const isVisible = await element.isDisplayed(); console.log(`Screen ${size.width}x${size.height}: Element visible: ${isVisible}`);}2. 测试横竖屏切换// 测试横竖屏切换async function testOrientation() { // 竖屏模式 await driver.rotateScreen('PORTRAIT'); const portraitElement = await driver.findElement(By.css('.portrait-element')); const portraitVisible = await portraitElement.isDisplayed(); console.log('Portrait mode:', portraitVisible); // 横屏模式 await driver.rotateScreen('LANDSCAPE'); const landscapeElement = await driver.findElement(By.css('.landscape-element')); const landscapeVisible = await landscapeElement.isDisplayed(); console.log('Landscape mode:', landscapeVisible);}await testOrientation();性能测试1. 页面加载时间// 测量页面加载时间async function measurePageLoadTime(driver, url) { const startTime = Date.now(); await driver.get(url); const endTime = Date.now(); const loadTime = endTime - startTime; console.log(`Page load time: ${loadTime}ms`); return loadTime;}const loadTime = await measurePageLoadTime(driver, 'https://example.com');assert(loadTime < 3000, 'Page load time should be less than 3 seconds');2. 资源加载时间// 获取性能指标const metrics = await driver.executeScript(` return window.performance.timing;`);const pageLoadTime = metrics.loadEventEnd - metrics.navigationStart;const domContentLoaded = metrics.domContentLoadedEventEnd - metrics.navigationStart;console.log('Page load time:', pageLoadTime);console.log('DOM content loaded:', domContentLoaded);3. 网络性能// 使用 Chrome DevTools Protocolconst performance = await driver.getPerformanceMetrics();console.log('Performance metrics:', performance);// 获取网络日志const logs = await driver.manage().logs().get('performance');console.log('Network logs:', logs);跨浏览器测试1. 多浏览器测试// 测试多个浏览器const browsers = [ { browserName: 'Chrome', platformName: 'Android' }, { browserName: 'Safari', platformName: 'iOS' }];for (const browser of browsers) { const capabilities = { ...browser, deviceName: 'Test Device', platformVersion: 'latest' }; const driver = await new Builder() .withCapabilities(capabilities) .build(); try { await driver.get('https://example.com'); // 执行测试 } finally { await driver.quit(); }}2. 浏览器兼容性测试// 测试浏览器兼容性async function testBrowserCompatibility(driver, url) { await driver.get(url); // 检查元素是否正确显示 const element = await driver.findElement(By.id('main-content')); const isVisible = await element.isDisplayed(); assert(isVisible, 'Element should be visible'); // 检查样式是否正确 const backgroundColor = await element.getCssValue('background-color'); console.log('Background color:', backgroundColor); // 检查交互是否正常 const button = await driver.findElement(By.id('submit_button')); await button.click(); const result = await driver.findElement(By.id('result_message')); const text = await result.getText(); assert.strictEqual(text, 'Success');}移动端特定功能测试1. 触摸手势测试// 测试触摸手势async function testTouchGestures(driver) { // 测试点击 const element = await driver.findElement(By.id('button')); await element.click(); // 测试滑动 const size = await driver.manage().window().getRect(); await driver.touchActions([ { action: 'press', x: size.width / 2, y: size.height * 0.8 }, { action: 'moveTo', x: size.width / 2, y: size.height * 0.2 }, { action: 'release' } ]); // 测试长按 await driver.touchActions([ { action: 'press', x: size.width / 2, y: size.height / 2 }, { action: 'wait', ms: 2000 }, { action: 'release' } ]);}2. 设备方向测试// 测试设备方向async function testDeviceOrientation(driver) { // 测试竖屏 await driver.rotateScreen('PORTRAIT'); const portraitElement = await driver.findElement(By.css('.portrait-only')); const portraitVisible = await portraitElement.isDisplayed(); console.log('Portrait element visible:', portraitVisible); // 测试横屏 await driver.rotateScreen('LANDSCAPE'); const landscapeElement = await driver.findElement(By.css('.landscape-only')); const landscapeVisible = await landscapeElement.isDisplayed(); console.log('Landscape element visible:', landscapeVisible);}3. 网络状态测试// 测试网络状态async function testNetworkConditions(driver) { // 设置网络速度 await driver.setNetworkConditions({ offline: false, latency: 100, // 100ms 延迟 download_throughput: 500 * 1024, // 500KB/s upload_throughput: 500 * 1024 // 500KB/s }); // 测试页面加载 const startTime = Date.now(); await driver.get('https://example.com'); const loadTime = Date.now() - startTime; console.log('Load time with slow network:', loadTime); // 恢复正常网络 await driver.setNetworkConditions({ offline: false, latency: 0, download_throughput: -1, upload_throughput: -1 });}最佳实践1. 使用 Page Object 模式// Page Object 模式class MobileWebPage { constructor(driver) { this.driver = driver; } async open(url) { await this.driver.get(url); } async fillForm(data) { for (const [key, value] of Object.entries(data)) { const input = await this.driver.findElement(By.id(key)); await input.clear(); await input.sendKeys(value); } } async submit() { const button = await this.driver.findElement(By.id('submit_button')); await button.click(); } async getResult() { const result = await this.driver.findElement(By.id('result_message')); return await result.getText(); }}// 使用 Page Objectconst page = new MobileWebPage(driver);await page.open('https://example.com');await page.fillForm({ username: 'test', password: 'password' });await page.submit();const result = await page.getResult();2. 响应式测试// 响应式测试套件async function runResponsiveTests(driver) { const screenSizes = [ { name: 'Mobile', width: 375, height: 667 }, { name: 'Tablet', width: 768, height: 1024 }, { name: 'Desktop', width: 1920, height: 1080 } ]; for (const size of screenSizes) { console.log(`Testing on ${size.name} (${size.width}x${size.height})`); await driver.manage().window().setRect({ width: size.width, height: size.height }); // 执行测试 await testResponsiveLayout(driver); }}3. 性能监控// 性能监控async function monitorPerformance(driver) { const metrics = await driver.executeScript(` return { loadTime: window.performance.timing.loadEventEnd - window.performance.timing.navigationStart, domContentLoaded: window.performance.timing.domContentLoadedEventEnd - window.performance.timing.navigationStart, firstPaint: window.performance.timing.responseStart - window.performance.timing.navigationStart }; `); console.log('Performance metrics:', metrics); // 验证性能指标 assert(metrics.loadTime < 3000, 'Page load time should be less than 3 seconds'); assert(metrics.domContentLoaded < 1500, 'DOM content loaded should be less than 1.5 seconds');}常见问题1. 浏览器启动失败问题:无法启动浏览器解决方案:// 检查浏览器是否安装// Android: 检查 Chrome 是否安装// iOS: 检查 Safari 是否可用// 使用正确的 browserNameconst capabilities = { platformName: 'Android', browserName: 'Chrome', // 确保浏览器名称正确 deviceName: 'Pixel 5'};2. 元素定位失败问题:无法定位元素解决方案:// 等待元素加载const element = await driver.wait( until.elementLocated(By.id('submit_button')), 10000);// 检查元素是否在视口中await driver.executeScript('arguments[0].scrollIntoView(true);', element);// 使用更精确的定位策略const element = await driver.findElement(By.css('#submit_button.btn-primary'));3. 触摸操作不响应问题:触摸操作没有响应解决方案:// 使用 JavaScript 点击await driver.executeScript('arguments[0].click();', element);// 使用坐标点击const rect = await element.getRect();const x = rect.x + rect.width / 2;const y = rect.y + rect.height / 2;await driver.touchActions([{ action: 'tap', x: x, y: y }]);Appium 的移动 Web 测试为测试人员提供了强大的移动浏览器自动化能力,通过合理的测试策略和最佳实践,可以构建全面、可靠的移动 Web 测试。
阅读 0·2月21日 16:19

什么是 Astro 框架,它的核心特性和工作原理是什么?

Astro 是一个现代化的静态站点生成器,它的核心理念是"零 JavaScript 默认"。这意味着 Astro 默认只输出纯 HTML,不会向浏览器发送任何客户端 JavaScript,除非你明确要求。核心特性:岛屿架构(Islands Architecture):这是 Astro 的核心概念。页面上的每个组件都是一个"岛屿",默认情况下是静态的 HTML。只有当你明确使用 client:* 指令时,组件才会变成交互式的 JavaScript 岛屿。多框架支持:Astro 允许你在同一个项目中混合使用 React、Vue、Svelte、Preact、SolidJS 等前端框架。你可以在一个 Astro 组件中使用 React,在另一个组件中使用 Vue。性能优化:通过默认输出纯 HTML,Astro 网站具有极快的加载速度和优秀的 SEO 表现。内容优先:Astro 特别适合内容驱动的网站,如博客、文档、营销网站等。工作原理:---// 服务端代码区域(只在构建时运行)const data = await fetch('https://api.example.com/data');const posts = await data.json();---<!-- 客户端代码区域(输出到 HTML) --><h1>我的博客</h1><ul> {posts.map(post => ( <li>{post.title}</li> ))}</ul>Astro 使用 --- 分隔符将组件分为服务端代码和客户端模板。服务端代码在构建时执行,可以执行异步操作、获取数据等;客户端模板则被编译成 HTML。与其他框架的区别:与 Next.js 不同,Astro 默认不进行客户端水合(hydration),除非你明确要求与传统的静态站点生成器(如 Jekyll、Hugo)不同,Astro 支持现代前端框架和组件化开发Astro 的构建输出是纯 HTML,而不是包含大量 JavaScript 的应用这种设计使得 Astro 成为构建高性能、内容驱动网站的理想选择。
阅读 0·2月21日 16:15