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

Side by Side 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, 3 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 unified diff | Download patch
OLDNEW
(Empty)
1 // Copyright 2016 The Chromium Authors. All rights reserved.
2 // Use of this source code is governed by a BSD-style license that can be
3 // found in the LICENSE file.
4
5 #import "ios/web/public/test/earl_grey/web_view_actions.h"
6
7 #include "base/callback_helpers.h"
8 #include "base/logging.h"
9 #include "base/mac/bind_objc_block.h"
10 #include "base/strings/stringprintf.h"
11 #include "base/test/ios/wait_util.h"
12 #include "base/values.h"
13 #import "ios/testing/earl_grey/wait_util.h"
14 #import "ios/web/public/test/earl_grey/web_view_matchers.h"
15 #import "ios/web/public/test/web_view_interaction_test_util.h"
16 #import "ios/web/web_state/web_state_impl.h"
17
18 using web::test::ExecuteJavaScript;
19
20 namespace {
21
22 // Long press duration to trigger context menu.
23 const NSTimeInterval kContextMenuLongPressDuration = 0.3;
24
25 // Callback prefix for injected verifiers.
26 const std::string CallbackPrefixForElementId(const std::string& element_id) {
27 return "__web_test_" + element_id + "_interaction";
28 }
29
30 // Generic verification injector. Injects one-time mousedown verification into
31 // |web_state| that will set the boolean pointed to by |verified| to true when
32 // |web_state|'s webview registers the mousedown event.
33 // RemoveVerifierForElementWithId() should be called after this to ensure
34 // future tests can add verifiers with the same prefix.
35 bool AddVerifierToElementWithId(web::WebState* web_state,
36 const std::string& element_id,
37 bool* verified) {
38 const std::string kCallbackPrefix = CallbackPrefixForElementId(element_id);
39 const char kCallbackCommand[] = "verified";
40 const std::string kCallbackInvocation =
41 kCallbackPrefix + '.' + kCallbackCommand;
42
43 const char kAddInteractionVerifierScriptTemplate[] =
44 "(function() {"
45 // First template param: element ID.
46 " var elementId = '%1$s';"
47 " var element = document.getElementById(elementId);"
48 " if (!element)"
49 " return 'Element ' + elementId + ' not found';"
50 " var invokeType = typeof __gCrWeb.message;"
51 " if (invokeType != 'object')"
52 " return 'Host invocation not installed (' + invokeType + ')';"
53 " var options = {'capture' : true, 'once' : true, 'passive' : true};"
54 " element.addEventListener('mousedown', function(event) {"
55 " __gCrWeb.message.invokeOnHost("
56 // Second template param: callback command.
57 " {'command' : '%2$s' });"
58 " }, options);"
59 " return true;"
60 "})();";
61
62 const std::string kAddVerifierScript =
63 base::StringPrintf(kAddInteractionVerifierScriptTemplate,
64 element_id.c_str(), kCallbackInvocation.c_str());
65 NSDate* deadline =
66 [NSDate dateWithTimeIntervalSinceNow:testing::kWaitForUIElementTimeout];
67 bool verifier_added = false;
68 while (([[NSDate date] compare:deadline] != NSOrderedDescending) &&
69 !verifier_added) {
70 std::unique_ptr<base::Value> value =
71 web::test::ExecuteJavaScript(web_state, kAddVerifierScript);
72 if (value) {
73 std::string error;
74 if (value->GetAsString(&error)) {
75 DLOG(ERROR) << "Verifier injection failed: " << error << ", retrying.";
76 } else if (value->GetAsBoolean(&verifier_added)) {
77 verifier_added = true;
78 }
79 }
80 base::test::ios::SpinRunLoopWithMaxDelay(
81 base::TimeDelta::FromSecondsD(testing::kSpinDelaySeconds));
82 }
83
84 if (!verifier_added)
85 return false;
86
87 // The callback doesn't care about any of the parameters, just whether it is
88 // called or not.
89 auto callback = base::BindBlock(^bool(const base::DictionaryValue& /* json */,
90 const GURL& /* origin_url */,
91 bool /* user_is_interacting */) {
92 *verified = true;
93 return true;
94 });
95
96 static_cast<web::WebStateImpl*>(web_state)->AddScriptCommandCallback(
97 callback, kCallbackPrefix);
98 return true;
99 }
100
101 // Removes the injected callback.
102 void RemoveVerifierForElementWithId(web::WebState* web_state,
103 const std::string& element_id) {
104 static_cast<web::WebStateImpl*>(web_state)->RemoveScriptCommandCallback(
105 CallbackPrefixForElementId(element_id));
106 }
107
108 } // namespace
109
110 namespace web {
111
112 id<GREYAction> webViewVerifiedActionOnElement(WebState* state,
113 id<GREYAction> action,
114 const std::string& element_id) {
115 NSString* action_name =
116 [NSString stringWithFormat:@"Verified action (%@) on webview element %s.",
117 action.name, element_id.c_str()];
118
119 GREYPerformBlock verified_tap = ^BOOL(id element, __strong NSError** error) {
120 // A pointer to |verified| is passed into AddVerifierToElementWithId() so
121 // the verifier can update its value, but |verified| also needs to be marked
122 // as __block so that waitUntilCondition(), below, can access it by
123 // reference.
124 __block bool verified = false;
125
126 // Ensure that RemoveVerifierForElementWithId() is run regardless of how
127 // the block exits.
128 base::ScopedClosureRunner cleanup(
129 base::Bind(&RemoveVerifierForElementWithId, state, element_id));
130
131 // Inject the vefifier.
132 bool verifier_added =
133 AddVerifierToElementWithId(state, element_id, &verified);
134 if (!verifier_added) {
135 NSString* description = [NSString
136 stringWithFormat:@"It wasn't possible to add the verification "
137 @"javascript for element_id %s",
138 element_id.c_str()];
139 NSDictionary* user_info = @{NSLocalizedDescriptionKey : description};
140 *error = [NSError errorWithDomain:kGREYInteractionErrorDomain
141 code:kGREYInteractionActionFailedErrorCode
142 userInfo:user_info];
143 return NO;
144 }
145
146 // Run the action.
147 [[EarlGrey selectElementWithMatcher:webViewInWebState(state)]
148 performAction:action
149 error:error];
150
151 if (*error) {
152 return NO;
153 }
154
155 // Wait for the verified to trigger and set |verified|.
156 NSString* verification_timeout_message =
157 [NSString stringWithFormat:@"The action (%@) on element_id %s wasn't "
158 @"verified before timing out.",
159 action.name, element_id.c_str()];
160 testing::WaitUntilCondition(testing::kWaitForJSCompletionTimeout,
161 verification_timeout_message, ^{
162 return verified;
163 });
164
165 // If |verified| is not true, the wait condition should have already exited
166 // this control flow, so sanity check that it has in fact been set to
167 // true by this point.
168 DCHECK(verified);
169 return YES;
170 };
171
172 return [GREYActionBlock actionWithName:action_name
173 constraints:webViewInWebState(state)
174 performBlock:verified_tap];
175 }
176
177 id<GREYAction> webViewLongPressElementForContextMenu(
178 WebState* state,
179 const std::string& element_id,
180 bool triggers_context_menu) {
181 CGRect rect = web::test::GetBoundingRectOfElementWithId(state, element_id);
182 // Check if |rect| is empty; if it is, return an action that just throws an
183 // error.
184 if (CGRectIsEmpty(rect)) {
185 NSString* description = [NSString
186 stringWithFormat:@"Couldn't locate a bounding rect for element_id %s; "
187 @"either it isn't there or it has no area.",
188 element_id.c_str()];
189 GREYPerformBlock throw_error = ^BOOL(id /* element */,
190 __strong NSError** error) {
191 NSDictionary* user_info = @{NSLocalizedDescriptionKey : description};
192 *error = [NSError errorWithDomain:kGREYInteractionErrorDomain
193 code:kGREYInteractionActionFailedErrorCode
194 userInfo:user_info];
195 return NO;
196 };
197 return [GREYActionBlock actionWithName:@"Locate element bounds"
198 performBlock:throw_error];
199 }
200
201 // If there's a usable rect, long-press in the center.
202 CGPoint point = CGPointMake(CGRectGetMidX(rect), CGRectGetMidY(rect));
203
204 id<GREYAction> longpress =
205 grey_longPressAtPointWithDuration(point, kContextMenuLongPressDuration);
206 id<GREYAction> action = longpress;
207
208 if (!triggers_context_menu) {
209 action = webViewVerifiedActionOnElement(state, longpress, element_id);
210 }
211
212 return action;
213 }
214
215 } // namespace web
OLDNEW
« 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