xref: /aosp_15_r20/development/tools/winscope/src/test/e2e/utils.ts (revision 90c8c64db3049935a07c6143d7fd006e26f8ecca)
1*90c8c64dSAndroid Build Coastguard Worker/*
2*90c8c64dSAndroid Build Coastguard Worker * Copyright (C) 2022 The Android Open Source Project
3*90c8c64dSAndroid Build Coastguard Worker *
4*90c8c64dSAndroid Build Coastguard Worker * Licensed under the Apache License, Version 2.0 (the "License");
5*90c8c64dSAndroid Build Coastguard Worker * you may not use this file except in compliance with the License.
6*90c8c64dSAndroid Build Coastguard Worker * You may obtain a copy of the License at
7*90c8c64dSAndroid Build Coastguard Worker *
8*90c8c64dSAndroid Build Coastguard Worker *      http://www.apache.org/licenses/LICENSE-2.0
9*90c8c64dSAndroid Build Coastguard Worker *
10*90c8c64dSAndroid Build Coastguard Worker * Unless required by applicable law or agreed to in writing, software
11*90c8c64dSAndroid Build Coastguard Worker * distributed under the License is distributed on an "AS IS" BASIS,
12*90c8c64dSAndroid Build Coastguard Worker * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13*90c8c64dSAndroid Build Coastguard Worker * See the License for the specific language governing permissions and
14*90c8c64dSAndroid Build Coastguard Worker * limitations under the License.
15*90c8c64dSAndroid Build Coastguard Worker */
16*90c8c64dSAndroid Build Coastguard Workerimport * as path from 'path';
17*90c8c64dSAndroid Build Coastguard Workerimport {browser, by, element, ElementFinder, protractor} from 'protractor';
18*90c8c64dSAndroid Build Coastguard Worker
19*90c8c64dSAndroid Build Coastguard Workerclass E2eTestUtils {
20*90c8c64dSAndroid Build Coastguard Worker  static readonly WINSCOPE_URL = 'http://localhost:8080';
21*90c8c64dSAndroid Build Coastguard Worker  static readonly REMOTE_TOOL_MOCK_URL = 'http://localhost:8081';
22*90c8c64dSAndroid Build Coastguard Worker
23*90c8c64dSAndroid Build Coastguard Worker  static async beforeEach(defaultTimeoutMs: number) {
24*90c8c64dSAndroid Build Coastguard Worker    await browser.manage().timeouts().implicitlyWait(defaultTimeoutMs);
25*90c8c64dSAndroid Build Coastguard Worker    await E2eTestUtils.checkServerIsUp('Winscope', E2eTestUtils.WINSCOPE_URL);
26*90c8c64dSAndroid Build Coastguard Worker    await browser.driver.manage().window().maximize();
27*90c8c64dSAndroid Build Coastguard Worker  }
28*90c8c64dSAndroid Build Coastguard Worker
29*90c8c64dSAndroid Build Coastguard Worker  static async checkServerIsUp(name: string, url: string) {
30*90c8c64dSAndroid Build Coastguard Worker    try {
31*90c8c64dSAndroid Build Coastguard Worker      await browser.get(url);
32*90c8c64dSAndroid Build Coastguard Worker    } catch (error) {
33*90c8c64dSAndroid Build Coastguard Worker      fail(`${name} server (${url}) looks down. Did you start it?`);
34*90c8c64dSAndroid Build Coastguard Worker    }
35*90c8c64dSAndroid Build Coastguard Worker  }
36*90c8c64dSAndroid Build Coastguard Worker
37*90c8c64dSAndroid Build Coastguard Worker  static async loadTraceAndCheckViewer(
38*90c8c64dSAndroid Build Coastguard Worker    fixturePath: string,
39*90c8c64dSAndroid Build Coastguard Worker    viewerTabTitle: string,
40*90c8c64dSAndroid Build Coastguard Worker    viewerSelector: string,
41*90c8c64dSAndroid Build Coastguard Worker  ) {
42*90c8c64dSAndroid Build Coastguard Worker    await E2eTestUtils.uploadFixture(fixturePath);
43*90c8c64dSAndroid Build Coastguard Worker    await E2eTestUtils.closeSnackBar();
44*90c8c64dSAndroid Build Coastguard Worker    await E2eTestUtils.clickViewTracesButton();
45*90c8c64dSAndroid Build Coastguard Worker    await E2eTestUtils.clickViewerTabButton(viewerTabTitle);
46*90c8c64dSAndroid Build Coastguard Worker
47*90c8c64dSAndroid Build Coastguard Worker    const viewerPresent = await element(by.css(viewerSelector)).isPresent();
48*90c8c64dSAndroid Build Coastguard Worker    expect(viewerPresent).toBeTruthy();
49*90c8c64dSAndroid Build Coastguard Worker  }
50*90c8c64dSAndroid Build Coastguard Worker
51*90c8c64dSAndroid Build Coastguard Worker  static async loadBugReport(defaulttimeMs: number) {
52*90c8c64dSAndroid Build Coastguard Worker    await E2eTestUtils.uploadFixture('bugreports/bugreport_stripped.zip');
53*90c8c64dSAndroid Build Coastguard Worker    await E2eTestUtils.checkHasLoadedTracesFromBugReport();
54*90c8c64dSAndroid Build Coastguard Worker    expect(await E2eTestUtils.areMessagesEmitted(defaulttimeMs)).toBeTruthy();
55*90c8c64dSAndroid Build Coastguard Worker    await E2eTestUtils.checkEmitsUnsupportedFileFormatMessages();
56*90c8c64dSAndroid Build Coastguard Worker    await E2eTestUtils.checkEmitsOldDataMessages();
57*90c8c64dSAndroid Build Coastguard Worker    await E2eTestUtils.closeSnackBar();
58*90c8c64dSAndroid Build Coastguard Worker  }
59*90c8c64dSAndroid Build Coastguard Worker
60*90c8c64dSAndroid Build Coastguard Worker  static async areMessagesEmitted(defaultTimeoutMs: number): Promise<boolean> {
61*90c8c64dSAndroid Build Coastguard Worker    // Messages are emitted quickly. There is no Need to wait for the entire
62*90c8c64dSAndroid Build Coastguard Worker    // default timeout to understand whether the messages where emitted or not.
63*90c8c64dSAndroid Build Coastguard Worker    await browser.manage().timeouts().implicitlyWait(1000);
64*90c8c64dSAndroid Build Coastguard Worker    const emitted = await element(by.css('snack-bar')).isPresent();
65*90c8c64dSAndroid Build Coastguard Worker    await browser.manage().timeouts().implicitlyWait(defaultTimeoutMs);
66*90c8c64dSAndroid Build Coastguard Worker    return emitted;
67*90c8c64dSAndroid Build Coastguard Worker  }
68*90c8c64dSAndroid Build Coastguard Worker
69*90c8c64dSAndroid Build Coastguard Worker  static async clickViewTracesButton() {
70*90c8c64dSAndroid Build Coastguard Worker    const button = element(by.css('.load-btn'));
71*90c8c64dSAndroid Build Coastguard Worker    await button.click();
72*90c8c64dSAndroid Build Coastguard Worker  }
73*90c8c64dSAndroid Build Coastguard Worker
74*90c8c64dSAndroid Build Coastguard Worker  static async clickClearAllButton() {
75*90c8c64dSAndroid Build Coastguard Worker    const button = element(by.css('.clear-all-btn'));
76*90c8c64dSAndroid Build Coastguard Worker    await button.click();
77*90c8c64dSAndroid Build Coastguard Worker  }
78*90c8c64dSAndroid Build Coastguard Worker
79*90c8c64dSAndroid Build Coastguard Worker  static async clickCloseIcon() {
80*90c8c64dSAndroid Build Coastguard Worker    const button = element.all(by.css('.uploaded-files button')).first();
81*90c8c64dSAndroid Build Coastguard Worker    await button.click();
82*90c8c64dSAndroid Build Coastguard Worker  }
83*90c8c64dSAndroid Build Coastguard Worker
84*90c8c64dSAndroid Build Coastguard Worker  static async clickDownloadTracesButton() {
85*90c8c64dSAndroid Build Coastguard Worker    const button = element(by.css('.save-button'));
86*90c8c64dSAndroid Build Coastguard Worker    await button.click();
87*90c8c64dSAndroid Build Coastguard Worker  }
88*90c8c64dSAndroid Build Coastguard Worker
89*90c8c64dSAndroid Build Coastguard Worker  static async clickUploadNewButton() {
90*90c8c64dSAndroid Build Coastguard Worker    const button = element(by.css('.upload-new'));
91*90c8c64dSAndroid Build Coastguard Worker    await button.click();
92*90c8c64dSAndroid Build Coastguard Worker  }
93*90c8c64dSAndroid Build Coastguard Worker
94*90c8c64dSAndroid Build Coastguard Worker  static async closeSnackBar() {
95*90c8c64dSAndroid Build Coastguard Worker    const closeButton = element(by.css('.snack-bar-action'));
96*90c8c64dSAndroid Build Coastguard Worker    const isPresent = await closeButton.isPresent();
97*90c8c64dSAndroid Build Coastguard Worker    if (isPresent) {
98*90c8c64dSAndroid Build Coastguard Worker      await closeButton.click();
99*90c8c64dSAndroid Build Coastguard Worker    }
100*90c8c64dSAndroid Build Coastguard Worker  }
101*90c8c64dSAndroid Build Coastguard Worker
102*90c8c64dSAndroid Build Coastguard Worker  static async clickViewerTabButton(title: string) {
103*90c8c64dSAndroid Build Coastguard Worker    const tabs: ElementFinder[] = await element.all(by.css('trace-view .tab'));
104*90c8c64dSAndroid Build Coastguard Worker    for (const tab of tabs) {
105*90c8c64dSAndroid Build Coastguard Worker      const tabTitle = await tab.getText();
106*90c8c64dSAndroid Build Coastguard Worker      if (tabTitle.includes(title)) {
107*90c8c64dSAndroid Build Coastguard Worker        await tab.click();
108*90c8c64dSAndroid Build Coastguard Worker        return;
109*90c8c64dSAndroid Build Coastguard Worker      }
110*90c8c64dSAndroid Build Coastguard Worker    }
111*90c8c64dSAndroid Build Coastguard Worker    throw new Error(`could not find tab corresponding to ${title}`);
112*90c8c64dSAndroid Build Coastguard Worker  }
113*90c8c64dSAndroid Build Coastguard Worker
114*90c8c64dSAndroid Build Coastguard Worker  static async checkTimelineTraceSelector(trace: {
115*90c8c64dSAndroid Build Coastguard Worker    icon: string;
116*90c8c64dSAndroid Build Coastguard Worker    color: string;
117*90c8c64dSAndroid Build Coastguard Worker  }) {
118*90c8c64dSAndroid Build Coastguard Worker    const traceSelector = element(by.css('#trace-selector'));
119*90c8c64dSAndroid Build Coastguard Worker    const text = await traceSelector.getText();
120*90c8c64dSAndroid Build Coastguard Worker    expect(text).toContain(trace.icon);
121*90c8c64dSAndroid Build Coastguard Worker
122*90c8c64dSAndroid Build Coastguard Worker    const icons = await element.all(by.css('.shown-selection .mat-icon'));
123*90c8c64dSAndroid Build Coastguard Worker    const iconColors: string[] = [];
124*90c8c64dSAndroid Build Coastguard Worker    for (const icon of icons) {
125*90c8c64dSAndroid Build Coastguard Worker      iconColors.push(await icon.getCssValue('color'));
126*90c8c64dSAndroid Build Coastguard Worker    }
127*90c8c64dSAndroid Build Coastguard Worker    expect(
128*90c8c64dSAndroid Build Coastguard Worker      iconColors.some((iconColor) => iconColor === trace.color),
129*90c8c64dSAndroid Build Coastguard Worker    ).toBeTruthy();
130*90c8c64dSAndroid Build Coastguard Worker  }
131*90c8c64dSAndroid Build Coastguard Worker
132*90c8c64dSAndroid Build Coastguard Worker  static async checkInitialRealTimestamp(timestamp: string) {
133*90c8c64dSAndroid Build Coastguard Worker    await E2eTestUtils.changeRealTimestampInWinscope(timestamp);
134*90c8c64dSAndroid Build Coastguard Worker    await E2eTestUtils.checkWinscopeRealTimestamp(timestamp.slice(12));
135*90c8c64dSAndroid Build Coastguard Worker    const prevEntryButton = element(by.css('#prev_entry_button'));
136*90c8c64dSAndroid Build Coastguard Worker    const isDisabled = await prevEntryButton.getAttribute('disabled');
137*90c8c64dSAndroid Build Coastguard Worker    expect(isDisabled).toEqual('true');
138*90c8c64dSAndroid Build Coastguard Worker  }
139*90c8c64dSAndroid Build Coastguard Worker
140*90c8c64dSAndroid Build Coastguard Worker  static async checkFinalRealTimestamp(timestamp: string) {
141*90c8c64dSAndroid Build Coastguard Worker    await E2eTestUtils.changeRealTimestampInWinscope(timestamp);
142*90c8c64dSAndroid Build Coastguard Worker    await E2eTestUtils.checkWinscopeRealTimestamp(timestamp.slice(12));
143*90c8c64dSAndroid Build Coastguard Worker    const nextEntryButton = element(by.css('#next_entry_button'));
144*90c8c64dSAndroid Build Coastguard Worker    const isDisabled = await nextEntryButton.getAttribute('disabled');
145*90c8c64dSAndroid Build Coastguard Worker    expect(isDisabled).toEqual('true');
146*90c8c64dSAndroid Build Coastguard Worker  }
147*90c8c64dSAndroid Build Coastguard Worker
148*90c8c64dSAndroid Build Coastguard Worker  static async checkWinscopeRealTimestamp(timestamp: string) {
149*90c8c64dSAndroid Build Coastguard Worker    const inputElement = element(by.css('input[name="humanTimeInput"]'));
150*90c8c64dSAndroid Build Coastguard Worker    const value = await inputElement.getAttribute('value');
151*90c8c64dSAndroid Build Coastguard Worker    expect(value).toEqual(timestamp);
152*90c8c64dSAndroid Build Coastguard Worker  }
153*90c8c64dSAndroid Build Coastguard Worker
154*90c8c64dSAndroid Build Coastguard Worker  static async changeRealTimestampInWinscope(newTimestamp: string) {
155*90c8c64dSAndroid Build Coastguard Worker    await E2eTestUtils.updateInputField('', 'humanTimeInput', newTimestamp);
156*90c8c64dSAndroid Build Coastguard Worker  }
157*90c8c64dSAndroid Build Coastguard Worker
158*90c8c64dSAndroid Build Coastguard Worker  static async checkWinscopeNsTimestamp(newTimestamp: string) {
159*90c8c64dSAndroid Build Coastguard Worker    const inputElement = element(by.css('input[name="nsTimeInput"]'));
160*90c8c64dSAndroid Build Coastguard Worker    const valueWithNsSuffix = await inputElement.getAttribute('value');
161*90c8c64dSAndroid Build Coastguard Worker    expect(valueWithNsSuffix).toEqual(newTimestamp + ' ns');
162*90c8c64dSAndroid Build Coastguard Worker  }
163*90c8c64dSAndroid Build Coastguard Worker
164*90c8c64dSAndroid Build Coastguard Worker  static async changeNsTimestampInWinscope(newTimestamp: string) {
165*90c8c64dSAndroid Build Coastguard Worker    await E2eTestUtils.updateInputField('', 'nsTimeInput', newTimestamp);
166*90c8c64dSAndroid Build Coastguard Worker  }
167*90c8c64dSAndroid Build Coastguard Worker
168*90c8c64dSAndroid Build Coastguard Worker  static async filterHierarchy(viewer: string, filterString: string) {
169*90c8c64dSAndroid Build Coastguard Worker    await E2eTestUtils.updateInputField(
170*90c8c64dSAndroid Build Coastguard Worker      `${viewer} hierarchy-view .title-section`,
171*90c8c64dSAndroid Build Coastguard Worker      'filter',
172*90c8c64dSAndroid Build Coastguard Worker      filterString,
173*90c8c64dSAndroid Build Coastguard Worker    );
174*90c8c64dSAndroid Build Coastguard Worker  }
175*90c8c64dSAndroid Build Coastguard Worker
176*90c8c64dSAndroid Build Coastguard Worker  static async updateInputField(
177*90c8c64dSAndroid Build Coastguard Worker    inputFieldSelector: string,
178*90c8c64dSAndroid Build Coastguard Worker    inputFieldName: string,
179*90c8c64dSAndroid Build Coastguard Worker    newInput: string,
180*90c8c64dSAndroid Build Coastguard Worker  ) {
181*90c8c64dSAndroid Build Coastguard Worker    const inputElement = element(
182*90c8c64dSAndroid Build Coastguard Worker      by.css(`${inputFieldSelector} input[name="${inputFieldName}"]`),
183*90c8c64dSAndroid Build Coastguard Worker    );
184*90c8c64dSAndroid Build Coastguard Worker    const inputStringStep1 = newInput.slice(0, -1);
185*90c8c64dSAndroid Build Coastguard Worker    const inputStringStep2 = newInput.slice(-1) + '\r\n';
186*90c8c64dSAndroid Build Coastguard Worker    const script = `document.querySelector("${inputFieldSelector} input[name=\\"${inputFieldName}\\"]").value = "${inputStringStep1}"`;
187*90c8c64dSAndroid Build Coastguard Worker    await browser.executeScript(script);
188*90c8c64dSAndroid Build Coastguard Worker    await inputElement.sendKeys(inputStringStep2);
189*90c8c64dSAndroid Build Coastguard Worker  }
190*90c8c64dSAndroid Build Coastguard Worker
191*90c8c64dSAndroid Build Coastguard Worker  static async selectItemInHierarchy(viewer: string, itemName: string) {
192*90c8c64dSAndroid Build Coastguard Worker    const nodes: ElementFinder[] = await element.all(
193*90c8c64dSAndroid Build Coastguard Worker      by.css(`${viewer} hierarchy-view .node`),
194*90c8c64dSAndroid Build Coastguard Worker    );
195*90c8c64dSAndroid Build Coastguard Worker    for (const node of nodes) {
196*90c8c64dSAndroid Build Coastguard Worker      const id = await node.getAttribute('id');
197*90c8c64dSAndroid Build Coastguard Worker      if (id.includes(itemName)) {
198*90c8c64dSAndroid Build Coastguard Worker        const desc = node.element(by.css('.description'));
199*90c8c64dSAndroid Build Coastguard Worker        await desc.click();
200*90c8c64dSAndroid Build Coastguard Worker        return;
201*90c8c64dSAndroid Build Coastguard Worker      }
202*90c8c64dSAndroid Build Coastguard Worker    }
203*90c8c64dSAndroid Build Coastguard Worker    throw new Error(`could not find item matching ${itemName} in hierarchy`);
204*90c8c64dSAndroid Build Coastguard Worker  }
205*90c8c64dSAndroid Build Coastguard Worker
206*90c8c64dSAndroid Build Coastguard Worker  static async applyStateToHierarchyOptions(
207*90c8c64dSAndroid Build Coastguard Worker    viewerSelector: string,
208*90c8c64dSAndroid Build Coastguard Worker    shouldEnable: boolean,
209*90c8c64dSAndroid Build Coastguard Worker  ) {
210*90c8c64dSAndroid Build Coastguard Worker    const options: ElementFinder[] = await element.all(
211*90c8c64dSAndroid Build Coastguard Worker      by.css(`${viewerSelector} hierarchy-view .view-controls .user-option`),
212*90c8c64dSAndroid Build Coastguard Worker    );
213*90c8c64dSAndroid Build Coastguard Worker    for (const option of options) {
214*90c8c64dSAndroid Build Coastguard Worker      const isEnabled = !(await option.getAttribute('class')).includes(
215*90c8c64dSAndroid Build Coastguard Worker        'not-enabled',
216*90c8c64dSAndroid Build Coastguard Worker      );
217*90c8c64dSAndroid Build Coastguard Worker      if (shouldEnable && !isEnabled) {
218*90c8c64dSAndroid Build Coastguard Worker        await option.click();
219*90c8c64dSAndroid Build Coastguard Worker      } else if (!shouldEnable && isEnabled) {
220*90c8c64dSAndroid Build Coastguard Worker        await option.click();
221*90c8c64dSAndroid Build Coastguard Worker      }
222*90c8c64dSAndroid Build Coastguard Worker    }
223*90c8c64dSAndroid Build Coastguard Worker  }
224*90c8c64dSAndroid Build Coastguard Worker
225*90c8c64dSAndroid Build Coastguard Worker  static async checkItemInPropertiesTree(
226*90c8c64dSAndroid Build Coastguard Worker    viewer: string,
227*90c8c64dSAndroid Build Coastguard Worker    itemName: string,
228*90c8c64dSAndroid Build Coastguard Worker    expectedText: string,
229*90c8c64dSAndroid Build Coastguard Worker  ) {
230*90c8c64dSAndroid Build Coastguard Worker    const nodes = await element.all(by.css(`${viewer} .properties-view .node`));
231*90c8c64dSAndroid Build Coastguard Worker    for (const node of nodes) {
232*90c8c64dSAndroid Build Coastguard Worker      const id: string = await node.getAttribute('id');
233*90c8c64dSAndroid Build Coastguard Worker      if (id === 'node' + itemName) {
234*90c8c64dSAndroid Build Coastguard Worker        const text = await node.getText();
235*90c8c64dSAndroid Build Coastguard Worker        expect(text).toEqual(expectedText);
236*90c8c64dSAndroid Build Coastguard Worker        return;
237*90c8c64dSAndroid Build Coastguard Worker      }
238*90c8c64dSAndroid Build Coastguard Worker    }
239*90c8c64dSAndroid Build Coastguard Worker    throw new Error(`could not find item ${itemName} in properties tree`);
240*90c8c64dSAndroid Build Coastguard Worker  }
241*90c8c64dSAndroid Build Coastguard Worker
242*90c8c64dSAndroid Build Coastguard Worker  static async checkRectLabel(viewer: string, expectedLabel: string) {
243*90c8c64dSAndroid Build Coastguard Worker    const labels = await element.all(
244*90c8c64dSAndroid Build Coastguard Worker      by.css(`${viewer} rects-view .rect-label`),
245*90c8c64dSAndroid Build Coastguard Worker    );
246*90c8c64dSAndroid Build Coastguard Worker
247*90c8c64dSAndroid Build Coastguard Worker    let foundLabel: ElementFinder | undefined;
248*90c8c64dSAndroid Build Coastguard Worker
249*90c8c64dSAndroid Build Coastguard Worker    for (const label of labels) {
250*90c8c64dSAndroid Build Coastguard Worker      const text = await label.getText();
251*90c8c64dSAndroid Build Coastguard Worker      if (text.includes(expectedLabel)) {
252*90c8c64dSAndroid Build Coastguard Worker        foundLabel = label;
253*90c8c64dSAndroid Build Coastguard Worker        break;
254*90c8c64dSAndroid Build Coastguard Worker      }
255*90c8c64dSAndroid Build Coastguard Worker    }
256*90c8c64dSAndroid Build Coastguard Worker
257*90c8c64dSAndroid Build Coastguard Worker    expect(foundLabel).toBeTruthy();
258*90c8c64dSAndroid Build Coastguard Worker  }
259*90c8c64dSAndroid Build Coastguard Worker
260*90c8c64dSAndroid Build Coastguard Worker  static async checkTotalScrollEntries(
261*90c8c64dSAndroid Build Coastguard Worker    viewerSelector: string,
262*90c8c64dSAndroid Build Coastguard Worker    numberOfEntries: number,
263*90c8c64dSAndroid Build Coastguard Worker    scrollToBottom = false,
264*90c8c64dSAndroid Build Coastguard Worker  ) {
265*90c8c64dSAndroid Build Coastguard Worker    if (scrollToBottom) {
266*90c8c64dSAndroid Build Coastguard Worker      const viewport = element(by.css(`${viewerSelector} .scroll`));
267*90c8c64dSAndroid Build Coastguard Worker      let lastId: string | undefined;
268*90c8c64dSAndroid Build Coastguard Worker      let lastScrollEntryItemId = await E2eTestUtils.getLastScrollEntryItemId(
269*90c8c64dSAndroid Build Coastguard Worker        viewerSelector,
270*90c8c64dSAndroid Build Coastguard Worker      );
271*90c8c64dSAndroid Build Coastguard Worker      while (lastId !== lastScrollEntryItemId) {
272*90c8c64dSAndroid Build Coastguard Worker        lastId = lastScrollEntryItemId;
273*90c8c64dSAndroid Build Coastguard Worker        await viewport.sendKeys(protractor.Key.END);
274*90c8c64dSAndroid Build Coastguard Worker        await new Promise<void>((resolve) => setTimeout(resolve, 500));
275*90c8c64dSAndroid Build Coastguard Worker        lastScrollEntryItemId = await E2eTestUtils.getLastScrollEntryItemId(
276*90c8c64dSAndroid Build Coastguard Worker          viewerSelector,
277*90c8c64dSAndroid Build Coastguard Worker        );
278*90c8c64dSAndroid Build Coastguard Worker      }
279*90c8c64dSAndroid Build Coastguard Worker    }
280*90c8c64dSAndroid Build Coastguard Worker    const entries = await element.all(
281*90c8c64dSAndroid Build Coastguard Worker      by.css(`${viewerSelector} .scroll .entry`),
282*90c8c64dSAndroid Build Coastguard Worker    );
283*90c8c64dSAndroid Build Coastguard Worker    expect(await entries[entries.length - 1].getAttribute('item-id')).toEqual(
284*90c8c64dSAndroid Build Coastguard Worker      `${numberOfEntries - 1}`,
285*90c8c64dSAndroid Build Coastguard Worker    );
286*90c8c64dSAndroid Build Coastguard Worker  }
287*90c8c64dSAndroid Build Coastguard Worker
288*90c8c64dSAndroid Build Coastguard Worker  static async getLastScrollEntryItemId(
289*90c8c64dSAndroid Build Coastguard Worker    viewerSelector: string,
290*90c8c64dSAndroid Build Coastguard Worker  ): Promise<string> {
291*90c8c64dSAndroid Build Coastguard Worker    const entries = await element.all(
292*90c8c64dSAndroid Build Coastguard Worker      by.css(`${viewerSelector} .scroll .entry`),
293*90c8c64dSAndroid Build Coastguard Worker    );
294*90c8c64dSAndroid Build Coastguard Worker    return await entries[entries.length - 1].getAttribute('item-id');
295*90c8c64dSAndroid Build Coastguard Worker  }
296*90c8c64dSAndroid Build Coastguard Worker
297*90c8c64dSAndroid Build Coastguard Worker  static async toggleSelectFilterOptions(
298*90c8c64dSAndroid Build Coastguard Worker    viewerSelector: string,
299*90c8c64dSAndroid Build Coastguard Worker    filterSelector: string,
300*90c8c64dSAndroid Build Coastguard Worker    options: string[],
301*90c8c64dSAndroid Build Coastguard Worker  ) {
302*90c8c64dSAndroid Build Coastguard Worker    await element(
303*90c8c64dSAndroid Build Coastguard Worker      by.css(
304*90c8c64dSAndroid Build Coastguard Worker        `${viewerSelector} .headers ${filterSelector} .mat-select-trigger`,
305*90c8c64dSAndroid Build Coastguard Worker      ),
306*90c8c64dSAndroid Build Coastguard Worker    ).click();
307*90c8c64dSAndroid Build Coastguard Worker
308*90c8c64dSAndroid Build Coastguard Worker    const optionElements: ElementFinder[] = await element.all(
309*90c8c64dSAndroid Build Coastguard Worker      by.css('.mat-select-panel .mat-option'),
310*90c8c64dSAndroid Build Coastguard Worker    );
311*90c8c64dSAndroid Build Coastguard Worker    for (const optionEl of optionElements) {
312*90c8c64dSAndroid Build Coastguard Worker      const optionText = (await optionEl.getText()).trim();
313*90c8c64dSAndroid Build Coastguard Worker      if (options.some((option) => optionText === option)) {
314*90c8c64dSAndroid Build Coastguard Worker        await optionEl.click();
315*90c8c64dSAndroid Build Coastguard Worker      }
316*90c8c64dSAndroid Build Coastguard Worker    }
317*90c8c64dSAndroid Build Coastguard Worker
318*90c8c64dSAndroid Build Coastguard Worker    const backdrop = await element(
319*90c8c64dSAndroid Build Coastguard Worker      by.css('.cdk-overlay-backdrop'),
320*90c8c64dSAndroid Build Coastguard Worker    ).getWebElement();
321*90c8c64dSAndroid Build Coastguard Worker    await browser.actions().mouseMove(backdrop, {x: 0, y: 0}).click().perform();
322*90c8c64dSAndroid Build Coastguard Worker  }
323*90c8c64dSAndroid Build Coastguard Worker
324*90c8c64dSAndroid Build Coastguard Worker  static async uploadFixture(...paths: string[]) {
325*90c8c64dSAndroid Build Coastguard Worker    const inputFile = element(by.css('input[type="file"]'));
326*90c8c64dSAndroid Build Coastguard Worker
327*90c8c64dSAndroid Build Coastguard Worker    // Uploading multiple files is not properly supported but
328*90c8c64dSAndroid Build Coastguard Worker    // chrome handles file paths joined with new lines
329*90c8c64dSAndroid Build Coastguard Worker    await inputFile.sendKeys(
330*90c8c64dSAndroid Build Coastguard Worker      paths.map((it) => E2eTestUtils.getFixturePath(it)).join('\n'),
331*90c8c64dSAndroid Build Coastguard Worker    );
332*90c8c64dSAndroid Build Coastguard Worker  }
333*90c8c64dSAndroid Build Coastguard Worker
334*90c8c64dSAndroid Build Coastguard Worker  static getFixturePath(filename: string): string {
335*90c8c64dSAndroid Build Coastguard Worker    if (path.isAbsolute(filename)) {
336*90c8c64dSAndroid Build Coastguard Worker      return filename;
337*90c8c64dSAndroid Build Coastguard Worker    }
338*90c8c64dSAndroid Build Coastguard Worker    return path.join(
339*90c8c64dSAndroid Build Coastguard Worker      E2eTestUtils.getProjectRootPath(),
340*90c8c64dSAndroid Build Coastguard Worker      'src/test/fixtures',
341*90c8c64dSAndroid Build Coastguard Worker      filename,
342*90c8c64dSAndroid Build Coastguard Worker    );
343*90c8c64dSAndroid Build Coastguard Worker  }
344*90c8c64dSAndroid Build Coastguard Worker
345*90c8c64dSAndroid Build Coastguard Worker  private static getProjectRootPath(): string {
346*90c8c64dSAndroid Build Coastguard Worker    let root = __dirname;
347*90c8c64dSAndroid Build Coastguard Worker    while (path.basename(root) !== 'winscope') {
348*90c8c64dSAndroid Build Coastguard Worker      root = path.dirname(root);
349*90c8c64dSAndroid Build Coastguard Worker    }
350*90c8c64dSAndroid Build Coastguard Worker    return root;
351*90c8c64dSAndroid Build Coastguard Worker  }
352*90c8c64dSAndroid Build Coastguard Worker
353*90c8c64dSAndroid Build Coastguard Worker  private static async checkHasLoadedTracesFromBugReport() {
354*90c8c64dSAndroid Build Coastguard Worker    const text = await element(by.css('.uploaded-files')).getText();
355*90c8c64dSAndroid Build Coastguard Worker    expect(text).toContain('Window Manager');
356*90c8c64dSAndroid Build Coastguard Worker    expect(text).toContain('Surface Flinger');
357*90c8c64dSAndroid Build Coastguard Worker    expect(text).toContain('Transactions');
358*90c8c64dSAndroid Build Coastguard Worker    expect(text).toContain('Transitions');
359*90c8c64dSAndroid Build Coastguard Worker
360*90c8c64dSAndroid Build Coastguard Worker    // Should be merged into a single Transitions trace
361*90c8c64dSAndroid Build Coastguard Worker    expect(text).not.toContain('WM Transitions');
362*90c8c64dSAndroid Build Coastguard Worker    expect(text).not.toContain('Shell Transitions');
363*90c8c64dSAndroid Build Coastguard Worker
364*90c8c64dSAndroid Build Coastguard Worker    expect(text).toContain('layers_trace_from_transactions.winscope');
365*90c8c64dSAndroid Build Coastguard Worker    expect(text).toContain('transactions_trace.winscope');
366*90c8c64dSAndroid Build Coastguard Worker    expect(text).toContain('wm_transition_trace.winscope');
367*90c8c64dSAndroid Build Coastguard Worker    expect(text).toContain('shell_transition_trace.winscope');
368*90c8c64dSAndroid Build Coastguard Worker    expect(text).toContain('window_CRITICAL.proto');
369*90c8c64dSAndroid Build Coastguard Worker
370*90c8c64dSAndroid Build Coastguard Worker    // discards some traces due to old data
371*90c8c64dSAndroid Build Coastguard Worker    expect(text).not.toContain('ProtoLog');
372*90c8c64dSAndroid Build Coastguard Worker    expect(text).not.toContain('IME Service');
373*90c8c64dSAndroid Build Coastguard Worker    expect(text).not.toContain('IME system_server');
374*90c8c64dSAndroid Build Coastguard Worker    expect(text).not.toContain('IME Clients');
375*90c8c64dSAndroid Build Coastguard Worker    expect(text).not.toContain('wm_log.winscope');
376*90c8c64dSAndroid Build Coastguard Worker    expect(text).not.toContain('ime_trace_service.winscope');
377*90c8c64dSAndroid Build Coastguard Worker    expect(text).not.toContain('ime_trace_managerservice.winscope');
378*90c8c64dSAndroid Build Coastguard Worker    expect(text).not.toContain('wm_trace.winscope');
379*90c8c64dSAndroid Build Coastguard Worker    expect(text).not.toContain('ime_trace_clients.winscope');
380*90c8c64dSAndroid Build Coastguard Worker  }
381*90c8c64dSAndroid Build Coastguard Worker
382*90c8c64dSAndroid Build Coastguard Worker  private static async checkEmitsUnsupportedFileFormatMessages() {
383*90c8c64dSAndroid Build Coastguard Worker    const text = await element(by.css('snack-bar')).getText();
384*90c8c64dSAndroid Build Coastguard Worker    expect(text).toContain('unsupported format');
385*90c8c64dSAndroid Build Coastguard Worker  }
386*90c8c64dSAndroid Build Coastguard Worker
387*90c8c64dSAndroid Build Coastguard Worker  private static async checkEmitsOldDataMessages() {
388*90c8c64dSAndroid Build Coastguard Worker    const text = await element(by.css('snack-bar')).getText();
389*90c8c64dSAndroid Build Coastguard Worker    expect(text).toContain('discarded because data is old');
390*90c8c64dSAndroid Build Coastguard Worker  }
391*90c8c64dSAndroid Build Coastguard Worker}
392*90c8c64dSAndroid Build Coastguard Worker
393*90c8c64dSAndroid Build Coastguard Workerexport {E2eTestUtils};
394