1// Copyright (c) 2012 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#include <string>
6
7#include "base/compiler_specific.h"
8#include "base/json/json_reader.h"
9#include "base/values.h"
10#include "chrome/test/chromedriver/chrome/browser_info.h"
11#include "chrome/test/chromedriver/chrome/navigation_tracker.h"
12#include "chrome/test/chromedriver/chrome/status.h"
13#include "chrome/test/chromedriver/chrome/stub_devtools_client.h"
14#include "testing/gtest/include/gtest/gtest.h"
15
16namespace {
17
18void AssertPendingState(NavigationTracker* tracker,
19                        const std::string& frame_id,
20                        bool expected_is_pending) {
21  bool is_pending = !expected_is_pending;
22  ASSERT_EQ(kOk, tracker->IsPendingNavigation(frame_id, &is_pending).code());
23  ASSERT_EQ(expected_is_pending, is_pending);
24}
25
26void AssertTrackerExpectsSingleStopEvent(BrowserInfo* browser_info) {
27  StubDevToolsClient client;
28  NavigationTracker tracker(&client, browser_info);
29  base::DictionaryValue params;
30  params.SetString("frameId", "f");
31
32  // pending_frames_set_.size() == 0
33  ASSERT_EQ(
34      kOk, tracker.OnEvent(&client, "Page.frameStartedLoading", params).code());
35  // pending_frames_set_.size() == 1
36  ASSERT_NO_FATAL_FAILURE(AssertPendingState(&tracker, "f", true));
37  ASSERT_EQ(
38      kOk, tracker.OnEvent(&client, "Page.frameStartedLoading", params).code());
39  // pending_frames_set_.size() == 2
40  ASSERT_NO_FATAL_FAILURE(AssertPendingState(&tracker, "f", true));
41  ASSERT_EQ(
42      kOk, tracker.OnEvent(&client, "Page.frameStoppedLoading", params).code());
43  // pending_frames_set_.size() == 0
44  ASSERT_NO_FATAL_FAILURE(AssertPendingState(&tracker, "f", false));
45}
46
47void AssertTrackerExpectsMultipleStopEvents(BrowserInfo* browser_info) {
48  StubDevToolsClient client;
49  NavigationTracker tracker(&client, browser_info);
50  base::DictionaryValue params;
51
52  // pending_frames_set_.size() == 0
53  params.SetString("frameId", "1");
54  ASSERT_EQ(
55      kOk, tracker.OnEvent(&client, "Page.frameStartedLoading", params).code());
56  // pending_frames_set_.size() == 1
57  ASSERT_NO_FATAL_FAILURE(AssertPendingState(&tracker, "1", true));
58  params.SetString("frameId", "2");
59  ASSERT_EQ(
60      kOk, tracker.OnEvent(&client, "Page.frameStartedLoading", params).code());
61  // pending_frames_set_.size() == 2
62  ASSERT_NO_FATAL_FAILURE(AssertPendingState(&tracker, "2", true));
63  params.SetString("frameId", "2");
64  ASSERT_EQ(
65      kOk, tracker.OnEvent(&client, "Page.frameStoppedLoading", params).code());
66  // pending_frames_set_.size() == 1
67  ASSERT_NO_FATAL_FAILURE(AssertPendingState(&tracker, "2", true));
68  params.SetString("frameId", "1");
69  ASSERT_EQ(
70      kOk, tracker.OnEvent(&client, "Page.frameStoppedLoading", params).code());
71  // pending_frames_set_.size() == 0
72  ASSERT_NO_FATAL_FAILURE(AssertPendingState(&tracker, "1", false));
73  params.SetString("frameId", "3");
74  ASSERT_EQ(
75      kOk, tracker.OnEvent(&client, "Page.frameStoppedLoading", params).code());
76  // pending_frames_set_.size() == 0
77  ASSERT_NO_FATAL_FAILURE(AssertPendingState(&tracker, "3", false));
78  ASSERT_EQ(
79      kOk, tracker.OnEvent(&client, "Page.frameStartedLoading", params).code());
80  // pending_frames_set_.size() == 1
81  ASSERT_NO_FATAL_FAILURE(AssertPendingState(&tracker, "3", true));
82}
83
84}  // namespace
85
86TEST(NavigationTracker, FrameLoadStartStop) {
87  StubDevToolsClient client;
88  BrowserInfo browser_info;
89  NavigationTracker tracker(&client, &browser_info);
90
91  base::DictionaryValue params;
92  params.SetString("frameId", "f");
93
94  ASSERT_EQ(
95      kOk, tracker.OnEvent(&client, "Page.frameStartedLoading", params).code());
96  ASSERT_NO_FATAL_FAILURE(AssertPendingState(&tracker, "f", true));
97  ASSERT_EQ(
98      kOk, tracker.OnEvent(&client, "Page.frameStoppedLoading", params).code());
99  ASSERT_NO_FATAL_FAILURE(AssertPendingState(&tracker, "f", false));
100}
101
102// When a frame fails to load due to (for example) a DNS resolution error, we
103// can sometimes see two Page.frameStartedLoading events with only a single
104// Page.frameStoppedLoading event.
105TEST(NavigationTracker, FrameLoadStartStartStop) {
106  StubDevToolsClient client;
107  BrowserInfo browser_info;
108  NavigationTracker tracker(&client, &browser_info);
109
110  base::DictionaryValue params;
111  params.SetString("frameId", "f");
112
113  ASSERT_EQ(
114      kOk, tracker.OnEvent(&client, "Page.frameStartedLoading", params).code());
115  ASSERT_NO_FATAL_FAILURE(AssertPendingState(&tracker, "f", true));
116  ASSERT_EQ(
117      kOk, tracker.OnEvent(&client, "Page.frameStartedLoading", params).code());
118  ASSERT_NO_FATAL_FAILURE(AssertPendingState(&tracker, "f", true));
119  ASSERT_EQ(
120      kOk, tracker.OnEvent(&client, "Page.frameStoppedLoading", params).code());
121  ASSERT_NO_FATAL_FAILURE(AssertPendingState(&tracker, "f", false));
122}
123
124// NavigationTracker::OnEvent handles the Page.frameStoppedLoading event
125// differently, depending on the browser and blink version. See the comment in
126// NavigationTracker::OnEvent for details.
127
128TEST(NavigationTracker, MultipleFramesLoadOldDevtools) {
129  BrowserInfo kOldChromeWithNewBlink("chrome", std::string(), 1916, 170248);
130  AssertTrackerExpectsSingleStopEvent(&kOldChromeWithNewBlink);
131
132  BrowserInfo kWebViewWithOldBlink("webview", std::string(), 9999, 170247);
133  AssertTrackerExpectsSingleStopEvent(&kWebViewWithOldBlink);
134}
135
136TEST(NavigationTracker, MultipleFramesLoad) {
137  BrowserInfo kNewChromeWithNewBlink("chrome", std::string(), 1917, 170248);
138  AssertTrackerExpectsMultipleStopEvents(&kNewChromeWithNewBlink);
139
140  BrowserInfo kWebViewWithNewBlink("webview", std::string(), 9999, 170248);
141  AssertTrackerExpectsMultipleStopEvents(&kWebViewWithNewBlink);
142}
143
144TEST(NavigationTracker, NavigationScheduledThenLoaded) {
145  StubDevToolsClient client;
146  BrowserInfo browser_info;
147  NavigationTracker tracker(
148      &client, NavigationTracker::kNotLoading, &browser_info);
149  base::DictionaryValue params;
150  params.SetString("frameId", "f");
151  base::DictionaryValue params_scheduled;
152  params_scheduled.SetInteger("delay", 0);
153  params_scheduled.SetString("frameId", "f");
154
155  ASSERT_EQ(
156      kOk,
157      tracker.OnEvent(
158          &client, "Page.frameScheduledNavigation", params_scheduled).code());
159  ASSERT_NO_FATAL_FAILURE(AssertPendingState(&tracker, "f", true));
160  ASSERT_EQ(
161      kOk, tracker.OnEvent(&client, "Page.frameStartedLoading", params).code());
162  ASSERT_NO_FATAL_FAILURE(AssertPendingState(&tracker, "f", true));
163  ASSERT_EQ(
164      kOk,
165      tracker.OnEvent(&client, "Page.frameClearedScheduledNavigation", params)
166          .code());
167  ASSERT_NO_FATAL_FAILURE(AssertPendingState(&tracker, "f", true));
168  ASSERT_EQ(
169      kOk, tracker.OnEvent(&client, "Page.frameStoppedLoading", params).code());
170  ASSERT_NO_FATAL_FAILURE(AssertPendingState(&tracker, "f", false));
171}
172
173TEST(NavigationTracker, NavigationScheduledForOtherFrame) {
174  StubDevToolsClient client;
175  BrowserInfo browser_info;
176  NavigationTracker tracker(
177      &client, NavigationTracker::kNotLoading, &browser_info);
178  base::DictionaryValue params_scheduled;
179  params_scheduled.SetInteger("delay", 0);
180  params_scheduled.SetString("frameId", "other");
181
182  ASSERT_EQ(
183      kOk,
184      tracker.OnEvent(
185          &client, "Page.frameScheduledNavigation", params_scheduled).code());
186  ASSERT_NO_FATAL_FAILURE(AssertPendingState(&tracker, "f", false));
187}
188
189TEST(NavigationTracker, NavigationScheduledThenCancelled) {
190  StubDevToolsClient client;
191  BrowserInfo browser_info;
192  NavigationTracker tracker(
193      &client, NavigationTracker::kNotLoading, &browser_info);
194  base::DictionaryValue params;
195  params.SetString("frameId", "f");
196  base::DictionaryValue params_scheduled;
197  params_scheduled.SetInteger("delay", 0);
198  params_scheduled.SetString("frameId", "f");
199
200  ASSERT_EQ(
201      kOk,
202      tracker.OnEvent(
203          &client, "Page.frameScheduledNavigation", params_scheduled).code());
204  ASSERT_NO_FATAL_FAILURE(AssertPendingState(&tracker, "f", true));
205  ASSERT_EQ(
206      kOk,
207      tracker.OnEvent(&client, "Page.frameClearedScheduledNavigation", params)
208          .code());
209  ASSERT_NO_FATAL_FAILURE(AssertPendingState(&tracker, "f", false));
210}
211
212TEST(NavigationTracker, NavigationScheduledTooFarAway) {
213  StubDevToolsClient client;
214  BrowserInfo browser_info;
215  NavigationTracker tracker(
216      &client, NavigationTracker::kNotLoading, &browser_info);
217
218  base::DictionaryValue params_scheduled;
219  params_scheduled.SetInteger("delay", 10);
220  params_scheduled.SetString("frameId", "f");
221  ASSERT_EQ(
222      kOk,
223      tracker.OnEvent(
224          &client, "Page.frameScheduledNavigation", params_scheduled).code());
225  ASSERT_NO_FATAL_FAILURE(AssertPendingState(&tracker, "f", false));
226}
227
228TEST(NavigationTracker, DiscardScheduledNavigationsOnMainFrameCommit) {
229  StubDevToolsClient client;
230  BrowserInfo browser_info;
231  NavigationTracker tracker(
232      &client, NavigationTracker::kNotLoading, &browser_info);
233
234  base::DictionaryValue params_scheduled;
235  params_scheduled.SetString("frameId", "subframe");
236  params_scheduled.SetInteger("delay", 0);
237  ASSERT_EQ(
238      kOk,
239      tracker.OnEvent(
240          &client, "Page.frameScheduledNavigation", params_scheduled).code());
241  ASSERT_NO_FATAL_FAILURE(AssertPendingState(&tracker, "subframe", true));
242
243  base::DictionaryValue params_navigated;
244  params_navigated.SetString("frame.parentId", "something");
245  ASSERT_EQ(
246      kOk,
247      tracker.OnEvent(&client, "Page.frameNavigated", params_navigated).code());
248  ASSERT_NO_FATAL_FAILURE(AssertPendingState(&tracker, "subframe", true));
249  params_navigated.Clear();
250  ASSERT_EQ(
251      kOk,
252      tracker.OnEvent(&client, "Page.frameNavigated", params_navigated).code());
253  ASSERT_NO_FATAL_FAILURE(AssertPendingState(&tracker, "subframe", false));
254}
255
256namespace {
257
258class FailToEvalScriptDevToolsClient : public StubDevToolsClient {
259 public:
260  virtual ~FailToEvalScriptDevToolsClient() {}
261
262  virtual Status SendCommandAndGetResult(
263      const std::string& method,
264      const base::DictionaryValue& params,
265      scoped_ptr<base::DictionaryValue>* result) OVERRIDE {
266    EXPECT_STREQ("Runtime.evaluate", method.c_str());
267    return Status(kUnknownError, "failed to eval script");
268  }
269};
270
271}  // namespace
272
273TEST(NavigationTracker, UnknownStateFailsToDetermineState) {
274  FailToEvalScriptDevToolsClient client;
275  BrowserInfo browser_info;
276  NavigationTracker tracker(&client, &browser_info);
277  bool is_pending;
278  ASSERT_EQ(kUnknownError,
279            tracker.IsPendingNavigation("f", &is_pending).code());
280}
281
282namespace {
283
284class DeterminingLoadStateDevToolsClient : public StubDevToolsClient {
285 public:
286  DeterminingLoadStateDevToolsClient(
287      bool is_loading,
288      const std::string& send_event_first,
289      base::DictionaryValue* send_event_first_params)
290      : is_loading_(is_loading),
291        send_event_first_(send_event_first),
292        send_event_first_params_(send_event_first_params) {}
293
294  virtual ~DeterminingLoadStateDevToolsClient() {}
295
296  virtual Status SendCommandAndGetResult(
297      const std::string& method,
298      const base::DictionaryValue& params,
299      scoped_ptr<base::DictionaryValue>* result) OVERRIDE {
300    if (send_event_first_.length()) {
301      Status status = listeners_.front()
302          ->OnEvent(this, send_event_first_, *send_event_first_params_);
303      if (status.IsError())
304        return status;
305    }
306
307    base::DictionaryValue result_dict;
308    result_dict.SetBoolean("result.value", is_loading_);
309    result->reset(result_dict.DeepCopy());
310    return Status(kOk);
311  }
312
313 private:
314  bool is_loading_;
315  std::string send_event_first_;
316  base::DictionaryValue* send_event_first_params_;
317};
318
319}  // namespace
320
321TEST(NavigationTracker, UnknownStateForcesStart) {
322  base::DictionaryValue params;
323  DeterminingLoadStateDevToolsClient client(true, std::string(), &params);
324  BrowserInfo browser_info;
325  NavigationTracker tracker(&client, &browser_info);
326  ASSERT_NO_FATAL_FAILURE(AssertPendingState(&tracker, "f", true));
327}
328
329TEST(NavigationTracker, UnknownStateForcesStartReceivesStop) {
330  base::DictionaryValue params;
331  params.SetString("frameId", "f");
332  DeterminingLoadStateDevToolsClient client(
333      true, "Page.frameStoppedLoading", &params);
334  BrowserInfo browser_info;
335  NavigationTracker tracker(&client, &browser_info);
336  ASSERT_NO_FATAL_FAILURE(AssertPendingState(&tracker, "f", false));
337}
338
339TEST(NavigationTracker, OnSuccessfulNavigate) {
340  base::DictionaryValue params;
341  params.SetString("frameId", "f");
342  DeterminingLoadStateDevToolsClient client(
343      true, "Page.frameStoppedLoading", &params);
344  BrowserInfo browser_info;
345  NavigationTracker tracker(
346      &client, NavigationTracker::kNotLoading, &browser_info);
347  tracker.OnCommandSuccess(&client, "Page.navigate");
348  ASSERT_NO_FATAL_FAILURE(AssertPendingState(&tracker, "f", false));
349}
350
351TEST(NavigationTracker, OnSuccessfulNavigateStillWaiting) {
352  base::DictionaryValue params;
353  params.SetString("frameId", "f");
354  DeterminingLoadStateDevToolsClient client(true, std::string(), &params);
355  BrowserInfo browser_info;
356  NavigationTracker tracker(
357      &client, NavigationTracker::kNotLoading, &browser_info);
358  tracker.OnCommandSuccess(&client, "Page.navigate");
359  ASSERT_NO_FATAL_FAILURE(AssertPendingState(&tracker, "f", true));
360}
361