-
Notifications
You must be signed in to change notification settings - Fork 913
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Adding support for accessibility custom actions #1295
Conversation
c9d359f
to
5c25cc7
Compare
Sources/KIF/Additions/UIAccessibilityCustomAction+KIFAdditions.m
Outdated
Show resolved
Hide resolved
@justinseanmartin What do you think of this proposed API? We're going to start relying on Custom Actions much more frequently in Market and im expecting I'll need this capability a fair amount to update our existing tests. |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Mostly NITs or questions, but I think there probably are some minor changes to be made at least for prefixing the method in the category, the autoreleasepool and potentially the respondsToSelector check.
As to the question about the API. overall it seems good to me. I'm not sure if there is a scenario where we'll want to use usingCustomActionWithName:
directly, as opposed to it being an implicit part of the action. I'd consider removing that from the public header if there isn't a scenario you're thinking of where we'll need to use that. I don't have a strong feeling on that though. It just seems kind of weird to have a [[viewTester usingCustomActionWithName:@"action"] waitForAppearance]
or maybe even wierder [[viewTester usingCustomActionWithName:@"action"] tap]
. We also don't have any tests exercising usingCustomActionWithName
directly, which feels potentially like another indicator that we might not need that exposed?
- (instancetype)usingCustomActionWithName:(NSString *)name | ||
{ | ||
NSPredicate *predicate = [NSPredicate predicateWithBlock:^BOOL(id evaluatedObject, NSDictionary *bindings) { | ||
NSArray *actions = [evaluatedObject accessibilityCustomActions]; |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Every element that we iterate over in the view & accessibility element hierarchy is guaranteed to respond to this selector? Or should we add a respondsToSelector:
check?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
This is a property on NSObject defined thusly in UIAccessibility.h
@property (nullable, nonatomic, strong) NSArray <UIAccessibilityCustomAction *> *accessibilityCustomActions API_AVAILABLE(ios(8.0));
Though a respondsToSelector: check couldn't hurt.
for (UIAccessibilityCustomAction *action in actions) { | ||
NSString *actionName = [action name]; | ||
if ([actionName isKindOfClass:[NSAttributedString class]]) { | ||
actionName = [(NSAttributedString *)actionName string]; | ||
} | ||
|
||
if ([actionName isEqualToString: name]) { | ||
return true; | ||
} | ||
} |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
NIT: Up to you, but maybe worth a helper method for getting the specified action by name? Its identical here and below and might make these methods slightly simpler.
@@ -385,6 +406,31 @@ - (void)swipeFromEdge:(UIRectEdge)edge | |||
[self.actor swipeFromEdge:edge]; | |||
} | |||
|
|||
- (void)activateCustomActionWithName:(NSString *)name; | |||
{ | |||
KIFUIObject *found = [[self usingCustomActionWithName:name] _predicateSearchWithRequiresMatch:YES mustBeTappable:NO]; |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
We generally wrap the body of these methods that return a KIFUIObject
in an @autoreleasepool
block so as to not accidentally extend the lifetime of the view object beyond this action.
|
||
@interface UIAccessibilityCustomAction (KIFAdditions) | ||
|
||
- (BOOL)activate; |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Use a prefix to avoid collisions, e.g. KIF_activate
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
oh man, i honestly do love objective c
[invocation getReturnValue:&returnValue]; | ||
return returnValue; | ||
} | ||
return NO; |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Would it make more sense to raise if we attempted to perform an action on an element that says it supports it, but hasn't implemented the selector? I'd kind of expect this to crash or raise an error in some way that is distinct from the selector returning false from the action.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Ohh, nice idea
} | ||
|
||
if ([actionName isEqualToString: name]) { | ||
if([action activate]) { |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
How do VoiceOver and other accessibility tools respond if a custom action returns false? Are there valid use cases where a custom action might want to return false and that be a valid test scenario rather than treating it as a test failure? We don't need to extend to support that right now, but it seemed worth some thought/discussion.
Is it reasonable that a custom action might fail in some cases? Or is the expectation that custom actions should generally only be present on an element if they're expected to be able to be performed? Looking at the Firefox iOS codebase as an example, it seems like they filter the actions to ones that are expected to be usable in the current context, and then always return true. That's just one example though.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
theres no official dogma on this particular question, but as we interpret the default behavior of UIKit and SwiftUI as guidance it would suggest that custom actions that are known to fail should be omitted entirely. Disabled buttons for example.
From the point of view of the person in front of the device, a failed action will play an additional "error" sound when activated.
You're right, we should consider the case where an action is expected to occasionally fail and allow for testing that assumption.
I've added an "expectedResult boolean" to allow this.
7903f6f
to
fb7f89f
Compare
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Looks great!
fb7f89f
to
4675a70
Compare
* silencing non-exhaustive switch warning (kif-framework#1297) silencing non-exhaustive switch warning * Adding support for accessibility custom actions (kif-framework#1295) * adding support for accessibility custom actions * introduce tryFindingViewInFrame - avoid scroll when looking for a view visibility * remove wrong comment * improve comments * add missing disableScroll argument * introduce usingCurrentFrame() method on KIFUIViewTestActor to disable automatic scroll if needed * fixes: - fix wrong parameter name in comments - add missing methods - add missing things that prevent us to build for tests * fix to scrollView behavior: - with scrollDisabled = YES it now consider only views visible in the current frame, as it was done with TableView and CollectionViews - make explicit the scrollDisabled argument in recursive calls = NO when we are inside the scrolling section * add new integration tests for tryFindingViewInFrameWithAccessibilityIdentifier() and usingCurrentFrame() * Fix bug that was considering an occluded view as tappable * simplify boolean check and add integration test for broken case * Revert "simplify boolean check and add integration test for broken case" This reverts commit 6827225. * simplify boolean logic of the tappable check * add missing fromRootView argument Co-authored-by: Justin Martin <[email protected]> * Change duplicated accessibilityLabel that was causing flakiness for 2 tests --------- Co-authored-by: Alex Odawa <[email protected]> Co-authored-by: Simone Scionti <[email protected]> Co-authored-by: Justin Martin <[email protected]>
Custom actions allow developers to provide custom interactions that can be initiated through assistive technologies like VoiceOver and Switch Control. For example, VoiceOver lets users access actions quickly using the Actions rotor.
Custom actions are relied on heavily by SwiftUI's
.accessibilityElement(children:.combine)
view modifier.This PR introduces two methods on KIFUIViewTestActor to enable validation and activation of an element's custom actions within KIF tests.
- (instancetype)usingCustomActionWithName:(NSString *)name;
adds a predicate that searches elements for one that provides a custom action with a specified name.- (void)activateCustomActionWithName:(NSString *)name;
first performs a search for an element that provides a custom action with a specified name and then activates the action.