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

Side by Side Diff: chrome/browser/extensions/api/automation/automation_apitest.cc

Issue 1155183006: Reimplement automation API on top of C++-backed AXTree. (Closed) Base URL: https://chromium.googlesource.com/chromium/src.git@automation_faster_2
Patch Set: Address most feedback Created 5 years, 6 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
1 // Copyright 2014 The Chromium Authors. All rights reserved. 1 // Copyright 2014 The Chromium Authors. All rights reserved.
2 // Use of this source code is governed by a BSD-style license that can be 2 // Use of this source code is governed by a BSD-style license that can be
3 // found in the LICENSE file. 3 // found in the LICENSE file.
4 4
5 #include "base/files/file_path.h" 5 #include "base/files/file_path.h"
6 #include "base/path_service.h" 6 #include "base/path_service.h"
7 #include "base/strings/string_number_conversions.h" 7 #include "base/strings/string_number_conversions.h"
8 #include "chrome/browser/extensions/api/automation_internal/automation_util.h" 8 #include "chrome/browser/accessibility/ax_tree_id_registry.h"
9 #include "chrome/browser/extensions/api/automation_internal/automation_event_rou ter.h"
9 #include "chrome/browser/extensions/chrome_extension_function.h" 10 #include "chrome/browser/extensions/chrome_extension_function.h"
10 #include "chrome/browser/extensions/extension_apitest.h" 11 #include "chrome/browser/extensions/extension_apitest.h"
11 #include "chrome/browser/ui/tabs/tab_strip_model.h" 12 #include "chrome/browser/ui/tabs/tab_strip_model.h"
12 #include "chrome/common/chrome_paths.h" 13 #include "chrome/common/chrome_paths.h"
13 #include "chrome/common/chrome_switches.h" 14 #include "chrome/common/chrome_switches.h"
14 #include "chrome/common/extensions/api/automation_internal.h" 15 #include "chrome/common/extensions/api/automation_internal.h"
16 #include "chrome/common/extensions/chrome_extension_messages.h"
15 #include "chrome/test/base/ui_test_utils.h" 17 #include "chrome/test/base/ui_test_utils.h"
16 #include "content/public/browser/ax_event_notification_details.h" 18 #include "content/public/browser/ax_event_notification_details.h"
17 #include "content/public/browser/render_widget_host.h" 19 #include "content/public/browser/render_widget_host.h"
18 #include "content/public/browser/render_widget_host_view.h" 20 #include "content/public/browser/render_widget_host_view.h"
19 #include "content/public/browser/web_contents.h" 21 #include "content/public/browser/web_contents.h"
20 #include "extensions/test/extension_test_message_listener.h" 22 #include "extensions/test/extension_test_message_listener.h"
21 #include "net/dns/mock_host_resolver.h" 23 #include "net/dns/mock_host_resolver.h"
22 #include "net/test/embedded_test_server/embedded_test_server.h" 24 #include "net/test/embedded_test_server/embedded_test_server.h"
23 #include "testing/gtest/include/gtest/gtest.h" 25 #include "testing/gtest/include/gtest/gtest.h"
24 #include "ui/accessibility/ax_node.h" 26 #include "ui/accessibility/ax_node.h"
(...skipping 64 matching lines...) Expand 10 before | Expand all | Expand 10 after
89 ASSERT_FALSE(tab->IsFullAccessibilityModeForTesting()); 91 ASSERT_FALSE(tab->IsFullAccessibilityModeForTesting());
90 ASSERT_TRUE(tab->IsTreeOnlyAccessibilityModeForTesting()); 92 ASSERT_TRUE(tab->IsTreeOnlyAccessibilityModeForTesting());
91 } 93 }
92 94
93 IN_PROC_BROWSER_TEST_F(AutomationApiTest, SanityCheck) { 95 IN_PROC_BROWSER_TEST_F(AutomationApiTest, SanityCheck) {
94 StartEmbeddedTestServer(); 96 StartEmbeddedTestServer();
95 ASSERT_TRUE(RunExtensionSubtest("automation/tests/tabs", "sanity_check.html")) 97 ASSERT_TRUE(RunExtensionSubtest("automation/tests/tabs", "sanity_check.html"))
96 << message_; 98 << message_;
97 } 99 }
98 100
99 IN_PROC_BROWSER_TEST_F(AutomationApiTest, Unit) {
100 ASSERT_TRUE(RunExtensionSubtest("automation/tests/unit", "unit.html"))
101 << message_;
102 }
103
104 IN_PROC_BROWSER_TEST_F(AutomationApiTest, GetTreeByTabId) { 101 IN_PROC_BROWSER_TEST_F(AutomationApiTest, GetTreeByTabId) {
105 StartEmbeddedTestServer(); 102 StartEmbeddedTestServer();
106 ASSERT_TRUE(RunExtensionSubtest("automation/tests/tabs", "tab_id.html")) 103 ASSERT_TRUE(RunExtensionSubtest("automation/tests/tabs", "tab_id.html"))
107 << message_; 104 << message_;
108 } 105 }
109 106
110 IN_PROC_BROWSER_TEST_F(AutomationApiTest, Events) { 107 IN_PROC_BROWSER_TEST_F(AutomationApiTest, Events) {
111 StartEmbeddedTestServer(); 108 StartEmbeddedTestServer();
112 ASSERT_TRUE(RunExtensionSubtest("automation/tests/tabs", "events.html")) 109 ASSERT_TRUE(RunExtensionSubtest("automation/tests/tabs", "events.html"))
113 << message_; 110 << message_;
(...skipping 79 matching lines...) Expand 10 before | Expand all | Expand 10 after
193 RunExtensionSubtest("automation/tests/tabs", "queryselector.html")) 190 RunExtensionSubtest("automation/tests/tabs", "queryselector.html"))
194 << message_; 191 << message_;
195 } 192 }
196 193
197 IN_PROC_BROWSER_TEST_F(AutomationApiTest, Find) { 194 IN_PROC_BROWSER_TEST_F(AutomationApiTest, Find) {
198 StartEmbeddedTestServer(); 195 StartEmbeddedTestServer();
199 ASSERT_TRUE(RunExtensionSubtest("automation/tests/tabs", "find.html")) 196 ASSERT_TRUE(RunExtensionSubtest("automation/tests/tabs", "find.html"))
200 << message_; 197 << message_;
201 } 198 }
202 199
203 // Flaky. http://crbug.com/467921 200 IN_PROC_BROWSER_TEST_F(AutomationApiTest, Attributes) {
204 IN_PROC_BROWSER_TEST_F(AutomationApiTest, DISABLED_Mixins) {
205 StartEmbeddedTestServer(); 201 StartEmbeddedTestServer();
206 ASSERT_TRUE(RunExtensionSubtest("automation/tests/tabs", "mixins.html")) 202 ASSERT_TRUE(RunExtensionSubtest("automation/tests/tabs", "attributes.html"))
207 << message_; 203 << message_;
208 } 204 }
209 205
210 IN_PROC_BROWSER_TEST_F(AutomationApiTest, TreeChange) { 206 IN_PROC_BROWSER_TEST_F(AutomationApiTest, TreeChange) {
211 StartEmbeddedTestServer(); 207 StartEmbeddedTestServer();
212 ASSERT_TRUE(RunExtensionSubtest("automation/tests/tabs", "tree_change.html")) 208 ASSERT_TRUE(RunExtensionSubtest("automation/tests/tabs", "tree_change.html"))
213 << message_; 209 << message_;
214 } 210 }
215 211
216
217 static const int kPid = 1;
218 static const int kTab0Rid = 1;
219 static const int kTab1Rid = 2;
220
221 using content::BrowserContext;
222
223 typedef ui::AXTreeSerializer<const ui::AXNode*> TreeSerializer;
224 typedef ui::AXTreeSource<const ui::AXNode*> TreeSource;
225
226 #define AX_EVENT_ASSERT_EQUAL ui::AX_EVENT_LOAD_COMPLETE
227 #define AX_EVENT_ASSERT_NOT_EQUAL ui::AX_EVENT_ACTIVEDESCENDANTCHANGED
228 #define AX_EVENT_IGNORE ui::AX_EVENT_CHILDREN_CHANGED
229 #define AX_EVENT_TEST_COMPLETE ui::AX_EVENT_BLUR
230
231 // This test is based on ui/accessibility/ax_generated_tree_unittest.cc
232 // However, because the tree updates need to be sent to the extension, we can't
233 // use a straightforward set of nested loops as that test does, so this class
234 // keeps track of where we're up to in our imaginary loops, while the extension
235 // function classes below do the work of actually incrementing the state when
236 // appropriate.
237 // The actual deserialization and comparison happens in the API bindings and the
238 // test extension respectively: see
239 // c/t/data/extensions/api_test/automation/tests/generated/generated_trees.js
240 class TreeSerializationState {
241 public:
242 TreeSerializationState()
243 #ifdef NDEBUG
244 : tree_size(3),
245 #else
246 : tree_size(2),
247 #endif
248 generator(tree_size, true),
249 num_trees(generator.UniqueTreeCount()),
250 tree0_version(0),
251 tree1_version(0) {
252 }
253
254 // Serializes tree and sends it as an accessibility event to the extension.
255 void SendDataForTree(const ui::AXTree* tree,
256 TreeSerializer* serializer,
257 int routing_id,
258 BrowserContext* browser_context) {
259 ui::AXTreeUpdate update;
260 serializer->SerializeChanges(tree->root(), &update);
261 SendUpdate(update,
262 ui::AX_EVENT_LAYOUT_COMPLETE,
263 tree->root()->id(),
264 routing_id,
265 browser_context);
266 }
267
268 // Sends the given AXTreeUpdate to the extension as an accessibility event.
269 void SendUpdate(ui::AXTreeUpdate update,
270 ui::AXEvent event,
271 int node_id,
272 int routing_id,
273 BrowserContext* browser_context) {
274 content::AXEventNotificationDetails detail(update.node_id_to_clear,
275 update.nodes,
276 event,
277 node_id,
278 kPid,
279 routing_id);
280 std::vector<content::AXEventNotificationDetails> details;
281 details.push_back(detail);
282 automation_util::DispatchAccessibilityEventsToAutomation(
283 details, browser_context, gfx::Vector2d());
284 }
285
286 // Notify the extension bindings to destroy the tree for the given tab
287 // (identified by routing_id)
288 void SendTreeDestroyedEvent(int routing_id, BrowserContext* browser_context) {
289 automation_util::DispatchTreeDestroyedEventToAutomation(
290 kPid, routing_id, browser_context);
291 }
292
293 // Reset tree0 to a new generated tree based on tree0_version, reset
294 // tree0_source accordingly.
295 void ResetTree0() {
296 tree0.reset(new ui::AXSerializableTree);
297 tree0_source.reset(tree0->CreateTreeSource());
298 generator.BuildUniqueTree(tree0_version, tree0.get());
299 if (!serializer0.get())
300 serializer0.reset(new TreeSerializer(tree0_source.get()));
301 }
302
303 // Reset tree0, set up serializer0, send down the initial tree data to create
304 // the tree in the extension
305 void InitializeTree0(BrowserContext* browser_context) {
306 ResetTree0();
307 serializer0->ChangeTreeSourceForTesting(tree0_source.get());
308 serializer0->Reset();
309 SendDataForTree(tree0.get(), serializer0.get(), kTab0Rid, browser_context);
310 }
311
312 // Reset tree1 to a new generated tree based on tree1_version, reset
313 // tree1_source accordingly.
314 void ResetTree1() {
315 tree1.reset(new ui::AXSerializableTree);
316 tree1_source.reset(tree1->CreateTreeSource());
317 generator.BuildUniqueTree(tree1_version, tree1.get());
318 if (!serializer1.get())
319 serializer1.reset(new TreeSerializer(tree1_source.get()));
320 }
321
322 // Reset tree1, set up serializer1, send down the initial tree data to create
323 // the tree in the extension
324 void InitializeTree1(BrowserContext* browser_context) {
325 ResetTree1();
326 serializer1->ChangeTreeSourceForTesting(tree1_source.get());
327 serializer1->Reset();
328 SendDataForTree(tree1.get(), serializer1.get(), kTab1Rid, browser_context);
329 }
330
331 const int tree_size;
332 const ui::TreeGenerator generator;
333
334 // The loop variables: comments indicate which variables in
335 // ax_generated_tree_unittest they correspond to.
336 const int num_trees; // n
337 int tree0_version; // i
338 int tree1_version; // j
339 int starting_node; // k
340
341 // Tree infrastructure; tree0 and tree1 need to be regenerated whenever
342 // tree0_version and tree1_version change, respectively; tree0_source and
343 // tree1_source need to be reset whenever that happens.
344 scoped_ptr<ui::AXSerializableTree> tree0, tree1;
345 scoped_ptr<TreeSource> tree0_source, tree1_source;
346 scoped_ptr<TreeSerializer> serializer0, serializer1;
347
348 // Whether tree0 needs to be destroyed after the extension has performed its
349 // checks
350 bool destroy_tree0;
351 };
352
353 static TreeSerializationState state;
354
355 // Override for chrome.automationInternal.enableTab
356 // This fakes out the process and routing IDs for two "tabs", which contain the
357 // source and target trees, respectively, and sends down the current tree for
358 // the requested tab - tab 1 always has tree1, and tab 0 starts with tree0
359 // and then has a series of updates intended to translate tree0 to tree1.
360 // Once all the updates have been sent, the extension asserts that both trees
361 // are equivalent, and then one or both of the trees are reset to a new version.
362 class FakeAutomationInternalEnableTabFunction
363 : public UIThreadExtensionFunction {
364 public:
365 FakeAutomationInternalEnableTabFunction() {}
366
367 ExtensionFunction::ResponseAction Run() override {
368 using api::automation_internal::EnableTab::Params;
369 scoped_ptr<Params> params(Params::Create(*args_));
370 EXTENSION_FUNCTION_VALIDATE(params.get());
371 if (!params->args.tab_id.get())
372 return RespondNow(Error("tab_id not specified"));
373 int tab_id = *params->args.tab_id;
374 if (tab_id == 0) {
375 // tab 0 <--> tree0
376 base::MessageLoop::current()->PostTask(
377 FROM_HERE,
378 base::Bind(&TreeSerializationState::InitializeTree0,
379 base::Unretained(&state),
380 base::Unretained(browser_context())));
381 // TODO(aboxhall): Need to rewrite this test in terms of tree ids.
382 return RespondNow(ArgumentList(
383 api::automation_internal::EnableTab::Results::Create(0)));
384 }
385 if (tab_id == 1) {
386 // tab 1 <--> tree1
387 base::MessageLoop::current()->PostTask(
388 FROM_HERE,
389 base::Bind(&TreeSerializationState::InitializeTree1,
390 base::Unretained(&state),
391 base::Unretained(browser_context())));
392 return RespondNow(ArgumentList(
393 api::automation_internal::EnableTab::Results::Create(0)));
394 }
395 return RespondNow(Error("Unrecognised tab_id"));
396 }
397 };
398
399 // Factory method for use in OverrideFunction()
400 ExtensionFunction* FakeAutomationInternalEnableTabFunctionFactory() {
401 return new FakeAutomationInternalEnableTabFunction();
402 }
403
404 // Helper method to serialize a series of updates via source_serializer to
405 // transform the tree which source_serializer was initialized from into
406 // target_tree, and then trigger the test code to assert the two tabs contain
407 // the same tree.
408 void TransformTree(TreeSerializer* source_serializer,
409 ui::AXTree* target_tree,
410 TreeSource* target_tree_source,
411 content::BrowserContext* browser_context) {
412 source_serializer->ChangeTreeSourceForTesting(target_tree_source);
413 for (int node_delta = 0; node_delta < state.tree_size; ++node_delta) {
414 int id = 1 + (state.starting_node + node_delta) % state.tree_size;
415 ui::AXTreeUpdate update;
416 source_serializer->SerializeChanges(target_tree->GetFromId(id), &update);
417 bool is_last_update = node_delta == state.tree_size - 1;
418 ui::AXEvent event =
419 is_last_update ? AX_EVENT_ASSERT_EQUAL : AX_EVENT_IGNORE;
420 state.SendUpdate(
421 update, event, target_tree->root()->id(), kTab0Rid, browser_context);
422 }
423 }
424
425 // Helper method to send a no-op tree update to tab 0 with the given event.
426 void SendEvent(ui::AXEvent event, content::BrowserContext* browser_context) {
427 ui::AXTreeUpdate update;
428 ui::AXNode* root = state.tree0->root();
429 state.serializer0->SerializeChanges(root, &update);
430 state.SendUpdate(update, event, root->id(), kTab0Rid, browser_context);
431 }
432
433 // Override for chrome.automationInternal.performAction
434 // This is used as a synchronization mechanism; the general flow is:
435 // 1. The extension requests tree0 and tree1 (on tab 0 and tab 1 respectively)
436 // 2. FakeAutomationInternalEnableTabFunction sends down the trees
437 // 3. When the callback for getTree(0) fires, the extension calls doDefault() on
438 // the root node of tree0, which calls into this class's Run() method.
439 // 4. In the normal case, we're in the "inner loop" (iterating over
440 // starting_node). For each value of starting_node, we do the following:
441 // a. Serialize a sequence of updates which should transform tree0 into
442 // tree1. Each of these updates is sent as a childrenChanged event,
443 // except for the last which is sent as a loadComplete event.
444 // b. state.destroy_tree0 is set to true
445 // c. state.starting_node gets incremented
446 // d. The loadComplete event triggers an assertion in the extension.
447 // e. The extension performs another doDefault() on the root node of the
448 // tree.
449 // f. This time, we send a destroy event to tab0, so that the tree can be
450 // reset.
451 // g. The extension is notified of the tree's destruction and requests the
452 // tree for tab 0 again, returning to step 2.
453 // 5. When starting_node exceeds state.tree_size, we increment tree0_version if
454 // it would not exceed state.num_trees, or increment tree1_version and reset
455 // tree0_version to 0 otherwise, and reset starting_node to 0.
456 // Then we reset one or both trees as appropriate, and send down destroyed
457 // events similarly, causing the extension to re-request the tree and going
458 // back to step 2 again.
459 // 6. When tree1_version has gone through all possible values, we send a blur
460 // event, signaling the extension to call chrome.test.succeed() and finish
461 // the test.
462 class FakeAutomationInternalPerformActionFunction
463 : public UIThreadExtensionFunction {
464 public:
465 FakeAutomationInternalPerformActionFunction() {}
466
467 ExtensionFunction::ResponseAction Run() override {
468 if (state.destroy_tree0) {
469 // Step 4.f: tell the extension to destroy the tree and re-request it.
470 state.SendTreeDestroyedEvent(kTab0Rid, browser_context());
471 state.destroy_tree0 = false;
472 return RespondNow(NoArguments());
473 }
474
475 TreeSerializer* serializer0 = state.serializer0.get();
476 if (state.starting_node < state.tree_size) {
477 // As a sanity check, if the trees are not equal, assert that they are not
478 // equal before serializing changes.
479 if (state.tree0_version != state.tree1_version)
480 SendEvent(AX_EVENT_ASSERT_NOT_EQUAL, browser_context());
481
482 // Step 4.a: pretend that tree0 turned into tree1, and serialize
483 // a sequence of updates to tab 0 to match.
484 TransformTree(serializer0,
485 state.tree1.get(),
486 state.tree1_source.get(),
487 browser_context());
488
489 // Step 4.b: remember that we need to tell the extension to destroy and
490 // re-request the tree on the next action.
491 state.destroy_tree0 = true;
492
493 // Step 4.c: increment starting_node.
494 state.starting_node++;
495 } else if (state.tree0_version < state.num_trees - 1) {
496 // Step 5: Increment tree0_version and reset starting_node
497 state.tree0_version++;
498 state.starting_node = 0;
499
500 // Step 5: Reset tree0 and tell the extension to destroy and re-request it
501 state.SendTreeDestroyedEvent(kTab0Rid, browser_context());
502 } else if (state.tree1_version < state.num_trees - 1) {
503 // Step 5: Increment tree1_version and reset tree0_version and
504 // starting_node
505 state.tree1_version++;
506 state.tree0_version = 0;
507 state.starting_node = 0;
508
509 // Step 5: Reset tree0 and tell the extension to destroy and re-request it
510 state.SendTreeDestroyedEvent(kTab0Rid, browser_context());
511
512 // Step 5: Reset tree1 and tell the extension to destroy and re-request it
513 state.SendTreeDestroyedEvent(kTab1Rid, browser_context());
514 } else {
515 // Step 6: Send a TEST_COMPLETE (blur) event to signal the extension to
516 // call chrome.test.succeed().
517 SendEvent(AX_EVENT_TEST_COMPLETE, browser_context());
518 }
519
520 return RespondNow(NoArguments());
521 }
522 };
523
524 // Factory method for use in OverrideFunction()
525 ExtensionFunction* FakeAutomationInternalPerformActionFunctionFactory() {
526 return new FakeAutomationInternalPerformActionFunction();
527 }
528
529 // http://crbug.com/396353
530 IN_PROC_BROWSER_TEST_F(AutomationApiTest, DISABLED_GeneratedTree) {
531 ASSERT_TRUE(extensions::ExtensionFunctionDispatcher::OverrideFunction(
532 "automationInternal.enableTab",
533 FakeAutomationInternalEnableTabFunctionFactory));
534 ASSERT_TRUE(extensions::ExtensionFunctionDispatcher::OverrideFunction(
535 "automationInternal.performAction",
536 FakeAutomationInternalPerformActionFunctionFactory));
537 ASSERT_TRUE(RunExtensionSubtest("automation/tests/generated",
538 "generated_trees.html")) << message_;
539 }
540
541 } // namespace extensions 212 } // namespace extensions
OLDNEW

Powered by Google App Engine
This is Rietveld 408576698