blob: 773ac8cde3745eb65c57a83c830e318c6cbaa238 [file] [log] [blame]
// Copyright 2020 The Chromium Authors
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.
import type * as SDK from '../../core/sdk/sdk.js';
import * as Protocol from '../../generated/protocol.js';
import {MockIssuesManager} from '../../testing/MockIssuesManager.js';
import {MockIssuesModel} from '../../testing/MockIssuesModel.js';
import {StubIssue} from '../../testing/StubIssue.js';
import * as IssuesManager from './issues_manager.js';
function requestIds(...issues: IssuesManager.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;
}
describe('AggregatedIssue', () => {
const aggregationKey = 'key' as unknown as IssuesManager.IssueAggregator.AggregationKey;
it('deduplicates network requests across issues', () => {
const issue1 = StubIssue.createFromRequestIds(['id1', 'id2']);
const issue2 = StubIssue.createFromRequestIds(['id1']);
const aggregatedIssue = new IssuesManager.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 IssuesManager.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 model = new MockIssuesModel([]) as unknown as SDK.IssuesModel.IssuesModel;
assert.exists(model);
return model;
}
describe('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 IssuesManager.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 IssuesManager.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 IssuesManager.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 IssuesManager.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 IssuesManager.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 IssuesManager.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);
});
});
});
describe('IssueAggregator', () => {
function getTestPermissionElementIssue(issueType: Protocol.Audits.PermissionElementIssueType):
IssuesManager.Issue.Issue {
const model = createModel();
return IssuesManager.PermissionElementIssue.PermissionElementIssue.fromInspectorIssue(model, {
code: Protocol.Audits.InspectorIssueCode.PermissionElementIssue,
details: {
permissionElementIssueDetails: {
issueType,
},
},
})[0];
}
it('aggregates permission element issues correctly', () => {
const model = createModel();
const issue1 = getTestPermissionElementIssue(Protocol.Audits.PermissionElementIssueType.InvalidType);
const issue2 = getTestPermissionElementIssue(Protocol.Audits.PermissionElementIssueType.InvalidType);
const issue3 = getTestPermissionElementIssue(Protocol.Audits.PermissionElementIssueType.FencedFrameDisallowed);
const mockManager = new MockIssuesManager([]) as unknown as IssuesManager.IssuesManager.IssuesManager;
const aggregator = new IssuesManager.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});
mockManager.dispatchEventToListeners(
IssuesManager.IssuesManager.Events.ISSUE_ADDED, {issuesModel: model, issue: issue3});
const issues = Array.from(aggregator.aggregatedIssues());
assert.lengthOf(issues, 2);
});
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 IssuesManager.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 IssuesManager.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},
]);
});
});
});
describe('IssueAggregator', () => {
let issuesManager: MockIssuesManager;
let aggregator: IssuesManager.IssueAggregator.IssueAggregator;
beforeEach(() => {
issuesManager = new MockIssuesManager([]);
aggregator = new IssuesManager.IssueAggregator.IssueAggregator(issuesManager);
});
it('aggregates hidden issues correctly', () => {
const issues = [
new StubIssue('HiddenStubIssue1', [], []),
new StubIssue('HiddenStubIssue2', [], []),
new StubIssue('UnhiddenStubIssue1', [], []),
new StubIssue('UnhiddenStubIssue2', [], []),
];
for (const issue of issues) {
if (issue.code().includes('Hidden')) {
issue.setHidden(true);
}
issuesManager.addIssue(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', [], []),
];
issues[0].setHidden(true);
for (const issue of issues) {
issuesManager.addIssue(issue);
}
assert.strictEqual(aggregator.numberOfAggregatedIssues(), 3);
assert.strictEqual(aggregator.numberOfHiddenAggregatedIssues(), 1);
issues[1].setHidden(true);
issuesManager.dispatchEventToListeners(IssuesManager.IssuesManager.Events.FULL_UPDATE_REQUIRED);
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', [], []),
];
for (const issue of issues) {
issue.setHidden(true);
issuesManager.addIssue(issue);
}
assert.strictEqual(aggregator.numberOfHiddenAggregatedIssues(), 4);
assert.strictEqual(aggregator.numberOfAggregatedIssues(), 0);
issues[2].setHidden(false);
issuesManager.dispatchEventToListeners(IssuesManager.IssuesManager.Events.FULL_UPDATE_REQUIRED);
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', [], []),
];
for (const issue of issues) {
issue.setHidden(true);
issuesManager.addIssue(issue);
}
assert.strictEqual(aggregator.numberOfHiddenAggregatedIssues(), 4);
assert.strictEqual(aggregator.numberOfAggregatedIssues(), 0);
for (const issue of issues) {
issue.setHidden(false);
}
issuesManager.dispatchEventToListeners(IssuesManager.IssuesManager.Events.FULL_UPDATE_REQUIRED);
assert.strictEqual(aggregator.numberOfAggregatedIssues(), 4);
assert.strictEqual(aggregator.numberOfHiddenAggregatedIssues(), 0);
});
});
describe('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: MockIssuesManager;
let model: SDK.IssuesModel.IssuesModel;
beforeEach(() => {
issuesManager = new MockIssuesManager([]);
model = new MockIssuesModel([]) as unknown as SDK.IssuesModel.IssuesModel;
});
it('should not aggregate third-party cookie phaseout or mitigation related issues', async () => {
// Preexisting issues should not be added
const issues = [
getTestCookieIssue(Protocol.Audits.CookieWarningReason.WarnDeprecationTrialMetadata),
getTestCookieIssue(Protocol.Audits.CookieWarningReason.WarnThirdPartyCookieHeuristic),
getTestCookieIssue(Protocol.Audits.CookieWarningReason.WarnThirdPartyPhaseout),
getTestCookieIssue(undefined, Protocol.Audits.CookieExclusionReason.ExcludeThirdPartyPhaseout)
];
for (const issue of issues) {
issuesManager.addIssue(issue as StubIssue);
}
const aggregator = new IssuesManager.IssueAggregator.IssueAggregator(issuesManager);
for (const issue of issues) {
issuesManager.addIssue(issue as StubIssue);
}
assert.strictEqual(aggregator.numberOfAggregatedIssues(), 0);
assert.strictEqual(aggregator.numberOfHiddenAggregatedIssues(), 0);
// But other cookie issues should get aggregated
issuesManager.addIssue(getTestCookieIssue(Protocol.Audits.CookieWarningReason.WarnDomainNonASCII) as StubIssue);
issuesManager.addIssue(
getTestCookieIssue(undefined, Protocol.Audits.CookieExclusionReason.ExcludeDomainNonASCII) as StubIssue);
assert.strictEqual(aggregator.numberOfAggregatedIssues(), 2);
assert.strictEqual(aggregator.numberOfHiddenAggregatedIssues(), 0);
});
});