Skip to content

Commit

Permalink
EnterpriseReporting UI browser test
Browse files Browse the repository at this point in the history
Bug: b:302138863
Change-Id: Ib4b3c15f2e9c259922d497433cde32939fc1906e
Reviewed-on: https://chromium-review.googlesource.com/c/chromium/src/+/4898505
Reviewed-by: Rebekah Potter <rbpotter@chromium.org>
Commit-Queue: Leonid Baraz <lbaraz@chromium.org>
Reviewed-by: Michael Checo <michaelcheco@google.com>
Cr-Commit-Position: refs/heads/main@{#1210338}
  • Loading branch information
Leonid Baraz authored and Chromium LUCI CQ committed Oct 16, 2023
1 parent f02deed commit 46964e1
Show file tree
Hide file tree
Showing 10 changed files with 281 additions and 30 deletions.
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
buganizer: {
component_id:817866 # Chrome > Enterprise > Reporting & Insights
}
os: CHROME_OS
team_email: "cros-reporting-team@google.com"
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
mixins: "//chrome/browser/resources/chromeos/enterprise_reporting/COMMON_METADATA"
Original file line number Diff line number Diff line change
Expand Up @@ -8,23 +8,27 @@ export class EnterpriseReportingBrowserProxy {
callbackRouter: PageCallbackRouter;
handler: PageHandlerInterface;

constructor() {
this.callbackRouter = new PageCallbackRouter();

this.handler = new PageHandlerRemote();

const factory = PageHandlerFactory.getRemote();
factory.createPageHandler(
this.callbackRouter.$.bindNewPipeAndPassRemote(),
(this.handler as PageHandlerRemote).$.bindNewPipeAndPassReceiver());
static getInstance(): EnterpriseReportingBrowserProxy {
if (!instance) {
const handler = new PageHandlerRemote();
const callbackRouter = new PageCallbackRouter();
PageHandlerFactory.getRemote().createPageHandler(
callbackRouter.$.bindNewPipeAndPassRemote(),
handler.$.bindNewPipeAndPassReceiver());
instance = new EnterpriseReportingBrowserProxy(handler, callbackRouter);
}
return instance;
}

static getInstance(): EnterpriseReportingBrowserProxy {
return instance || (instance = new EnterpriseReportingBrowserProxy());
static createInstanceForTest(
handler: PageHandlerInterface, callbackRouter: PageCallbackRouter) {
instance = new EnterpriseReportingBrowserProxy(handler, callbackRouter);
}

static setInstance(obj: EnterpriseReportingBrowserProxy) {
instance = obj;
private constructor(
handler: PageHandlerInterface, callbackRouter: PageCallbackRouter) {
this.handler = handler;
this.callbackRouter = callbackRouter;
}
}

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -57,6 +57,9 @@ export class ReportingHistoryElement extends PolymerElement {
override connectedCallback() {
super.connectedCallback();

// Set up history table to initially show as empty.
this.setEmptyErpTable();

// Add a listener for the asynchronous 'setErpHistoryData' event
// to be invoked by page handler and populate the table.
this.browserProxy.callbackRouter.setErpHistoryData.addListener(
Expand All @@ -70,37 +73,42 @@ export class ReportingHistoryElement extends PolymerElement {

// Set initial history on/off state after refresh.
this.browserProxy.handler.getDebugState().then(
(value: {state: boolean}) => {
this.loggingState = value.state;
({state}: {state: boolean}) => {
this.loggingState = state;
});

// Populate history upon page refresh.
this.browserProxy.handler.getErpHistoryData().then(
(value: {historyData: ErpHistoryData}) => {
this.updateErpTable(value.historyData);
({historyData}: {historyData: ErpHistoryData}) => {
this.updateErpTable(historyData);
});
}

// Fills the table as empty (initially or upon update).
private setEmptyErpTable() {
const emptyRow = document.createElement('tr');
// Pad with empty data cells, so that the alignment matches.
emptyRow.replaceChildren(
this.createHistoryTableDataCell('No events', 'erp-type'),
this.composeEventParameters([], 'erp-parameters'),
this.createHistoryTableDataCell('', 'erp-status'),
this.createHistoryTableDataCell('', 'erp-timestamp'));
this.$.body.appendChild(emptyRow);
}

// Fills the passed table element with the given history.
private updateErpTable(history: ErpHistoryData) {
// Clear the table first.
// Reset table.
this.$.body.replaceChildren();

// If there are no events, present a placeholder.
if (history.events.length === 0) {
const emptyRow = document.createElement('tr');
// Padd with empty data cells, so that the alignment matches.
emptyRow.replaceChildren(
this.createHistoryTableDataCell('No events', 'erp-type'),
this.composeEventParameters([], 'erp-parameters'),
this.createHistoryTableDataCell('', 'erp-status'),
this.createHistoryTableDataCell('', 'erp-timestamp'));
this.$.body.appendChild(emptyRow);
this.setEmptyErpTable();
return;
}

// Iterate through the history in reverse order so that the most recent
// event shows up first.
// Populate the table row by the events: iterate through the history
// in reverse order so that the most recent event shows up first.
for (const event of history.events.reverse()) {
const row = this.composeTableRow(event);
this.$.body.appendChild(row);
Expand Down
8 changes: 6 additions & 2 deletions chrome/test/data/webui/BUILD.gn
Original file line number Diff line number Diff line change
Expand Up @@ -103,8 +103,10 @@ source_set("browser_tests") {
}

if (is_chromeos_ash) {
sources +=
[ "chromeos/personalization_app/personalization_app_browsertest.cc" ]
sources += [
"chromeos/enterprise_reporting/enterprise_reporting_browsertest.cc",
"chromeos/personalization_app/personalization_app_browsertest.cc",
]
} else {
sources += [
"intro/intro_browsertest.cc",
Expand Down Expand Up @@ -622,6 +624,7 @@ generate_grd("build_grd") {
"chromeos/cloud_upload:build_grdp",
"chromeos/diagnostics:build_grdp",
"chromeos/emoji_picker:build_grdp",
"chromeos/enterprise_reporting:build_grdp",
"chromeos/firmware_update:build_grdp",
"chromeos/manage_mirrorsync:build_grdp",
"chromeos/office_fallback:build_grdp",
Expand All @@ -643,6 +646,7 @@ generate_grd("build_grd") {
"$target_gen_dir/chromeos/parent_access/resources.grdp",
"$target_gen_dir/chromeos/diagnostics/resources.grdp",
"$target_gen_dir/chromeos/emoji_picker/resources.grdp",
"$target_gen_dir/chromeos/enterprise_reporting/resources.grdp",
"$target_gen_dir/chromeos/firmware_update/resources.grdp",
"$target_gen_dir/chromeos/office_fallback/resources.grdp",
"$target_gen_dir/chromeos/personalization_app/resources.grdp",
Expand Down
21 changes: 21 additions & 0 deletions chrome/test/data/webui/chromeos/enterprise_reporting/BUILD.gn
Original file line number Diff line number Diff line change
@@ -0,0 +1,21 @@
# Copyright 2023 The Chromium Authors
# Use of this source code is governed by a BSD-style license that can be
# found in the LICENSE file.

import("../../build_webui_tests.gni")

assert(is_chromeos_ash)

build_webui_tests("build") {
resource_path_prefix = "enterprise_reporting"

files = [ "enterprise_reporting_test.ts" ]

ts_path_mappings = [ "chrome://enterprise-reporting/*|" + rebase_path(
"$root_gen_dir/chrome/browser/resources/chromeos/enterprise_reporting/tsc/*",
target_gen_dir) ]
ts_deps = [
"//chrome/browser/resources/chromeos/enterprise_reporting:build_ts",
"//third_party/polymer/v3_0:library",
]
}
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
mixins: "//chrome/browser/resources/chromeos/enterprise_reporting/COMMON_METADATA"
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
file://components/reporting/OWNERS
Original file line number Diff line number Diff line change
@@ -0,0 +1,27 @@
// Copyright 2023 The Chromium Authors
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.

#include "ash/constants/ash_features.h"
#include "base/test/scoped_feature_list.h"
#include "chrome/common/webui_url_constants.h"
#include "chrome/test/base/web_ui_mocha_browser_test.h"
#include "content/public/test/browser_test.h"

namespace reporting {
namespace {

class EnterpriseReportingUITest : public WebUIMochaBrowserTest {
protected:
base::test::ScopedFeatureList scoped_feature_list_{
ash::features::kEnterpriseReportingUI};
};

IN_PROC_BROWSER_TEST_F(EnterpriseReportingUITest, App) {
set_test_loader_host(chrome::kChromeUIEnterpriseReportingHost);
RunTest("chromeos/enterprise_reporting/enterprise_reporting_test.js",
"mocha.run()");
}

} // namespace
} // namespace reporting
Original file line number Diff line number Diff line change
@@ -0,0 +1,179 @@
// Copyright 2023 The Chromium Authors
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.

/**
* @fileoverview Unittests for the chrome://enterprise-reporting element.
*/

import {EnterpriseReportingBrowserProxy} from 'chrome://enterprise-reporting/browser_proxy.js';
import {ErpHistoryData, ErpHistoryEvent, ErpHistoryEventParameter, PageCallbackRouter, PageHandlerRemote, PageRemote} from 'chrome://enterprise-reporting/enterprise_reporting.mojom-webui.js';
import {ReportingHistoryElement} from 'chrome://enterprise-reporting/reporting_history.js';
import {assertEquals} from 'chrome://webui-test/chai_assert.js';
import {flushTasks} from 'chrome://webui-test/polymer_test_util.js';
import {TestMock} from 'chrome://webui-test/test_mock.js';

suite('enterprise_reporting', function() {
let reportingHistoryElement: ReportingHistoryElement;
let callbackRouterRemote: PageRemote;
let handler: TestMock<PageHandlerRemote>;

// Number of cells in HTML row representing a single event, produced by
// `chrome://enterprise_reporting/reporting_history.ts`:
// `call`, `parameters`, `status` and `timestamp`.
const numCellsInRow = 4;

type Constructor<T> = new (...args: any[]) => T;
type Installer<T> = (instance: T) => void;

function installMock<T extends object>(
clazz: Constructor<T>, installer?: Installer<T>): TestMock<T> {
installer = installer ||
(clazz as unknown as {setInstance: Installer<T>}).setInstance;
const mock = TestMock.fromClass(clazz);
installer!(mock);
return mock;
}

function getHistoryTable(): HTMLElement {
return reportingHistoryElement.$.body;
}

function timestampToString(timestampSeconds: bigint): string {
// Multiply by 1000 since the constructor expects milliseconds, but the
// timestamps are in seconds.
const timestamp: Date = new Date(Number(timestampSeconds) * 1000);

// For today's timestamp, show time only.
const now: Date = new Date();
if (timestamp.getDate() === now.getDate()) {
return timestamp.toLocaleTimeString();
}

// Otherwise show whole timestamp.
return timestamp.toLocaleString();
}

function parametersMatch(
expectedParameters: ErpHistoryEventParameter[],
parameters: HTMLTableCellElement) {
const lines = parameters.querySelectorAll<HTMLLIElement>('li');
assertEquals(expectedParameters.length, lines.length);
lines.forEach((line, index) => {
assertEquals(
(expectedParameters[index]!.name + ': ' +
expectedParameters[index]!.value),
line.innerText);
});
}

function cellMatches(
expectedEvent: ErpHistoryEvent, row: HTMLTableRowElement) {
// Enumerate and match cells in the row.
const cells = row.querySelectorAll<HTMLTableCellElement>('td');
assertEquals(numCellsInRow, cells.length);
assertEquals(expectedEvent.call, cells[0]!.innerText);
parametersMatch(expectedEvent.parameters, cells[1]!);
assertEquals(expectedEvent.status, cells[2]!.innerText);
assertEquals(timestampToString(expectedEvent.time), cells[3]!.innerText);
}

function rowsMatchInReverse(
expectedHistory: ErpHistoryData, rows: NodeListOf<HTMLTableRowElement>) {
// Enumerate and match all rows.
assertEquals(expectedHistory.events.length, rows.length);
rows.forEach((row, index) => {
cellMatches(
expectedHistory.events[expectedHistory.events.length - index - 1]!,
row);
});
}

function emptyMatch(rows: NodeListOf<HTMLTableRowElement>) {
// Check for empty case.
assertEquals(1, rows.length);
// Enumerate the cells.
const cells = rows[0]!.querySelectorAll<HTMLTableCellElement>('td');
assertEquals(numCellsInRow, cells.length);
assertEquals('No events', cells[0]!.innerText);
assertEquals('', cells[1]!.innerText);
assertEquals('', cells[2]!.innerText);
assertEquals('', cells[3]!.innerText);
}

async function setInitialSettings() {
callbackRouterRemote.setErpHistoryData({events: []});
handler.setResultFor('getDebugState', Promise.resolve({state: true}));
handler.setResultFor('getErpHistoryData', Promise.resolve({events: []}));
reportingHistoryElement =
document.createElement(ReportingHistoryElement.is);
document.body.appendChild(reportingHistoryElement);
await handler.whenCalled('getDebugState');
await handler.whenCalled('getErpHistoryData');
}

setup(() => {
handler = installMock(
PageHandlerRemote,
(mock: PageHandlerRemote) =>
EnterpriseReportingBrowserProxy.createInstanceForTest(
mock, new PageCallbackRouter()));
callbackRouterRemote = EnterpriseReportingBrowserProxy.getInstance()
.callbackRouter.$.bindNewPipeAndPassRemote();
});

teardown(async () => {
await callbackRouterRemote.$.flushForTesting();
await flushTasks();
reportingHistoryElement.remove();
});

test('create History Element and see that it is empty', async () => {
await setInitialSettings();
await callbackRouterRemote.$.flushForTesting();
await handler.whenCalled('getErpHistoryData');
await flushTasks();

const table = getHistoryTable();
emptyMatch(table.querySelectorAll<HTMLTableRowElement>('tr'));
});

test('create History Element and update it with data', async () => {
await setInitialSettings();
await callbackRouterRemote.$.flushForTesting();

const event1: ErpHistoryEvent = {
call: 'call',
parameters: [{name: 'seq_id', value: '345'} as ErpHistoryEventParameter],
status: 'OK',
time: BigInt(123456789),
};
const event2: ErpHistoryEvent = {
call: 'recall',
parameters: [
{name: 'seq_id', value: '123'} as ErpHistoryEventParameter,
{name: 'count', value: '777'} as ErpHistoryEventParameter,
],
status: 'Error',
time: BigInt(987654321),
};
const event3: ErpHistoryEvent = {
call: 'upload',
parameters: [
{name: 'seq_id', value: '123'} as ErpHistoryEventParameter,
{name: 'seq_id', value: '456'} as ErpHistoryEventParameter,
{name: 'seq_id', value: '789'} as ErpHistoryEventParameter,
],
status: 'Success',
time: BigInt(555666777),
};
const history: ErpHistoryData = {events: [event1, event2, event3]};
callbackRouterRemote.setErpHistoryData(history);
await handler.whenCalled('getErpHistoryData');
await flushTasks();

const table = getHistoryTable();
rowsMatchInReverse(
history, table.querySelectorAll<HTMLTableRowElement>('tr'));
});
});

0 comments on commit 46964e1

Please sign in to comment.