Index: ios/web/public/test/earl_grey/web_view_actions.mm |
diff --git a/ios/web/public/test/earl_grey/web_view_actions.mm b/ios/web/public/test/earl_grey/web_view_actions.mm |
new file mode 100644 |
index 0000000000000000000000000000000000000000..fa043725493edeabcd4959cedb53b89915c9849b |
--- /dev/null |
+++ b/ios/web/public/test/earl_grey/web_view_actions.mm |
@@ -0,0 +1,205 @@ |
+// Copyright 2016 The Chromium Authors. All rights reserved. |
+// Use of this source code is governed by a BSD-style license that can be |
+// found in the LICENSE file. |
+ |
+#import "ios/web/public/test/earl_grey/web_view_actions.h" |
+ |
+#include "base/callback_helpers.h" |
+#include "base/logging.h" |
+#include "base/mac/bind_objc_block.h" |
+#include "base/strings/stringprintf.h" |
+#include "base/test/ios/wait_util.h" |
+#include "base/values.h" |
+#include "ios/testing/earl_grey/wait_util.h" |
Eugene But (OOO till 7-30)
2016/08/30 18:11:05
s/include/import
marq (ping after 24h)
2016/08/31 13:07:58
Done.
|
+#import "ios/web/public/test/earl_grey/web_view_matchers.h" |
+#import "ios/web/public/test/web_view_interaction_test_util.h" |
+#include "ios/web/web_state/web_state_impl.h" |
Eugene But (OOO till 7-30)
2016/08/30 18:11:05
s/include/import
marq (ping after 24h)
2016/08/31 13:07:57
Done.
|
+ |
+using web::test::ExecuteScript; |
+ |
+namespace { |
+ |
+// Long press duration to trigger context menu. |
+const NSTimeInterval kContextMenuLongPressDuration = 0.3; |
+ |
+// Callback prefix for injected verifiers. |
+const std::string CallbackPrefixForElementId(const std::string& element_id) { |
+ return "__web_test_" + element_id + "_interaction"; |
+} |
+ |
+// Generic verification injector. Injects one-time mousedown verification into |
+// |web_state| that will set the boolean pointed to by |verified| to true when |
+// |web_state|'s webview registers the mousedown event. |
+// RemoveVerifierForElementWithId() should be called after this to ensure |
+// future tests can add verifiers with the same prefix. |
+bool AddVerifierToElementWithId(web::WebState* web_state, |
+ const std::string& element_id, |
+ bool* verified) { |
+ const std::string kCallbackPrefix = CallbackPrefixForElementId(element_id); |
+ const char kCallbackCommand[] = "verified"; |
+ const std::string kCallbackInvocation = |
+ kCallbackPrefix + '.' + kCallbackCommand; |
+ |
+ const char kAddInteractionVerifierScriptTemplate[] = |
+ "(function() {" |
+ // First template param: element ID. |
+ " var elementId = '%1$s';" |
+ " var element = document.getElementById(elementId);" |
+ " if (!element)" |
+ " return 'Element ' + elementId + ' not found';" |
Eugene But (OOO till 7-30)
2016/08/30 18:11:05
WKWebView allows returning objects. Do you want to
marq (ping after 24h)
2016/08/31 13:07:58
Other code does that, but it's more work to parse;
|
+ " var invokeType = typeof __gCrWeb.message;" |
+ " if (invokeType != 'object')" |
+ " return 'Host invocation not installed (' + invokeType + ')';" |
+ " var options = {'capture' : true, 'once' : true, 'passive' : true};" |
+ " element.addEventListener('mousedown', function(event) {" |
+ " __gCrWeb.message.invokeOnHost(" |
+ // Second template param: callback command. |
+ " {'command' : '%2$s' });" |
+ " }, options);" |
+ " return true;" |
+ "})();"; |
+ |
+ const std::string kAddVerifierScript = |
+ base::StringPrintf(kAddInteractionVerifierScriptTemplate, |
+ element_id.c_str(), kCallbackInvocation.c_str()); |
+ NSDate* deadline = |
+ [NSDate dateWithTimeIntervalSinceNow:testing::kWaitForUIElementTimeout]; |
+ bool verifier_added = false; |
+ while (([[NSDate date] compare:deadline] != NSOrderedDescending) && |
+ !verifier_added) { |
+ std::unique_ptr<base::Value> value = |
+ web::test::ExecuteScript(web_state, kAddVerifierScript); |
+ if (value) { |
+ std::string error; |
+ if (value->GetAsString(&error)) { |
+ DLOG(ERROR) << "Verifier injection failed: " << error << ", retrying."; |
+ } else if (value->GetAsBoolean(&verifier_added)) { |
+ verifier_added = true; |
+ } |
+ } |
+ base::test::ios::SpinRunLoopWithMaxDelay( |
+ base::TimeDelta::FromSecondsD(testing::kSpinDelaySeconds)); |
+ } |
+ |
+ if (!verifier_added) |
+ return false; |
+ |
+ // The callback doesn't care about any of the parameters, just whether it is |
+ // called or not. |
+ auto callback = base::BindBlock(^bool(const base::DictionaryValue& JSON, |
Eugene But (OOO till 7-30)
2016/08/30 18:11:05
s/JSON/json
Also origin_url and user_is_interacti
Eugene But (OOO till 7-30)
2016/08/30 18:11:05
NIT: Do you want to comment-out first param as wel
marq (ping after 24h)
2016/08/31 13:07:58
Done.
marq (ping after 24h)
2016/08/31 13:07:58
Yes, I uncommented it during debugging.
|
+ const GURL& /* originURL */, |
+ bool /* userIsInteracting */) { |
+ *verified = true; |
+ return true; |
+ }); |
+ |
+ static_cast<web::WebStateImpl*>(web_state)->AddScriptCommandCallback( |
+ callback, kCallbackPrefix); |
+ return true; |
+} |
+ |
+// Remove the injected callback. |
Eugene But (OOO till 7-30)
2016/08/30 18:11:05
s/Remove/Removes
marq (ping after 24h)
2016/08/31 13:07:58
Done.
|
+void RemoveVerifierForElementWithId(web::WebState* web_state, |
+ const std::string& element_id) { |
+ static_cast<web::WebStateImpl*>(web_state)->RemoveScriptCommandCallback( |
+ CallbackPrefixForElementId(element_id)); |
+} |
+ |
+} // namespace |
+ |
+namespace web { |
+ |
+id<GREYAction> webViewVerifiedActionOnElement(WebState* state, |
+ id<GREYAction> action, |
+ std::string element_id) { |
+ NSString* action_name = |
+ [NSString stringWithFormat:@"Verified action (%@) on webview element %s.", |
+ action.name, element_id.c_str()]; |
+ |
+ GREYPerformBlock verified_tap = ^BOOL(id element, __strong NSError** error) { |
+ // A pointer to |verified| is passed into AddVerifierToElementWithId() so |
+ // the verifier can update its value, but |verified| also needs to be marked |
+ // as __block so that waitUntilCondition(), below, can access it by |
+ // reference. |
+ __block bool verified = false; |
+ |
+ // Ensure that RemoveVerifierForElementWithId() is run regardless of how |
+ // the block exits. |
+ base::ScopedClosureRunner cleanup( |
+ base::Bind(&RemoveVerifierForElementWithId, state, element_id)); |
+ |
+ // Inject the vefifier. |
+ bool verifier_added = |
+ AddVerifierToElementWithId(state, element_id, &verified); |
+ if (!verifier_added) { |
+ NSString* description = [NSString |
+ stringWithFormat:@"It wasn't possible to add the verification " |
+ @"javascript for element_id %s", |
+ element_id.c_str()]; |
+ NSDictionary* userInfo = @{NSLocalizedDescriptionKey : description}; |
Eugene But (OOO till 7-30)
2016/08/30 18:11:05
s/userInfo/user_info
marq (ping after 24h)
2016/08/31 13:07:58
Done.
|
+ *error = [NSError errorWithDomain:kGREYInteractionErrorDomain |
+ code:kGREYInteractionActionFailedErrorCode |
+ userInfo:userInfo]; |
+ return NO; |
+ } |
+ |
+ // Run the action. |
+ [[EarlGrey selectElementWithMatcher:webViewInWebState(state)] |
+ performAction:action |
+ error:error]; |
+ |
+ if (*error) { |
+ return NO; |
+ } |
+ |
+ // Wait for the verified to trigger and set |verified|. |
+ NSString* verification_timeout_message = |
+ [NSString stringWithFormat:@"The action (%@) on element_id %s wasn't " |
+ @"verified before timing out.", |
+ action.name, element_id.c_str()]; |
+ testing::WaitUntilCondition(testing::kWaitForJSCompletionTimeout, |
+ verification_timeout_message, ^{ |
+ return verified; |
+ }); |
+ |
+ // If |verified| is not true, the wait condition should have already exited |
+ // this control flow, so sanity check that it has in fact been set to |
+ // true by this point. |
+ DCHECK(verified); |
+ return YES; |
+ }; |
+ |
+ return [GREYActionBlock actionWithName:action_name |
+ constraints:webViewInWebState(state) |
+ performBlock:verified_tap]; |
+} |
+ |
+void LongPressWebViewElementForContextMenu(WebState* state, |
+ std::string element_id, |
+ bool triggers_context_menu) { |
+ CGRect rect = web::test::BoundingRectOfElementWithId(state, element_id); |
+ // Assert that |rect| is non-empty. |
+ NSString* description = [NSString |
+ stringWithFormat:@"Couldn't locate a bounding rect for element_id %s; " |
+ @"either it isn't there or it has no area.", |
+ element_id.c_str()]; |
+ GREYAssert( |
+ ^bool() { |
+ return !CGRectIsEmpty(rect); |
+ }, |
+ description); |
+ CGPoint point = CGPointMake(CGRectGetMidX(rect), CGRectGetMidY(rect)); |
Eugene But (OOO till 7-30)
2016/08/30 18:11:05
Should this be something more sophisticated like |
marq (ping after 24h)
2016/08/31 13:07:57
-tappablePointInRect: is KIF code I'd have to conv
|
+ |
+ id<GREYAction> longpress = |
+ grey_longPressAtPointWithDuration(point, kContextMenuLongPressDuration); |
+ id<GREYAction> action = longpress; |
+ |
+ if (!triggers_context_menu) { |
+ action = webViewVerifiedActionOnElement(state, longpress, element_id); |
+ } |
+ |
+ [[EarlGrey selectElementWithMatcher:webViewInWebState(state)] |
+ performAction:action]; |
+} |
+ |
+} // namespace web |