1 // Copyright 2020 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 "chrome/browser/page_load_metrics/integration_tests/metric_integration_test.h"
6 
7 #include "base/json/json_string_value_serializer.h"
8 #include "base/strings/strcat.h"
9 #include "base/test/trace_event_analyzer.h"
10 #include "build/build_config.h"
11 #include "chrome/test/base/ui_test_utils.h"
12 #include "content/public/test/browser_test.h"
13 #include "services/metrics/public/cpp/ukm_builders.h"
14 
15 using trace_analyzer::Query;
16 using trace_analyzer::TraceAnalyzer;
17 using trace_analyzer::TraceEvent;
18 using trace_analyzer::TraceEventVector;
19 using ukm::builders::PageLoad;
20 
21 namespace {
22 
ValidateCandidate(int expected_size,const TraceEvent & event)23 void ValidateCandidate(int expected_size, const TraceEvent& event) {
24   std::unique_ptr<base::Value> data;
25   ASSERT_TRUE(event.GetArgAsValue("data", &data));
26 
27   const base::Optional<int> traced_size = data->FindIntKey("size");
28   ASSERT_TRUE(traced_size.has_value());
29   EXPECT_EQ(traced_size.value(), expected_size);
30 
31   const base::Optional<bool> traced_main_frame_flag =
32       data->FindBoolKey("isMainFrame");
33   ASSERT_TRUE(traced_main_frame_flag.has_value());
34   EXPECT_TRUE(traced_main_frame_flag.value());
35 }
36 
GetCandidateIndex(const TraceEvent & event)37 int GetCandidateIndex(const TraceEvent& event) {
38   std::unique_ptr<base::Value> data = event.GetKnownArgAsValue("data");
39   base::Optional<int> candidate_idx = data->FindIntKey("candidateIndex");
40   DCHECK(candidate_idx.has_value()) << "couldn't find 'candidateIndex'";
41 
42   return candidate_idx.value();
43 }
44 
compare_candidate_index(const TraceEvent * lhs,const TraceEvent * rhs)45 bool compare_candidate_index(const TraceEvent* lhs, const TraceEvent* rhs) {
46   return GetCandidateIndex(*lhs) < GetCandidateIndex(*rhs);
47 }
48 
ValidateTraceEvents(std::unique_ptr<TraceAnalyzer> analyzer)49 void ValidateTraceEvents(std::unique_ptr<TraceAnalyzer> analyzer) {
50   TraceEventVector events;
51   analyzer->FindEvents(Query::EventNameIs("largestContentfulPaint::Candidate"),
52                        &events);
53   EXPECT_EQ(3ul, events.size());
54   std::sort(events.begin(), events.end(), compare_candidate_index);
55 
56   // LCP_0 uses green-16x16.png, of size 16 x 16.
57   ValidateCandidate(16 * 16, *events[0]);
58   // LCP_1 uses blue96x96.png, of size 96 x 96.
59   ValidateCandidate(96 * 96, *events[1]);
60   // LCP_2 uses green-256x256.png, of size 16 x 16.
61   ValidateCandidate(256 * 256, *events[2]);
62 }
63 
64 }  // namespace
65 
IN_PROC_BROWSER_TEST_F(MetricIntegrationTest,LargestContentfulPaint)66 IN_PROC_BROWSER_TEST_F(MetricIntegrationTest, LargestContentfulPaint) {
67   Start();
68   StartTracing({"loading"});
69   Load("/largest_contentful_paint.html");
70 
71   // The test harness serves files from something like http://example.com:34777
72   // but the port number can vary. Extract the 'window.origin' property so we
73   // can compare encountered URLs to expected values.
74   const std::string window_origin =
75       EvalJs(web_contents(), "window.origin").ExtractString();
76   const std::string image_1_url_expected =
77       base::StrCat({window_origin, "/images/green-16x16.png"});
78   const std::string image_2_url_expected =
79       base::StrCat({window_origin, "/images/blue96x96.png"});
80   const std::string image_3_url_expected =
81       base::StrCat({window_origin, "/images/green-256x256.png"});
82 
83   content::EvalJsResult result = EvalJs(web_contents(), "run_test()");
84   EXPECT_EQ("", result.error);
85 
86   // Verify that the JS API yielded three LCP reports. Note that, as we resolve
87   // https://github.com/WICG/largest-contentful-paint/issues/41, this test may
88   // need to be updated to reflect new semantics.
89   const auto& list = result.value.GetList();
90   const std::string expected_url[3] = {
91       image_1_url_expected, image_2_url_expected, image_3_url_expected};
92   base::Optional<double> lcp_timestamps[3];
93   for (size_t i = 0; i < 3; i++) {
94     const std::string* url = list[i].FindStringPath("url");
95     EXPECT_TRUE(url);
96     EXPECT_EQ(*url, expected_url[i]);
97     lcp_timestamps[i] = list[i].FindDoublePath("time");
98     EXPECT_TRUE(lcp_timestamps[i].has_value());
99   }
100   EXPECT_LT(lcp_timestamps[0], lcp_timestamps[1])
101       << "The first LCP report should be before the second";
102   EXPECT_LT(lcp_timestamps[1], lcp_timestamps[2])
103       << "The second LCP report should be before the third";
104 
105   // Need to navigate away from the test html page to force metrics to get
106   // flushed/synced.
107   ui_test_utils::NavigateToURL(browser(), GURL("about:blank"));
108 
109   // Check Trace Events.
110   ValidateTraceEvents(StopTracingAndAnalyze());
111 
112   // Check UKM.
113   // Since UKM rounds to an integer while the JS API returns a double, we'll
114   // assert that the UKM and JS values are within 1.0 of each other. Comparing
115   // with strict equality could round incorrectly and introduce flakiness into
116   // the test.
117   ExpectUKMPageLoadMetricNear(
118       PageLoad::kPaintTiming_NavigationToLargestContentfulPaintName,
119       lcp_timestamps[2].value(), 1.0);
120   ExpectUKMPageLoadMetricNear(
121       PageLoad::kPaintTiming_NavigationToLargestContentfulPaint_MainFrameName,
122       lcp_timestamps[2].value(), 1.0);
123 
124   // Check UMA.
125   // Similar to UKM, rounding could introduce flakiness, so use helper to
126   // compare near.
127   ExpectUniqueUMAPageLoadMetricNear(
128       "PageLoad.PaintTiming.NavigationToLargestContentfulPaint",
129       lcp_timestamps[2].value());
130   ExpectUniqueUMAPageLoadMetricNear(
131       "PageLoad.PaintTiming.NavigationToLargestContentfulPaint.MainFrame",
132       lcp_timestamps[2].value());
133 }
134 
135 // TODO(crbug.com/1135527): Flaky.
IN_PROC_BROWSER_TEST_F(MetricIntegrationTest,DISABLED_LargestContentfulPaint_SubframeInput)136 IN_PROC_BROWSER_TEST_F(MetricIntegrationTest,
137                        DISABLED_LargestContentfulPaint_SubframeInput) {
138   Start();
139   Load("/lcp_subframe_input.html");
140   auto* sub = ChildFrameAt(web_contents()->GetMainFrame(), 0);
141   EXPECT_EQ(EvalJs(sub, "test_step_1()").value.GetString(), "green-16x16.png");
142 
143   content::SimulateMouseClickAt(web_contents(), 0,
144                                 blink::WebMouseEvent::Button::kLeft,
145                                 gfx::Point(100, 100));
146 
147   EXPECT_EQ(EvalJs(sub, "test_step_2()").value.GetString(), "green-16x16.png");
148 }
149