Chromium Code Reviews
chromiumcodereview-hr@appspot.gserviceaccount.com (chromiumcodereview-hr) | Please choose your nickname with Settings | Help | Chromium Project | Gerrit Changes | Sign out
(420)

Unified Diff: ios/web/public/test/earl_grey/web_view_actions.mm

Issue 2275303004: Context menu egtests, plus related utilities. (Closed)
Patch Set: Fewer References. Created 4 years, 4 months ago
Use n/p to move between diff chunks; N/P to move between comments. Draft comments are only viewable by you.
Jump to:
View side-by-side diff with in-line comments
Download patch
« no previous file with comments | « ios/web/public/test/earl_grey/web_view_actions.h ('k') | ios/web/public/test/earl_grey/web_view_matchers.h » ('j') | no next file with comments »
Expand Comments ('e') | Collapse Comments ('c') | Show Comments Hide Comments ('s')
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..ec9df6b30ff32ac3003c42d15c22de15b048cd71
--- /dev/null
+++ b/ios/web/public/test/earl_grey/web_view_actions.mm
@@ -0,0 +1,215 @@
+// 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"
+#import "ios/testing/earl_grey/wait_util.h"
+#import "ios/web/public/test/earl_grey/web_view_matchers.h"
+#import "ios/web/public/test/web_view_interaction_test_util.h"
+#import "ios/web/web_state/web_state_impl.h"
+
+using web::test::ExecuteJavaScript;
+
+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';"
+ " 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::ExecuteJavaScript(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 */,
+ const GURL& /* origin_url */,
+ bool /* user_is_interacting */) {
+ *verified = true;
+ return true;
+ });
+
+ static_cast<web::WebStateImpl*>(web_state)->AddScriptCommandCallback(
+ callback, kCallbackPrefix);
+ return true;
+}
+
+// Removes the injected callback.
+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,
+ const 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* user_info = @{NSLocalizedDescriptionKey : description};
+ *error = [NSError errorWithDomain:kGREYInteractionErrorDomain
+ code:kGREYInteractionActionFailedErrorCode
+ userInfo:user_info];
+ 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];
+}
+
+id<GREYAction> webViewLongPressElementForContextMenu(
+ WebState* state,
+ const std::string& element_id,
+ bool triggers_context_menu) {
+ CGRect rect = web::test::GetBoundingRectOfElementWithId(state, element_id);
+ // Check if |rect| is empty; if it is, return an action that just throws an
+ // error.
+ if (CGRectIsEmpty(rect)) {
+ 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()];
+ GREYPerformBlock throw_error = ^BOOL(id /* element */,
+ __strong NSError** error) {
+ NSDictionary* user_info = @{NSLocalizedDescriptionKey : description};
+ *error = [NSError errorWithDomain:kGREYInteractionErrorDomain
+ code:kGREYInteractionActionFailedErrorCode
+ userInfo:user_info];
+ return NO;
+ };
+ return [GREYActionBlock actionWithName:@"Locate element bounds"
+ performBlock:throw_error];
+ }
+
+ // If there's a usable rect, long-press in the center.
+ CGPoint point = CGPointMake(CGRectGetMidX(rect), CGRectGetMidY(rect));
+
+ id<GREYAction> longpress =
+ grey_longPressAtPointWithDuration(point, kContextMenuLongPressDuration);
+ id<GREYAction> action = longpress;
+
+ if (!triggers_context_menu) {
+ action = webViewVerifiedActionOnElement(state, longpress, element_id);
+ }
+
+ return action;
+}
+
+} // namespace web
« no previous file with comments | « ios/web/public/test/earl_grey/web_view_actions.h ('k') | ios/web/public/test/earl_grey/web_view_matchers.h » ('j') | no next file with comments »

Powered by Google App Engine
This is Rietveld 408576698