chrome-devtools-frontend
Version:
Chrome DevTools UI
428 lines (362 loc) • 19.1 kB
text/typescript
// Copyright 2020 The Chromium Authors. All rights reserved.
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.
import type * as Common from '../../core/common/common.js';
import * as SDK from '../../core/sdk/sdk.js';
import * as Protocol from '../../generated/protocol.js';
import * as IssuesManager from '../../models/issues_manager/issues_manager.js';
import {
createFakeSetting,
createTarget,
describeWithEnvironment,
} from '../../testing/EnvironmentHelpers.js';
import {describeWithMockConnection} from '../../testing/MockConnection.js';
import {MockIssuesManager} from '../../testing/MockIssuesManager.js';
import {StubIssue} from '../../testing/StubIssue.js';
import * as Issues from './issues.js';
function requestIds(...issues: Issues.IssueAggregator.AggregatedIssue[]): Set<string|undefined> {
const requestIds = new Set<string|undefined>();
for (const issue of issues) {
for (const {requestId} of issue.requests()) {
requestIds.add(requestId);
}
}
return requestIds;
}
describeWithEnvironment('AggregatedIssue', () => {
const aggregationKey = 'key' as unknown as Issues.IssueAggregator.AggregationKey;
it('deduplicates network requests across issues', () => {
const issue1 = StubIssue.createFromRequestIds(['id1', 'id2']);
const issue2 = StubIssue.createFromRequestIds(['id1']);
const aggregatedIssue = new Issues.IssueAggregator.AggregatedIssue('code', aggregationKey);
aggregatedIssue.addInstance(issue1);
aggregatedIssue.addInstance(issue2);
assert.deepEqual(requestIds(aggregatedIssue), new Set(['id1', 'id2']));
});
it('deduplicates affected cookies across issues', () => {
const issue1 = StubIssue.createFromCookieNames(['cookie1']);
const issue2 = StubIssue.createFromCookieNames(['cookie2']);
const issue3 = StubIssue.createFromCookieNames(['cookie1', 'cookie2']);
const aggregatedIssue = new Issues.IssueAggregator.AggregatedIssue('code', aggregationKey);
aggregatedIssue.addInstance(issue1);
aggregatedIssue.addInstance(issue2);
aggregatedIssue.addInstance(issue3);
const actualCookieNames = [...aggregatedIssue.cookies()].map(c => c.name).sort();
assert.deepEqual(actualCookieNames, ['cookie1', 'cookie2']);
});
});
function createModel() {
const target = createTarget();
const model = target.model(SDK.IssuesModel.IssuesModel);
assert.exists(model);
return model;
}
describeWithMockConnection('IssueAggregator', () => {
it('deduplicates issues with the same code', () => {
const issue1 = StubIssue.createFromRequestIds(['id1']);
const issue2 = StubIssue.createFromRequestIds(['id2']);
const model = createModel();
const mockManager = new MockIssuesManager([]) as unknown as IssuesManager.IssuesManager.IssuesManager;
const aggregator = new Issues.IssueAggregator.IssueAggregator(mockManager);
mockManager.dispatchEventToListeners(
IssuesManager.IssuesManager.Events.ISSUE_ADDED, {issuesModel: model, issue: issue1});
mockManager.dispatchEventToListeners(
IssuesManager.IssuesManager.Events.ISSUE_ADDED, {issuesModel: model, issue: issue2});
assert.deepEqual(requestIds(...aggregator.aggregatedIssues()), new Set(['id1', 'id2']));
});
it('deduplicates issues with the same code added before its creation', () => {
const issue1 = StubIssue.createFromRequestIds(['id1']);
const issue2 = StubIssue.createFromRequestIds(['id2']);
const issue1b = StubIssue.createFromRequestIds(['id1']); // Duplicate id.
const issue3 = StubIssue.createFromRequestIds(['id3']);
const model = createModel();
const mockManager =
new MockIssuesManager([issue1b, issue3]) as unknown as IssuesManager.IssuesManager.IssuesManager;
const aggregator = new Issues.IssueAggregator.IssueAggregator(mockManager);
mockManager.dispatchEventToListeners(
IssuesManager.IssuesManager.Events.ISSUE_ADDED, {issuesModel: model, issue: issue1});
mockManager.dispatchEventToListeners(
IssuesManager.IssuesManager.Events.ISSUE_ADDED, {issuesModel: model, issue: issue2});
assert.deepEqual(requestIds(...aggregator.aggregatedIssues()), new Set(['id1', 'id2', 'id3']));
});
it('keeps issues with different codes separate', () => {
const issue1 = new StubIssue('codeA', ['id1'], []);
const issue2 = new StubIssue('codeB', ['id1'], []);
const issue1b = new StubIssue('codeC', ['id1'], []);
const issue3 = new StubIssue('codeA', ['id1'], []);
const model = createModel();
const mockManager =
new MockIssuesManager([issue1b, issue3]) as unknown as IssuesManager.IssuesManager.IssuesManager;
const aggregator = new Issues.IssueAggregator.IssueAggregator(mockManager);
mockManager.dispatchEventToListeners(
IssuesManager.IssuesManager.Events.ISSUE_ADDED, {issuesModel: model, issue: issue1});
mockManager.dispatchEventToListeners(
IssuesManager.IssuesManager.Events.ISSUE_ADDED, {issuesModel: model, issue: issue2});
const issues = Array.from(aggregator.aggregatedIssues());
assert.lengthOf(issues, 3);
const issueCodes = issues.map(r => r.aggregationKey().toString()).sort((a, b) => a.localeCompare(b));
assert.deepEqual(issueCodes, ['codeA', 'codeB', 'codeC']);
});
describe('aggregates issue kind', () => {
it('for a single issue', () => {
const issues = StubIssue.createFromIssueKinds([IssuesManager.Issue.IssueKind.IMPROVEMENT]);
const mockManager = new MockIssuesManager(issues) as unknown as IssuesManager.IssuesManager.IssuesManager;
const aggregator = new Issues.IssueAggregator.IssueAggregator(mockManager);
const aggregatedIssues = Array.from(aggregator.aggregatedIssues());
assert.lengthOf(aggregatedIssues, 1);
const aggregatedIssue = aggregatedIssues[0];
assert.strictEqual(aggregatedIssue.getKind(), IssuesManager.Issue.IssueKind.IMPROVEMENT);
});
it('for issues of two different kinds', () => {
const issues = StubIssue.createFromIssueKinds([
IssuesManager.Issue.IssueKind.IMPROVEMENT,
IssuesManager.Issue.IssueKind.BREAKING_CHANGE,
IssuesManager.Issue.IssueKind.IMPROVEMENT,
]);
const mockManager = new MockIssuesManager(issues) as unknown as IssuesManager.IssuesManager.IssuesManager;
const aggregator = new Issues.IssueAggregator.IssueAggregator(mockManager);
const aggregatedIssues = Array.from(aggregator.aggregatedIssues());
assert.lengthOf(aggregatedIssues, 1);
const aggregatedIssue = aggregatedIssues[0];
assert.strictEqual(aggregatedIssue.getKind(), IssuesManager.Issue.IssueKind.BREAKING_CHANGE);
});
it('for issues of three different kinds', () => {
const issues = StubIssue.createFromIssueKinds([
IssuesManager.Issue.IssueKind.BREAKING_CHANGE,
IssuesManager.Issue.IssueKind.PAGE_ERROR,
IssuesManager.Issue.IssueKind.IMPROVEMENT,
]);
const mockManager = new MockIssuesManager(issues) as unknown as IssuesManager.IssuesManager.IssuesManager;
const aggregator = new Issues.IssueAggregator.IssueAggregator(mockManager);
const aggregatedIssues = Array.from(aggregator.aggregatedIssues());
assert.lengthOf(aggregatedIssues, 1);
const aggregatedIssue = aggregatedIssues[0];
assert.strictEqual(aggregatedIssue.getKind(), IssuesManager.Issue.IssueKind.PAGE_ERROR);
});
});
});
describeWithMockConnection('IssueAggregator', () => {
it('aggregates heavy ad issues correctly', () => {
const model = createModel();
const details1 = {
resolution: Protocol.Audits.HeavyAdResolutionStatus.HeavyAdBlocked,
reason: Protocol.Audits.HeavyAdReason.CpuPeakLimit,
frame: {frameId: 'main' as Protocol.Page.FrameId},
};
const issue1 = new IssuesManager.HeavyAdIssue.HeavyAdIssue(details1, model);
const details2 = {
resolution: Protocol.Audits.HeavyAdResolutionStatus.HeavyAdWarning,
reason: Protocol.Audits.HeavyAdReason.NetworkTotalLimit,
frame: {frameId: 'main' as Protocol.Page.FrameId},
};
const issue2 = new IssuesManager.HeavyAdIssue.HeavyAdIssue(details2, model);
const mockManager = new MockIssuesManager([]) as unknown as IssuesManager.IssuesManager.IssuesManager;
const aggregator = new Issues.IssueAggregator.IssueAggregator(mockManager);
mockManager.dispatchEventToListeners(
IssuesManager.IssuesManager.Events.ISSUE_ADDED, {issuesModel: model, issue: issue1});
mockManager.dispatchEventToListeners(
IssuesManager.IssuesManager.Events.ISSUE_ADDED, {issuesModel: model, issue: issue2});
const issues = Array.from(aggregator.aggregatedIssues());
assert.lengthOf(issues, 1);
const resolutions = [...issues[0].getHeavyAdIssues()].map(r => r.details().resolution).sort();
assert.deepEqual(resolutions, [
Protocol.Audits.HeavyAdResolutionStatus.HeavyAdBlocked,
Protocol.Audits.HeavyAdResolutionStatus.HeavyAdWarning,
]);
});
const scriptId1 = '1' as Protocol.Runtime.ScriptId;
describe('IssueAggregator', () => {
it('aggregates affected locations correctly', () => {
const model = createModel();
const issue1 = StubIssue.createFromAffectedLocations([{url: 'foo', lineNumber: 1, columnNumber: 1}]);
const issue2 = StubIssue.createFromAffectedLocations([
{url: 'foo', lineNumber: 1, columnNumber: 1},
{url: 'foo', lineNumber: 1, columnNumber: 12},
]);
const issue3 = StubIssue.createFromAffectedLocations([
{url: 'bar', lineNumber: 1, columnNumber: 1},
{url: 'baz', lineNumber: 1, columnNumber: 1},
]);
const issue4 = StubIssue.createFromAffectedLocations([
{url: 'bar', lineNumber: 1, columnNumber: 1, scriptId: scriptId1},
{url: 'foo', lineNumber: 2, columnNumber: 1},
]);
const mockManager = new MockIssuesManager([]) as unknown as IssuesManager.IssuesManager.IssuesManager;
const aggregator = new Issues.IssueAggregator.IssueAggregator(mockManager);
for (const issue of [issue1, issue2, issue3, issue4]) {
mockManager.dispatchEventToListeners(
IssuesManager.IssuesManager.Events.ISSUE_ADDED, {issuesModel: model, issue});
}
const issues = Array.from(aggregator.aggregatedIssues());
assert.lengthOf(issues, 1);
const locations = [...issues[0].sources()].sort((x, y) => JSON.stringify(x).localeCompare(JSON.stringify(y)));
assert.deepEqual(locations, [
{url: 'bar', lineNumber: 1, columnNumber: 1, scriptId: scriptId1},
{url: 'bar', lineNumber: 1, columnNumber: 1},
{url: 'baz', lineNumber: 1, columnNumber: 1},
{url: 'foo', lineNumber: 1, columnNumber: 1},
{url: 'foo', lineNumber: 1, columnNumber: 12},
{url: 'foo', lineNumber: 2, columnNumber: 1},
]);
});
});
});
describeWithMockConnection('IssueAggregator', () => {
let hideIssueByCodeSetting: Common.Settings.Setting<IssuesManager.IssuesManager.HideIssueMenuSetting>;
let showThirdPartyIssuesSetting: Common.Settings.Setting<boolean>;
let issuesManager: IssuesManager.IssuesManager.IssuesManager;
let model: SDK.IssuesModel.IssuesModel;
let aggregator: Issues.IssueAggregator.IssueAggregator;
beforeEach(() => {
hideIssueByCodeSetting =
createFakeSetting('hide by code', ({} as IssuesManager.IssuesManager.HideIssueMenuSetting));
showThirdPartyIssuesSetting = createFakeSetting('third party flag', false);
issuesManager = new IssuesManager.IssuesManager.IssuesManager(showThirdPartyIssuesSetting, hideIssueByCodeSetting);
const target = createTarget();
model = target.model(SDK.IssuesModel.IssuesModel) as SDK.IssuesModel.IssuesModel;
aggregator = new Issues.IssueAggregator.IssueAggregator(issuesManager);
});
it('aggregates hidden issues correctly', () => {
const issues = [
new StubIssue('HiddenStubIssue1', [], []),
new StubIssue('HiddenStubIssue2', [], []),
new StubIssue('UnhiddenStubIssue1', [], []),
new StubIssue('UnhiddenStubIssue2', [], []),
];
hideIssueByCodeSetting.set({
HiddenStubIssue1: IssuesManager.IssuesManager.IssueStatus.HIDDEN,
HiddenStubIssue2: IssuesManager.IssuesManager.IssueStatus.HIDDEN,
});
for (const issue of issues) {
issuesManager.addIssue(model, issue);
}
assert.strictEqual(aggregator.numberOfAggregatedIssues(), 2);
assert.strictEqual(aggregator.numberOfHiddenAggregatedIssues(), 2);
});
it('aggregates hidden issues correctly on updating settings', () => {
const issues = [
new StubIssue('HiddenStubIssue1', [], []),
new StubIssue('HiddenStubIssue2', [], []),
new StubIssue('UnhiddenStubIssue1', [], []),
new StubIssue('UnhiddenStubIssue2', [], []),
];
for (const issue of issues) {
issuesManager.addIssue(model, issue);
}
hideIssueByCodeSetting.set({
HiddenStubIssue1: IssuesManager.IssuesManager.IssueStatus.HIDDEN,
});
assert.strictEqual(aggregator.numberOfAggregatedIssues(), 3);
assert.strictEqual(aggregator.numberOfHiddenAggregatedIssues(), 1);
hideIssueByCodeSetting.set({
HiddenStubIssue1: IssuesManager.IssuesManager.IssueStatus.HIDDEN,
HiddenStubIssue2: IssuesManager.IssuesManager.IssueStatus.HIDDEN,
});
assert.strictEqual(aggregator.numberOfAggregatedIssues(), 2);
assert.strictEqual(aggregator.numberOfHiddenAggregatedIssues(), 2);
});
it('aggregates hidden issues correctly when issues get unhidden', () => {
const issues = [
new StubIssue('HiddenStubIssue1', [], []),
new StubIssue('HiddenStubIssue2', [], []),
new StubIssue('UnhiddenStubIssue1', [], []),
new StubIssue('UnhiddenStubIssue2', [], []),
];
hideIssueByCodeSetting.set({
HiddenStubIssue1: IssuesManager.IssuesManager.IssueStatus.HIDDEN,
HiddenStubIssue2: IssuesManager.IssuesManager.IssueStatus.HIDDEN,
UnhiddenStubIssue1: IssuesManager.IssuesManager.IssueStatus.HIDDEN,
UnhiddenStubIssue2: IssuesManager.IssuesManager.IssueStatus.HIDDEN,
});
for (const issue of issues) {
issuesManager.addIssue(model, issue);
}
assert.strictEqual(aggregator.numberOfHiddenAggregatedIssues(), 4);
assert.strictEqual(aggregator.numberOfAggregatedIssues(), 0);
hideIssueByCodeSetting.set({
HiddenStubIssue1: IssuesManager.IssuesManager.IssueStatus.HIDDEN,
HiddenStubIssue2: IssuesManager.IssuesManager.IssueStatus.HIDDEN,
UnhiddenStubIssue1: IssuesManager.IssuesManager.IssueStatus.UNHIDDEN,
UnhiddenStubIssue2: IssuesManager.IssuesManager.IssueStatus.HIDDEN,
});
assert.strictEqual(aggregator.numberOfAggregatedIssues(), 1);
assert.strictEqual(aggregator.numberOfHiddenAggregatedIssues(), 3);
});
it('aggregates hidden issues correctly when all issues get unhidden', () => {
const issues = [
new StubIssue('HiddenStubIssue1', [], []),
new StubIssue('HiddenStubIssue2', [], []),
new StubIssue('UnhiddenStubIssue1', [], []),
new StubIssue('UnhiddenStubIssue2', [], []),
];
hideIssueByCodeSetting.set({
HiddenStubIssue1: IssuesManager.IssuesManager.IssueStatus.HIDDEN,
HiddenStubIssue2: IssuesManager.IssuesManager.IssueStatus.HIDDEN,
UnhiddenStubIssue1: IssuesManager.IssuesManager.IssueStatus.HIDDEN,
UnhiddenStubIssue2: IssuesManager.IssuesManager.IssueStatus.HIDDEN,
});
for (const issue of issues) {
issuesManager.addIssue(model, issue);
}
assert.strictEqual(aggregator.numberOfHiddenAggregatedIssues(), 4);
assert.strictEqual(aggregator.numberOfAggregatedIssues(), 0);
issuesManager.unhideAllIssues();
assert.strictEqual(aggregator.numberOfAggregatedIssues(), 4);
assert.strictEqual(aggregator.numberOfHiddenAggregatedIssues(), 0);
});
});
describeWithMockConnection('IssueAggregator', () => {
function getTestCookieIssue(
warningReason?: Protocol.Audits.CookieWarningReason,
exclusionReason?: Protocol.Audits.CookieExclusionReason): IssuesManager.Issue.Issue {
return IssuesManager.IssuesManager.createIssuesFromProtocolIssue(model, {
code: Protocol.Audits.InspectorIssueCode.CookieIssue,
details: {
cookieIssueDetails: {
cookie: {
name: 'test',
path: '/',
domain: 'a.test',
},
cookieExclusionReasons: exclusionReason ? [exclusionReason] : [],
cookieWarningReasons: warningReason ? [warningReason] : [],
operation: Protocol.Audits.CookieOperation.ReadCookie,
cookieUrl: 'a.test',
},
},
})[0];
}
let issuesManager: IssuesManager.IssuesManager.IssuesManager;
let model: SDK.IssuesModel.IssuesModel;
beforeEach(() => {
const showThirdPartyIssuesSetting = createFakeSetting('third party flag', true);
issuesManager = new IssuesManager.IssuesManager.IssuesManager(showThirdPartyIssuesSetting);
const target = createTarget();
model = target.model(SDK.IssuesModel.IssuesModel) as SDK.IssuesModel.IssuesModel;
});
it('should not aggregate third-party cookie phaseout or mitigation related issues', async () => {
// Preexisting issues should not be added
issuesManager.addIssue(model, getTestCookieIssue(Protocol.Audits.CookieWarningReason.WarnDeprecationTrialMetadata));
issuesManager.addIssue(
model, getTestCookieIssue(Protocol.Audits.CookieWarningReason.WarnThirdPartyCookieHeuristic));
issuesManager.addIssue(model, getTestCookieIssue(Protocol.Audits.CookieWarningReason.WarnThirdPartyPhaseout));
issuesManager.addIssue(
model, getTestCookieIssue(undefined, Protocol.Audits.CookieExclusionReason.ExcludeThirdPartyPhaseout));
const aggregator = new Issues.IssueAggregator.IssueAggregator(issuesManager);
// Issues added after aggregator creation should not exist either
issuesManager.addIssue(model, getTestCookieIssue(Protocol.Audits.CookieWarningReason.WarnDeprecationTrialMetadata));
issuesManager.addIssue(
model, getTestCookieIssue(Protocol.Audits.CookieWarningReason.WarnThirdPartyCookieHeuristic));
issuesManager.addIssue(model, getTestCookieIssue(Protocol.Audits.CookieWarningReason.WarnThirdPartyPhaseout));
issuesManager.addIssue(
model, getTestCookieIssue(undefined, Protocol.Audits.CookieExclusionReason.ExcludeThirdPartyPhaseout));
assert.strictEqual(aggregator.numberOfAggregatedIssues(), 0);
assert.strictEqual(aggregator.numberOfHiddenAggregatedIssues(), 0);
// But other cookie issues should get aggregated
issuesManager.addIssue(model, getTestCookieIssue(Protocol.Audits.CookieWarningReason.WarnDomainNonASCII));
issuesManager.addIssue(
model, getTestCookieIssue(undefined, Protocol.Audits.CookieExclusionReason.ExcludeDomainNonASCII));
assert.strictEqual(aggregator.numberOfAggregatedIssues(), 2);
assert.strictEqual(aggregator.numberOfHiddenAggregatedIssues(), 0);
});
});