1// Copyright (C) 2021 The Android Open Source Project 2// 3// Licensed under the Apache License, Version 2.0 (the "License"); 4// you may not use this file except in compliance with the License. 5// You may obtain a copy of the License at 6// 7// http://www.apache.org/licenses/LICENSE-2.0 8// 9// Unless required by applicable law or agreed to in writing, software 10// distributed under the License is distributed on an "AS IS" BASIS, 11// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12// See the License for the specific language governing permissions and 13// limitations under the License. 14 15import fs from 'fs'; 16import path from 'path'; 17import {Browser, Page} from 'puppeteer'; 18 19import {assertExists} from '../base/logging'; 20 21import { 22 compareScreenshots, 23 failIfTraceProcessorHttpdIsActive, 24 getTestTracePath, 25 waitForPerfettoIdle, 26} from './perfetto_ui_test_helper'; 27 28declare let global: {__BROWSER__: Browser}; 29const browser = assertExists(global.__BROWSER__); 30const expectedScreenshotPath = path.join('test', 'data', 'ui-screenshots'); 31const tmpDir = path.resolve('./ui-test-artifacts'); 32const reportPath = path.join(tmpDir, 'report.txt'); 33 34async function getPage(): Promise<Page> { 35 const pages = await browser.pages(); 36 expect(pages.length).toBe(1); 37 return pages[pages.length - 1]; 38} 39 40// Executed once at the beginning of the test. Navigates to the UI. 41beforeAll(async () => { 42 await failIfTraceProcessorHttpdIsActive(); 43 jest.setTimeout(60000); 44 const page = await getPage(); 45 await page.setViewport({width: 1920, height: 1080}); 46 47 // Empty the file with collected screenshot diffs 48 fs.writeFileSync(reportPath, ''); 49}); 50 51// After each test (regardless of nesting) capture a screenshot named after the 52// test('') name and compare the screenshot with the expected one in 53// /test/data/ui-screenshots. 54afterEach(async () => { 55 let testName = expect.getState().currentTestName; 56 testName = testName.replace(/[^a-z0-9-]/gim, '_').toLowerCase(); 57 const page = await getPage(); 58 59 const screenshotName = `ui-${testName}.png`; 60 const actualFilename = path.join(tmpDir, screenshotName); 61 const expectedFilename = path.join(expectedScreenshotPath, screenshotName); 62 await page.screenshot({path: actualFilename}); 63 const rebaseline = process.env['PERFETTO_UI_TESTS_REBASELINE'] === '1'; 64 if (rebaseline) { 65 console.log('Saving reference screenshot into', expectedFilename); 66 fs.copyFileSync(actualFilename, expectedFilename); 67 } else { 68 await compareScreenshots(reportPath, actualFilename, expectedFilename); 69 } 70}); 71 72describe('android_trace_30s', () => { 73 let page: Page; 74 75 beforeAll(async () => { 76 page = await getPage(); 77 await page.goto('http://localhost:10000/?testing=1'); 78 await waitForPerfettoIdle(page); 79 }); 80 81 test('load', async () => { 82 const file = await page.waitForSelector('input.trace_file'); 83 const tracePath = getTestTracePath('example_android_trace_30s.pb'); 84 assertExists(file).uploadFile(tracePath); 85 await waitForPerfettoIdle(page); 86 }); 87 88 test('expand_camera', async () => { 89 await page.click('.pf-overlay'); 90 await page.click('h1[title="com.google.android.GoogleCamera 5506"]'); 91 await page.evaluate(() => { 92 document.querySelector('.scrolling-panel-container')!.scrollTo(0, 400); 93 }); 94 await waitForPerfettoIdle(page); 95 }); 96}); 97 98describe('chrome_rendering_desktop', () => { 99 let page: Page; 100 101 beforeAll(async () => { 102 page = await getPage(); 103 await page.goto('http://localhost:10000/?testing=1'); 104 await waitForPerfettoIdle(page); 105 }); 106 107 test('load', async () => { 108 const page = await getPage(); 109 const file = await page.waitForSelector('input.trace_file'); 110 const tracePath = getTestTracePath('chrome_rendering_desktop.pftrace'); 111 assertExists(file).uploadFile(tracePath); 112 await waitForPerfettoIdle(page); 113 }); 114 115 test('expand_browser_proc', async () => { 116 const page = await getPage(); 117 await page.click('.pf-overlay'); 118 await page.click('h1[title="Browser 12685"]'); 119 await waitForPerfettoIdle(page); 120 }); 121 122 test('select_slice_with_flows', async () => { 123 const page = await getPage(); 124 const searchInput = '.omnibox input'; 125 await page.focus(searchInput); 126 await page.keyboard.type('GenerateRenderPass'); 127 await waitForPerfettoIdle(page); 128 for (let i = 0; i < 3; i++) { 129 await page.keyboard.type('\n'); 130 } 131 await waitForPerfettoIdle(page); 132 await page.focus('canvas'); 133 await page.keyboard.type('f'); // Zoom to selection 134 await waitForPerfettoIdle(page); 135 }); 136}); 137 138// Tests that chrome traces with missing process/thread names still open 139// correctly in the UI. 140describe('chrome_missing_track_names', () => { 141 let page: Page; 142 143 beforeAll(async () => { 144 page = await getPage(); 145 await page.goto('http://localhost:10000/?testing=1'); 146 await waitForPerfettoIdle(page); 147 }); 148 149 test('load', async () => { 150 const page = await getPage(); 151 const file = await page.waitForSelector('input.trace_file'); 152 const tracePath = getTestTracePath('chrome_missing_track_names.pb.gz'); 153 assertExists(file).uploadFile(tracePath); 154 await waitForPerfettoIdle(page); 155 }); 156}); 157 158describe('routing', () => { 159 describe('open_two_traces_then_go_back', () => { 160 let page: Page; 161 162 beforeAll(async () => { 163 page = await getPage(); 164 await page.goto('http://localhost:10000/?testing=1'); 165 await waitForPerfettoIdle(page); 166 }); 167 168 test('open_first_trace_from_url', async () => { 169 await page.goto( 170 'http://localhost:10000/?testing=1/#!/?url=http://localhost:10000/test/data/chrome_memory_snapshot.pftrace', 171 ); 172 await waitForPerfettoIdle(page); 173 }); 174 175 test('open_second_trace_from_url', async () => { 176 await page.goto( 177 'http://localhost:10000/?testing=1#!/?url=http://localhost:10000/test/data/chrome_scroll_without_vsync.pftrace', 178 ); 179 await waitForPerfettoIdle(page); 180 }); 181 182 test('access_subpage_then_go_back', async () => { 183 await waitForPerfettoIdle(page); 184 await page.goto( 185 'http://localhost:10000/?testing=1/#!/metrics?local_cache_key=76c25a80-25dd-1eb7-2246-d7b3c7a10f91', 186 ); 187 await page.goBack(); 188 await waitForPerfettoIdle(page); 189 }); 190 }); 191 192 describe('start_from_no_trace', () => { 193 let page: Page; 194 195 beforeAll(async () => { 196 page = await getPage(); 197 await page.goto('about:blank'); 198 }); 199 200 test('go_to_page_with_no_trace', async () => { 201 await page.goto('http://localhost:10000/?testing=1#!/info'); 202 await waitForPerfettoIdle(page); 203 }); 204 205 test('open_trace ', async () => { 206 await page.goto( 207 'http://localhost:10000/?testing=1#!/viewer?local_cache_key=76c25a80-25dd-1eb7-2246-d7b3c7a10f91', 208 ); 209 await waitForPerfettoIdle(page); 210 }); 211 212 test('refresh', async () => { 213 await page.reload(); 214 await waitForPerfettoIdle(page); 215 }); 216 217 test('open_second_trace', async () => { 218 await page.goto( 219 'http://localhost:10000/?testing=1#!/viewer?local_cache_key=00000000-0000-0000-e13c-bd7db4ff646f', 220 ); 221 await waitForPerfettoIdle(page); 222 223 // click on the 'Continue' button in the interstitial 224 await page.click('[id="trace_id_open"]'); 225 await waitForPerfettoIdle(page); 226 }); 227 228 test('go_back_to_first_trace', async () => { 229 await page.goBack(); 230 await waitForPerfettoIdle(page); 231 // click on the 'Continue' button in the interstitial 232 await page.click('[id="trace_id_open"]'); 233 await waitForPerfettoIdle(page); 234 }); 235 236 test('open_invalid_trace', async () => { 237 await page.goto( 238 'http://localhost:10000/?testing=1#!/viewer?local_cache_key=invalid', 239 ); 240 await waitForPerfettoIdle(page); 241 }); 242 }); 243 244 describe('navigate', () => { 245 let page: Page; 246 247 beforeAll(async () => { 248 page = await getPage(); 249 await page.goto('http://localhost:10000/?testing=1'); 250 await waitForPerfettoIdle(page); 251 }); 252 253 test('open_trace_from_url', async () => { 254 await page.goto( 255 'http://localhost:10000/?testing=1/#!/?url=http://localhost:10000/test/data/chrome_memory_snapshot.pftrace', 256 ); 257 await waitForPerfettoIdle(page); 258 }); 259 260 test('navigate_back_and_forward', async () => { 261 await page.click('[id="info_and_stats"]'); 262 await waitForPerfettoIdle(page); 263 await page.click('[id="metrics"]'); 264 await waitForPerfettoIdle(page); 265 await page.goBack(); 266 await waitForPerfettoIdle(page); 267 await page.goBack(); 268 await waitForPerfettoIdle(page); 269 await page.goForward(); 270 await waitForPerfettoIdle(page); 271 await page.goForward(); 272 await waitForPerfettoIdle(page); 273 }); 274 }); 275 276 test('open_trace_and_go_back_to_landing_page', async () => { 277 const page = await getPage(); 278 await page.goto('http://localhost:10000/?testing=1'); 279 await page.goto( 280 'http://localhost:10000/?testing=1#!/viewer?local_cache_key=76c25a80-25dd-1eb7-2246-d7b3c7a10f91', 281 ); 282 await waitForPerfettoIdle(page); 283 await page.goBack(); 284 await waitForPerfettoIdle(page); 285 }); 286 287 test('open_invalid_trace_from_blank_page', async () => { 288 const page = await getPage(); 289 await page.goto('about:blank'); 290 await page.goto( 291 'http://localhost:10000/?testing=1#!/viewer?local_cache_key=invalid', 292 ); 293 await waitForPerfettoIdle(page); 294 }); 295}); 296 297// Regression test for b/235335853. 298describe('modal_dialog', () => { 299 let page: Page; 300 301 beforeAll(async () => { 302 page = await getPage(); 303 await page.goto('http://localhost:10000/?testing=1'); 304 await waitForPerfettoIdle(page); 305 }); 306 307 test('show_dialog_1', async () => { 308 await page.click('#keyboard_shortcuts'); 309 await waitForPerfettoIdle(page); 310 }); 311 312 test('dismiss_1', async () => { 313 await page.keyboard.press('Escape'); 314 await waitForPerfettoIdle(page); 315 }); 316 317 test('switch_page_no_dialog', async () => { 318 await page.click('#record_new_trace'); 319 await waitForPerfettoIdle(page); 320 }); 321 322 test('show_dialog_2', async () => { 323 await page.click('#keyboard_shortcuts'); 324 await waitForPerfettoIdle(page); 325 }); 326 327 test('dismiss_2', async () => { 328 await page.keyboard.press('Escape'); 329 await waitForPerfettoIdle(page); 330 }); 331}); 332 333describe('features', () => { 334 let page: Page; 335 336 beforeAll(async () => { 337 page = await getPage(); 338 await page.goto('http://localhost:10000/?testing=1'); 339 await waitForPerfettoIdle(page); 340 }); 341 342 // Test that we show a (debuggable) chip next to tracks for debuggable apps. 343 // Regression test for aosp/3106008 . 344 test('track_debuggable_chip', async () => { 345 const page = await getPage(); 346 await page.goto( 347 'http://localhost:10000/?testing=1/#!/?url=http://localhost:10000/test/data/api32_startup_warm.perfetto-trace', 348 ); 349 await waitForPerfettoIdle(page); 350 await page.hover( 351 'h1[title="androidx.benchmark.integration.macrobenchmark.test 7527"]', 352 ); 353 await waitForPerfettoIdle(page); 354 }); 355}); 356