Highlight
Xcode 13 为 XCTest UI 测试加入 iPadOS 指针、watchOS Digital Crown 和 macOS 触控板滚动自动化 API,让开发者可以验证过去依赖真实输入设备的交互路径。
核心内容
iPad 应用支持鼠标和触控板之后,很多交互只在指针环境下出现。比如,用户把光标悬停在按钮上会看到动画,用两指横向滑动触控板会打开侧边栏。以前这类行为很难放进 UI 测试,测试只能覆盖触摸路径,指针路径要靠人工点一遍。
Xcode 13 把 iPadOS 指针变成 XCTest 可以驱动的输入源。测试代码可以执行悬停、点击、右键、双击、两指滚动、按住修饰键,以及点击后拖拽。测试目标从“页面能打开”推进到“使用 Magic Keyboard 时页面也按预期响应”。
这个能力有一个现实问题:同一个 App 可能同时跑在 iPad 和 iPhone 上。iPhone 不支持指针交互。如果直接运行指针测试,测试会失败。Xcode 13 同时提供 supportsPointerInteraction,测试可以在不支持的设备上跳过,避免把环境限制误报成产品缺陷。
watchOS 也有类似痛点。很多手表应用把 Digital Crown(数码表冠)当成核心输入方式,例如滚动时间、调节数值、浏览列表。Xcode 12.5 已经支持 watchOS UI 测试和表冠点击,Xcode 13 补上表冠旋转。测试可以指定旋转圈数和速度,验证界面文本是否跟着变化。
macOS 的问题在滚动。鼠标滚轮是离散滚动,触控板是连续滚动。过去 XCTest 在 macOS 上已有按像素滚动的方法,适合模拟鼠标滚轮。Xcode 13 新增类似触控板的连续 swipe 方法,用来测试带惯性、带速度的滚动行为。
详细内容
iPadOS 指针支持检测
(01:28)Xcode 13 在 XCUIDevice 上加入 supportsPointerInteraction。它告诉测试当前设备是否支持指针交互。
extension XCUIDevice {
public var supportsPointerInteraction: Bool
}
关键点:
XCUIDevice表示当前 UI 测试运行的设备。supportsPointerInteraction返回布尔值。- iPad 指针测试可以先检查这个值,再决定运行或跳过。
(04:36)完整测试里,Apple 用 XCTSkipUnless 跳过不支持指针交互的设备。
import XCTest
class Pointer_UI_Tests: XCTestCase {
@available(iOS 15.0, *)
func testHorizontalScrollRevealsSidebar() throws {
try XCTSkipUnless(XCUIDevice.shared.supportsPointerInteraction,
"Device does not support pointer interaction")
let app = XCUIApplication()
app.launch()
let sidebar = app.tables["Sidebar"]
// Make sure sidebar menu is not present initially.
XCTAssertFalse(sidebar.exists, "Sidebar should not be present initially")
// Swipe horizontally to reveal sidebar.
app.staticTexts["Select a smoothie"].scroll(byDeltaX: -200, deltaY: 0)
// Verify sidebar is now present.
XCTAssertTrue(sidebar.waitForExistence(timeout: 5),
"Sidebar did not appear within 5 second timeout")
}
}
关键点:
@available(iOS 15.0, *)标记这段测试只使用 iOS 15 之后可用的 API。XCTSkipUnless在条件不满足时跳过测试。XCUIDevice.shared.supportsPointerInteraction过滤掉不支持指针交互的设备。XCUIApplication()创建被测应用对象。app.launch()启动被测应用。app.tables["Sidebar"]找到无障碍标识或名称为Sidebar的表格。XCTAssertFalse(sidebar.exists, ...)验证侧边栏启动时不存在。app.staticTexts["Select a smoothie"].scroll(byDeltaX: -200, deltaY: 0)在指定文本元素上执行横向两指滚动。waitForExistence(timeout: 5)最多等待 5 秒,验证侧边栏出现。
iPadOS 指针交互方法
(01:37)Xcode 13 在 XCUIElement 上加入一组指针方法。演讲还提到,这些方法也可以用在 XCUICoordinate,需要更精确坐标时可以使用坐标对象。
extension XCUIElement {
open func hover()
open func click()
open func rightClick()
open func doubleClick()
open func scroll(byDeltaX: CGFloat, deltaY: CGFloat)
open func click(forDuration: TimeInterval, thenDragToElement: XCUIElement)
open func click(forDuration: TimeInterval, thenDragToElement: XCUIElement,
withVelocity: XCUIGestureVelocity, thenHoldForDuration: TimeInterval)
open class func perform(withKeyModifiers flags: XCUIElement.KeyModifiers,
block: () -> Void)
}
关键点:
hover()把指针移动到元素上,用来测试悬停动画或悬停状态。click()执行普通点击。rightClick()执行右键点击。doubleClick()执行双击。scroll(byDeltaX:deltaY:)执行两指滚动,deltaX控制水平距离,deltaY控制垂直距离。click(forDuration:thenDragToElement:)先按住,再拖到另一个元素。- 带
withVelocity和thenHoldForDuration的重载可以指定拖拽速度和拖拽结束后的停留时间。 perform(withKeyModifiers:block:)在一个代码块内按住修饰键执行操作。
watchOS Digital Crown 旋转
(05:27)Xcode 13 在 XCUIDevice 上加入 rotateDigitalCrown。它接收旋转圈数,并可以指定速度。
// New rotateDigitalCrown API
extension XCUIDevice {
open func rotateDigitalCrown(delta: CGFloat, velocity: XCUIGestureVelocity = .default)
}
关键点:
rotateDigitalCrown(delta:velocity:)合成 Digital Crown 旋转事件。delta表示旋转圈数。- 正数表示向前旋转,负数表示向后旋转。
velocity类型是XCUIGestureVelocity。- 速度可以使用
.slow、.fast、.default预设,也可以使用自定义小数值,单位是每秒旋转圈数。
(06:22)演讲用一个天气 App 说明测试方式。表冠向前滚动查看未来温度,向后滚动查看过去温度。
// Example use of watchOS Digital Crown API
func testForecastScrolling() {
let app = XCUIApplication()
app.launch()
let forecastTime = app.staticTexts["forecast-time"]
XCTAssertEqual(forecastTime.label, "Current Temperature")
// Scroll 1 full rotation forward.
XCUIDevice.shared.rotateDigitalCrown(delta: 1.0)
XCTAssertEqual(forecastTime.label, "One hour from now")
// Scroll 2 full rotations backward.
XCUIDevice.shared.rotateDigitalCrown(delta: -2.0)
XCTAssertEqual(forecastTime.label, "One hour ago")
}
关键点:
XCUIApplication()创建 watchOS 被测应用对象。app.launch()启动应用。app.staticTexts["forecast-time"]找到显示预测时间的文本。- 第一个
XCTAssertEqual验证初始状态是当前温度。 rotateDigitalCrown(delta: 1.0)向前旋转一整圈。- 第二个
XCTAssertEqual验证界面更新到一小时后。 rotateDigitalCrown(delta: -2.0)向后旋转两整圈。- 最后一个
XCTAssertEqual验证界面更新到一小时前。
macOS 离散滚动和连续滚动
(07:46)macOS 上原有的 scroll(byDeltaX:deltaY:) 适合离散滚动。演讲把它类比为鼠标滚轮:每一格移动固定距离,停止输入后内容立刻停止。
extension XCUIElement {
// Use the existing API for discrete (mouse wheel-like) scrolling.
open func scroll(byDeltaX: CGFloat, deltaY: CGFloat)
}
关键点:
scroll(byDeltaX:deltaY:)是已有 API。deltaX指定水平滚动距离。deltaY指定垂直滚动距离。- 这个方法用于像素级、非惯性的滚动测试。
(08:05)Xcode 13 新增连续滚动方法,适合模拟触控板两指滑动。连续滚动有速度和惯性,手指离开后内容会逐渐停下。
extension XCUIElement {
// Use the new API for continuous (trackpad-like) scrolling.
open func swipeUp(velocity: XCUIGestureVelocity = .default)
open func swipeDown(velocity: XCUIGestureVelocity = .default)
open func swipeLeft(velocity: XCUIGestureVelocity = .default)
open func swipeRight(velocity: XCUIGestureVelocity = .default)
}
关键点:
swipeUp(velocity:)合成向上滑动。swipeDown(velocity:)合成向下滑动。swipeLeft(velocity:)合成向左滑动。swipeRight(velocity:)合成向右滑动。velocity类型是XCUIGestureVelocity。- 速度可以使用预设值,也可以使用自定义小数值,单位是每秒像素。
核心启发
-
做什么:给 iPad 侧边栏、分栏导航、画布应用补指针测试。 为什么值得做:Xcode 13 可以在 UI 测试中执行两指滚动、悬停和点击,指针专属路径不用再靠人工验收。 怎么开始:在 UI 测试里用
XCTSkipUnless(XCUIDevice.shared.supportsPointerInteraction, ...)保护测试,再对目标元素调用scroll(byDeltaX:deltaY:)或hover()。 -
做什么:为 iPad 右键菜单写回归测试。 为什么值得做:指针 API 支持
rightClick(),可以覆盖上下文菜单是否出现、菜单项是否可点。 怎么开始:找到触发菜单的XCUIElement,调用rightClick(),再断言菜单中的按钮或文本存在。 -
做什么:给 watchOS 表冠调节界面写边界测试。 为什么值得做:
rotateDigitalCrown(delta:velocity:)可以指定旋转方向和圈数,适合测试时间轴、音量、温度、计时器这类表冠驱动界面。 怎么开始:在 watchOS UI 测试里启动 App,读取当前标签,调用XCUIDevice.shared.rotateDigitalCrown(delta:),再检查标签或数值变化。 -
做什么:给 macOS 长列表或时间线补触控板滚动测试。 为什么值得做:新的
swipeUp、swipeDown、swipeLeft、swipeRight更接近触控板输入,可以覆盖惯性滚动下的加载和选择行为。 怎么开始:选中滚动容器元素,调用swipeUp(velocity:)或swipeDown(velocity:),检查目标内容是否出现。 -
做什么:测试带修饰键的指针操作。 为什么值得做:
perform(withKeyModifiers:block:)可以在 UI 测试中按住修饰键执行点击或拖拽,适合文件选择、范围选择、画布编辑器。 怎么开始:把click()或拖拽操作放进XCUIElement.perform(withKeyModifiers:block:)的闭包中,再断言选择状态变化。
关联 Session
- Embrace Expected Failures in XCTest — 讲
XCTExpectFailure,适合和本场一起改进 UI 测试的失败信号。 - Diagnose unreliable code with test repetitions — 讲测试重复运行,适合定位指针、滚动、表冠测试中的偶发失败。
- Detect and diagnose memory issues — 讲 Xcode 内存诊断和 XCTest 采集,属于同一组测试工具能力。
- Ultimate application performance survival guide — 讲性能指标、MetricKit 和 XCTest,适合扩展 UI 自动化之外的质量检查。
评论
GitHub Issues · utterances