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