Skip to content

Commit 76246a4

Browse files
committed
Fix sibling switch redirection for value queries and extract duplicated polling logic
- Only apply sibling switch redirection for label-based queries, not value or id queries - Extract polling retry logic into shared AccessibilityPoller.pollForResolution function - Fixes test assertion failure in elementsWithSameTypeAndFrameButDifferentRolesAreDistinct - Eliminates code duplication between batch and direct tap polling paths
1 parent 681ac45 commit 76246a4

3 files changed

Lines changed: 46 additions & 27 deletions

File tree

Sources/AXe/Utilities/AccessibilityPoller.swift

Lines changed: 21 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -10,7 +10,26 @@ struct AccessibilityPoller {
1010
elementType: String? = nil,
1111
logger: AxeLogger
1212
) async throws -> TapResolution {
13-
let roots = try await AccessibilityFetcher.fetchAccessibilityElements(for: simulatorUDID, logger: logger)
13+
try await pollForResolution(
14+
query: query,
15+
waitTimeout: waitTimeout,
16+
pollInterval: pollInterval,
17+
elementType: elementType,
18+
logger: logger
19+
) {
20+
try await AccessibilityFetcher.fetchAccessibilityElements(for: simulatorUDID, logger: logger)
21+
}
22+
}
23+
24+
static func pollForResolution(
25+
query: AccessibilityQuery,
26+
waitTimeout: TimeInterval,
27+
pollInterval: TimeInterval,
28+
elementType: String?,
29+
logger: AxeLogger,
30+
rootsFetcher: () async throws -> [AccessibilityElement]
31+
) async throws -> TapResolution {
32+
let roots = try await rootsFetcher()
1433
do {
1534
return try AccessibilityTargetResolver.resolveTap(roots: roots, query: query, elementType: elementType)
1635
} catch let error as ElementResolutionError where error.isNotFound && waitTimeout > 0 {
@@ -22,7 +41,7 @@ struct AccessibilityPoller {
2241
logger.info().log("Element not found, retrying in \(pollInterval)s…")
2342
try await Task.sleep(for: .seconds(pollInterval))
2443

25-
let freshRoots = try await AccessibilityFetcher.fetchAccessibilityElements(for: simulatorUDID, logger: logger)
44+
let freshRoots = try await rootsFetcher()
2645
do {
2746
return try AccessibilityTargetResolver.resolveTap(roots: freshRoots, query: query, elementType: elementType)
2847
} catch let retryError as ElementResolutionError where retryError.isNotFound {

Sources/AXe/Utilities/AccessibilityTargetResolver.swift

Lines changed: 14 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -4,6 +4,15 @@ enum AccessibilityQuery {
44
case id(String)
55
case label(String)
66
case value(String)
7+
8+
var allowsSiblingRedirection: Bool {
9+
switch self {
10+
case .label:
11+
return true
12+
case .id, .value:
13+
return false
14+
}
15+
}
716
}
817

918
enum ElementResolutionError: LocalizedError {
@@ -81,7 +90,8 @@ struct AccessibilityTargetResolver {
8190
let activationElement = try selectActivationElement(
8291
from: matchedElement,
8392
roots: roots,
84-
selectorDescription: selectorDescription
93+
selectorDescription: selectorDescription,
94+
allowSiblingRedirection: query.allowsSiblingRedirection
8595
)
8696

8797
guard let frame = activationElement.frame else {
@@ -157,7 +167,8 @@ struct AccessibilityTargetResolver {
157167
private static func selectActivationElement(
158168
from matchedElement: AccessibilityElement,
159169
roots: [AccessibilityElement],
160-
selectorDescription: String
170+
selectorDescription: String,
171+
allowSiblingRedirection: Bool
161172
) throws -> AccessibilityElement {
162173
if matchedElement.isSwitchLikeControl {
163174
return matchedElement
@@ -178,7 +189,7 @@ struct AccessibilityTargetResolver {
178189
return matchedElement
179190
}
180191

181-
if let ancestor = nearestAncestor(of: matchedElement, in: roots) {
192+
if allowSiblingRedirection, let ancestor = nearestAncestor(of: matchedElement, in: roots) {
182193
let siblingSwitches = ancestor.switchLikeDescendantsIncludingSelf()
183194
if siblingSwitches.count == 1 {
184195
return siblingSwitches[0]

Sources/AXe/Utilities/Batch/Command+BatchConvertible.swift

Lines changed: 11 additions & 22 deletions
Original file line numberDiff line numberDiff line change
@@ -29,28 +29,17 @@ private func resolveBatchTapPoint(
2929
elementType: String?,
3030
logger: AxeLogger
3131
) async throws -> TapResolution {
32-
let roots = try await context.accessibilityRoots(logger: logger)
33-
do {
34-
return try AccessibilityTargetResolver.resolveTap(roots: roots, query: query, elementType: elementType)
35-
} catch let error as ElementResolutionError where error.isNotFound && context.waitTimeout > 0 {
36-
let clock = ContinuousClock()
37-
let deadline = clock.now + .seconds(context.waitTimeout)
38-
39-
var lastError = error
40-
while clock.now < deadline {
41-
logger.info().log("Element not found, retrying in \(context.pollInterval)s…")
42-
try await Task.sleep(for: .seconds(context.pollInterval))
43-
44-
let freshRoots = try await context.accessibilityRoots(logger: logger, forceRefresh: true)
45-
do {
46-
return try AccessibilityTargetResolver.resolveTap(roots: freshRoots, query: query, elementType: elementType)
47-
} catch let retryError as ElementResolutionError where retryError.isNotFound {
48-
lastError = retryError
49-
continue
50-
}
51-
}
52-
53-
throw lastError
32+
var isFirstFetch = true
33+
return try await AccessibilityPoller.pollForResolution(
34+
query: query,
35+
waitTimeout: context.waitTimeout,
36+
pollInterval: context.pollInterval,
37+
elementType: elementType,
38+
logger: logger
39+
) {
40+
let forceRefresh = !isFirstFetch
41+
isFirstFetch = false
42+
return try await context.accessibilityRoots(logger: logger, forceRefresh: forceRefresh)
5443
}
5544
}
5645

0 commit comments

Comments
 (0)