无法为 UI 测试调整 datepicker 的值:类型 6 的 PickerWheel
Posted
技术标签:
【中文标题】无法为 UI 测试调整 datepicker 的值:类型 6 的 PickerWheel【英文标题】:Unable to adjust the value of datepicker for UI testing: PickerWheel of type 6 【发布时间】:2020-10-22 01:32:44 【问题描述】:我们正在使用 XCTest 框架为我们的 ios 应用程序编写 UI 自动化,我们必须使用 datepicker 选择日期。 在 iOS 13 中,我们使用 adjust() 方法来设置 datepicker 的值。 但是在 iOS 14 中,当我们尝试为 datepicker 设置值时,如下所示
element.adjust(toPickerWheelValue: "September")
我收到以下错误
Unsupported picker wheel "2020" PickerWheel of type 6
其他人已在https://developer.apple.com/forums/thread/661809 发布了类似问题。
有人遇到过这个问题吗?
【问题讨论】:
【参考方案1】:从 Xcode 12.1 开始,似乎仍然没有支持更改新日期选择器轮的方法。
我想出了这个方法来解决这个问题:
func adjustDatePicker(wheel: XCUIElement, to newValue: String) -> Bool
let x = wheel.frame.width / 2.0
let y = wheel.frame.height / 2.0
// each wheel notch is about 30px high, so tapping y - 30 rotates up. y + 30 rotates down.
var offset: CGFloat = -30.0
var reversed = false
let previousValue = wheel.value as? String
while wheel.value as? String != newValue
wheel.coordinate(withNormalizedOffset: .zero).withOffset(CGVector(dx: x, dy: y + offset)).tap()
let briefWait = expectation(description: "Wait for wheel to rotate")
briefWait.isInverted = true
wait(for: [briefWait], timeout: 0.25)
if previousValue == wheel.value as? String
if reversed
// we already tried reversing, can't find the desired value
break
// we didn't move the wheel. try reversing direction
offset = 30.0
reversed = true
return wheel.value as? String == newValue
【讨论】:
接受这个答案,因为我用它作为参考来实现我自己的解决方案,并为多个值旋转***。 分享一下你的实际解决方案怎么样? 我在下面发布了解决方案。【参考方案2】:import Foundation
import XCTest
struct ScrollParameters
let halfX: CGFloat
let dy: CGFloat
let tapCount: Int
// how many notches to rotate with one tap
let notchCountToRotate: Int
// To rotate n notches with one tap, we have to multiply notch height with nth value in following array
// e.g. To rotate 3 notches, multiply notch height with 2.7
static let reverseScrollingOffset:[CGFloat] = [0, 1, 2, 2.7]
static let forwardScrollingOffset:[CGFloat] = [0, 1, 2, 2.5]
// each wheel notch is about 30px high, so tapping y - 30 rotates up. y + 30 rotates down.
static let wheelNotchHeight:CGFloat = 30.0
static let maxAttempts = 50
/// Given the number of notches to be rotated with one tap, this function determines how many taps are required in order to minimize the difference between current picker value and the value to be set.
private func calculateTapCount(forZeroing difference: Int, withNotchRotationCount notchRotationCount: Int) -> Int
if difference == 0
return 0
let positiveDiff = difference < 0 ? (difference * -1) : difference
return positiveDiff / notchRotationCount
private func scroll(_ wheel: XCUIElement, with scrollParams: ScrollParameters)
var currentTapCount = 0
while currentTapCount < scrollParams.tapCount
wheel.coordinate(withNormalizedOffset: .zero)
.withOffset(CGVector(dx: scrollParams.halfX, dy: scrollParams.dy))
.tap()
currentTapCount += 1
/// Determines number of notches to be rotated with one tap.
private func getNotchRotationCountWithOneTap(_ difference: Int) -> Int
if difference == 0
return 0
if (difference >= 3) || (difference <= -3)
return 3
return (difference % 2 == 0) ? 2 : 1
/// Returns the y-axis offset required for rotating n notches of pickerwheel with 1 tap.
/// The offset values are stored in static arrays in ScrollParameters.
/// If we want to rotate the wheel in reverse direction, we have to subtract the offset from center coordinate.
/// Hence, offset value is multiplied by -1.
private func getTappingOffset(_ difference: Int, _ notchRotationCountForOneTap: Int) -> CGFloat
if difference > 0
return ScrollParameters.reverseScrollingOffset[notchRotationCountForOneTap] * (-1.0)
else
return ScrollParameters.forwardScrollingOffset[notchRotationCountForOneTap]
/// Returns the metadata for rotating pickerwheel notches.
private func getRotationMetadata(for difference: Int, with wheel:XCUIElement) -> ScrollParameters
let notchRotationCountForOneTap = getNotchRotationCountWithOneTap(difference) // 0, 1, 2, 3
let tapCount = calculateTapCount(forZeroing: difference, withNotchRotationCount: notchRotationCountForOneTap)
let halfX:CGFloat = wheel.frame.width / 2.0
let halfY:CGFloat = wheel.frame.height / 2.0
let dy:CGFloat = halfY + (ScrollParameters.wheelNotchHeight * getTappingOffset(difference, notchRotationCountForOneTap))
return ScrollParameters(halfX: halfX, dy: dy, tapCount: tapCount, notchCountToRotate: notchRotationCountForOneTap)
private func getYearWheelValue(_ wheel: XCUIElement) -> Int
sleep(for: 250) //will sleep for 250 milliseconds (.025 seconds)
return Int(wheel.value as! String)!
private func setYear(_ wheel: XCUIElement, _ yearToSet: Int)
var pickerWheelValue = getYearWheelValue(wheel)
var counter = 0
repeat
let diff = pickerWheelValue - yearToSet
let scrollParams = getRotationMetadata(for: diff, with: wheel)
scroll(wheel, with: scrollParams)
pickerWheelValue = getYearWheelValue(wheel)
counter += 1
while (pickerWheelValue != yearToSet) && (counter < ScrollParameters.maxAttempts)
private func getMonthWheelIndex(_ wheel: XCUIElement) -> Int
sleep(for: 250)
let pickerValue = wheel.value as! String
let monthNames:[String] = DateFormatter().monthSymbols
return monthNames.firstIndex(of: pickerValue)!
private func selectMonth(_ wheel: XCUIElement, _ valueToSet: Int)
let expectedValueIndex = valueToSet - 1
var pickerValueIndex: Int = getMonthWheelIndex(wheel)
var counter = 0
repeat
let diff = pickerValueIndex - expectedValueIndex
let scrollParams = getRotationMetadata(for: diff, with: wheel)
scroll(wheel, with: scrollParams)
pickerValueIndex = getMonthWheelIndex(wheel)
counter += 1
while (pickerValueIndex != expectedValueIndex) && (counter < ScrollParameters.maxAttempts)
private func getButtonForDay(with predicateForDayButton: NSPredicate) -> XCUIElement?
let datepicker = GlobalTestContext.shared.app?.datePickers.element(boundBy: 1)
let buttons = datepicker?.buttons.allElementsBoundByIndex
for button in buttons!
let query = button.descendants(matching: .staticText).matching(predicateForDayButton)
if query.count == 1
return button
XCTAssertFalse(true, "No matching day found in the datepicker")
return nil
private func selectDay(_ day: Int)
let predicateForDayButton = NSPredicate(format: "label == %@", String(describing: day))
let dayButton = getButtonForDay(with: predicateForDayButton)
dayButton?.tap()
private func dismissDatePicker(_ app: XCUIApplication)
let start = app.coordinate(withNormalizedOffset: CGVector(dx: 1.0, dy: 0.8))
// tapping bottom right corner to dismiss the datepicker
let end = app.coordinate(withNormalizedOffset: CGVector(dx: 1.0, dy: 1.0))
start.press(forDuration: 0.1, thenDragTo: end)
func isNewDatePicker(_ datePicker: XCUIElement) -> Bool
return datePicker.pickerWheels.count == 0
// entry point
func setDateInNewDatepicker(_ datePicker: XCUIElement, _ dateComponents: DateComponents) -> Any
let app = XCUIApplication()
app.descendants(matching: .other).matching(identifier: "Date Picker").element.tap()
app.descendants(matching: .button).matching(identifier: "Show year picker").element.tap()
let yearPicker = (app.descendants(matching: .pickerWheel).element(boundBy: 1))
setYear(yearPicker, dateComponents.year!)
let monthPicker = (app.descendants(matching: .pickerWheel).element(boundBy: 0))
selectMonth(monthPicker, dateComponents.month!)
app.descendants(matching: .button).matching(identifier: "Hide year picker").element.tap()
selectDay(dateComponents.day!)
dismissDatePicker(app)
return datePicker
【讨论】:
以上是关于无法为 UI 测试调整 datepicker 的值:类型 6 的 PickerWheel的主要内容,如果未能解决你的问题,请参考以下文章
React 中 Material UI DatePicker 中的最小和最大日期
如果超出范围,防止 jQuery UI datepicker 更改文本字段的值
在 Xcode 8.3.2 的 UI 测试用例中转换为当前的快速语法“无法调用非函数类型‘XCUIElement’的值”时出错
转换器无法在 windows phone 8.1 Datepicker 中将类型“system.datetime”的值转换为类型“datetime”