| // Copyright 2021 The Chromium Authors |
| // Use of this source code is governed by a BSD-style license that can be |
| // found in the LICENSE file. |
| |
| import * as Protocol from '../../generated/protocol.js'; |
| import { |
| createTarget, |
| } from '../../testing/EnvironmentHelpers.js'; |
| import { |
| describeWithMockConnection, |
| } from '../../testing/MockConnection.js'; |
| import { |
| FRAME_URL, |
| getInitializedResourceTreeModel, |
| getMainFrame, |
| navigate, |
| } from '../../testing/ResourceTreeHelpers.js'; |
| import * as Common from '../common/common.js'; |
| import * as Platform from '../platform/platform.js'; |
| |
| import * as SDK from './sdk.js'; |
| |
| const {urlString} = Platform.DevToolsPath; |
| |
| describeWithMockConnection('ConsoleMessage', () => { |
| const scriptId1 = '1' as Protocol.Runtime.ScriptId; |
| const scriptId2 = '2' as Protocol.Runtime.ScriptId; |
| |
| function newMessage({ |
| source = Common.Console.FrontendMessageSource.ConsoleAPI, |
| message = 'Message', |
| url, |
| scriptId, |
| executionContextId, |
| stackTrace, |
| }: { |
| source?: SDK.ConsoleModel.MessageSource, |
| message?: string, |
| url?: Platform.DevToolsPath.UrlString, |
| scriptId?: Protocol.Runtime.ScriptId, |
| executionContextId?: number, |
| stackTrace?: Protocol.Runtime.StackTrace, |
| }) { |
| return new SDK.ConsoleModel.ConsoleMessage( |
| null, source, null, message, {url, executionContextId, scriptId, stackTrace}); |
| } |
| |
| it('compares using message', () => { |
| const a = newMessage({}); |
| const b = newMessage({}); |
| const c = newMessage({message: 'DifferentMessage'}); |
| assert.isTrue(a.isEqual(b)); |
| assert.isFalse(b.isEqual(c)); |
| assert.isFalse(c.isEqual(a)); |
| assert.isTrue(c.isEqual(c)); |
| }); |
| |
| it('compares using source', () => { |
| const a = newMessage({}); |
| const b = newMessage({}); |
| const c = newMessage({source: Common.Console.FrontendMessageSource.CSS}); |
| assert.isTrue(a.isEqual(b)); |
| assert.isFalse(b.isEqual(c)); |
| assert.isFalse(c.isEqual(a)); |
| }); |
| |
| it('compares using url', () => { |
| const a = newMessage({}); |
| const b = newMessage({url: urlString`http://a.b/c`}); |
| const c = newMessage({url: urlString`http://a.b/c`}); |
| const d = newMessage({url: urlString`http://a.b/d`}); |
| assert.isFalse(a.isEqual(b)); |
| assert.isTrue(b.isEqual(c)); |
| assert.isFalse(c.isEqual(d)); |
| assert.isFalse(d.isEqual(a)); |
| }); |
| |
| it('compares using execution context and script id', () => { |
| const a = newMessage({}); |
| const b = newMessage({executionContextId: 5, scriptId: scriptId1}); |
| const c = newMessage({executionContextId: 5, scriptId: scriptId1}); |
| const d = newMessage({executionContextId: 6, scriptId: scriptId1}); |
| const e = newMessage({executionContextId: 5, scriptId: scriptId2}); |
| assert.isFalse(a.isEqual(b)); |
| assert.isFalse(b.isEqual(a)); |
| assert.isTrue(b.isEqual(c)); |
| assert.isFalse(c.isEqual(d)); |
| assert.isFalse(c.isEqual(e)); |
| }); |
| |
| it('compares using script ids in stack traces', () => { |
| const functionName = 'foo'; |
| const url = 'http://localhost/foo.js'; |
| const lineNumber = 1; |
| const columnNumber = 1; |
| const a = |
| newMessage({stackTrace: {callFrames: [{functionName, scriptId: scriptId1, url, lineNumber, columnNumber}]}}); |
| const b = |
| newMessage({stackTrace: {callFrames: [{functionName, scriptId: scriptId2, url, lineNumber, columnNumber}]}}); |
| assert.isFalse(a.isEqual(b)); |
| }); |
| |
| it('logs a message on main frame navigation', async () => { |
| Common.Settings.Settings.instance().moduleSetting('preserve-console-log').set(true); |
| const consoleLog = sinon.spy(Common.Console.Console.instance(), 'log'); |
| const tabTarget = createTarget({type: SDK.Target.Type.TAB}); |
| const mainFrameTarget = createTarget({type: SDK.Target.Type.FRAME, parentTarget: tabTarget}); |
| const subframeTarget = createTarget({type: SDK.Target.Type.FRAME, parentTarget: mainFrameTarget}); |
| await getInitializedResourceTreeModel(subframeTarget); |
| navigate(getMainFrame(subframeTarget)); |
| sinon.assert.notCalled(consoleLog); |
| |
| await getInitializedResourceTreeModel(mainFrameTarget); |
| navigate(getMainFrame(mainFrameTarget)); |
| sinon.assert.calledOnce(consoleLog); |
| assert.isTrue(consoleLog.calledOnceWith(`Navigated to ${FRAME_URL}`)); |
| }); |
| |
| it('logs a message on main frame navigation via bfcache', async () => { |
| Common.Settings.Settings.instance().moduleSetting('preserve-console-log').set(true); |
| const consoleLog = sinon.spy(Common.Console.Console.instance(), 'log'); |
| const tabTarget = createTarget({type: SDK.Target.Type.TAB}); |
| const mainFrameTarget = createTarget({type: SDK.Target.Type.FRAME, parentTarget: tabTarget}); |
| const subframeTarget = createTarget({type: SDK.Target.Type.FRAME, parentTarget: mainFrameTarget}); |
| await getInitializedResourceTreeModel(subframeTarget); |
| navigate(getMainFrame(subframeTarget), {}, Protocol.Page.NavigationType.BackForwardCacheRestore); |
| sinon.assert.notCalled(consoleLog); |
| |
| await getInitializedResourceTreeModel(mainFrameTarget); |
| navigate(getMainFrame(mainFrameTarget), {}, Protocol.Page.NavigationType.BackForwardCacheRestore); |
| sinon.assert.calledOnce(consoleLog); |
| assert.isTrue(consoleLog.calledOnceWith( |
| `Navigation to ${FRAME_URL} was restored from back/forward cache (see https://web.dev/bfcache/)`)); |
| }); |
| |
| it('discards duplicate console messages with identical timestamps', async () => { |
| const target = createTarget({type: SDK.Target.Type.FRAME}); |
| const runtimeModel = target.model(SDK.RuntimeModel.RuntimeModel); |
| assert.exists(runtimeModel); |
| const resourceTreeModel = target.model(SDK.ResourceTreeModel.ResourceTreeModel); |
| assert.exists(resourceTreeModel); |
| const consoleModel = target.model(SDK.ConsoleModel.ConsoleModel); |
| assert.exists(consoleModel); |
| const addMessage = sinon.spy(consoleModel, 'addMessage'); |
| resourceTreeModel.dispatchEventToListeners(SDK.ResourceTreeModel.Events.CachedResourcesLoaded, resourceTreeModel); |
| |
| const consoleAPICall = { |
| type: Protocol.Runtime.ConsoleAPICalledEventType.Log, |
| args: [{type: Protocol.Runtime.RemoteObjectType.String, value: 'log me'}], |
| executionContextId: 1, |
| timestamp: 123456.789, |
| }; |
| |
| runtimeModel.dispatchEventToListeners(SDK.RuntimeModel.Events.ConsoleAPICalled, consoleAPICall); |
| sinon.assert.calledOnce(addMessage); |
| assert.isTrue(addMessage.calledOnceWith(sinon.match({messageText: 'log me'}))); |
| |
| runtimeModel.dispatchEventToListeners(SDK.RuntimeModel.Events.ConsoleAPICalled, consoleAPICall); |
| sinon.assert.calledOnce(addMessage); |
| |
| runtimeModel.dispatchEventToListeners( |
| SDK.RuntimeModel.Events.ConsoleAPICalled, {...consoleAPICall, timestamp: 123457.000}); |
| sinon.assert.calledTwice(addMessage); |
| sinon.assert.calledWith(addMessage.secondCall, sinon.match({messageText: 'log me'})); |
| }); |
| |
| it('clears when main frame global object cleared', async () => { |
| Common.Settings.Settings.instance().moduleSetting('preserve-console-log').set(false); |
| const tabTarget = createTarget({type: SDK.Target.Type.TAB}); |
| const mainFrameTarget = createTarget({type: SDK.Target.Type.FRAME, parentTarget: tabTarget}); |
| const subframeTarget = createTarget({type: SDK.Target.Type.FRAME, parentTarget: mainFrameTarget}); |
| const clearGlobalObjectOnTarget = (target: SDK.Target.Target) => { |
| const resourceTreeModel = target.model(SDK.ResourceTreeModel.ResourceTreeModel); |
| assert.exists(resourceTreeModel); |
| resourceTreeModel.dispatchEventToListeners(SDK.ResourceTreeModel.Events.CachedResourcesLoaded, resourceTreeModel); |
| |
| const debuggerModel = target.model(SDK.DebuggerModel.DebuggerModel); |
| assert.exists(debuggerModel); |
| debuggerModel.dispatchEventToListeners(SDK.DebuggerModel.Events.GlobalObjectCleared, debuggerModel); |
| }; |
| |
| let consoleClearEventsTabTarget = 0; |
| let consoleClearEventsMainFrameTarget = 0; |
| let consoleClearEventsSubframeTarget = 0; |
| tabTarget.model(SDK.ConsoleModel.ConsoleModel) |
| ?.addEventListener(SDK.ConsoleModel.Events.ConsoleCleared, () => ++consoleClearEventsTabTarget); |
| mainFrameTarget.model(SDK.ConsoleModel.ConsoleModel) |
| ?.addEventListener(SDK.ConsoleModel.Events.ConsoleCleared, () => ++consoleClearEventsMainFrameTarget); |
| subframeTarget.model(SDK.ConsoleModel.ConsoleModel) |
| ?.addEventListener(SDK.ConsoleModel.Events.ConsoleCleared, () => ++consoleClearEventsSubframeTarget); |
| |
| clearGlobalObjectOnTarget(subframeTarget); |
| assert.strictEqual(consoleClearEventsTabTarget, 0); |
| assert.strictEqual(consoleClearEventsMainFrameTarget, 0); |
| assert.strictEqual(consoleClearEventsSubframeTarget, 0); |
| |
| clearGlobalObjectOnTarget(mainFrameTarget); |
| assert.strictEqual(consoleClearEventsTabTarget, 0); |
| assert.strictEqual(consoleClearEventsMainFrameTarget, 1); |
| assert.strictEqual(consoleClearEventsSubframeTarget, 0); |
| }); |
| }); |