UNPKG

chrome-devtools-frontend

Version:
213 lines (184 loc) • 8.83 kB
// Copyright 2024 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 * as SDK from '../../core/sdk/sdk.js'; import * as Protocol from '../../generated/protocol.js'; import {describeWithRealConnection, getExecutionContext} from '../../testing/RealConnection.js'; import * as Freestyler from './EvaluateAction.js'; describe('FreestylerEvaluateAction', () => { describe('error handling', () => { function executeWithResult( mockResult: SDK.RuntimeModel.EvaluationResult, pausedOnBreakpoint = false): Promise<string> { const executionContextStub = sinon.createStubInstance(SDK.RuntimeModel.ExecutionContext); executionContextStub.debuggerModel = sinon.createStubInstance(SDK.DebuggerModel.DebuggerModel); if (pausedOnBreakpoint) { executionContextStub.debuggerModel.selectedCallFrame = () => { return sinon.createStubInstance(SDK.DebuggerModel.CallFrame); }; } executionContextStub.callFunctionOn.resolves(mockResult); executionContextStub.runtimeModel = sinon.createStubInstance(SDK.RuntimeModel.RuntimeModel); return Freestyler.EvaluateAction.execute('', [], executionContextStub, {throwOnSideEffect: false}); } function mockRemoteObject(overrides: Partial<SDK.RemoteObject.RemoteObject> = {}): SDK.RemoteObject.RemoteObject { return sinon.createStubInstance(SDK.RemoteObject.RemoteObject, { ...(overrides.className ? {className: overrides.className} : null), ...(overrides.subtype ? {subtype: overrides.subtype} : null), ...(overrides.type ? {type: overrides.type} : null), ...(overrides.value ? {value: overrides.value} : null), ...(overrides.preview ? {preview: overrides.preview} : null), }); } function mockExceptionDetails({description}: {description: string}): Protocol.Runtime.ExceptionDetails { return { exceptionId: 3, text: 'SyntaxError', lineNumber: 3, columnNumber: 3, exception: {type: Protocol.Runtime.RemoteObjectType.String, description}, }; } beforeEach(() => { sinon.restore(); }); it('should serialize a CDP error as a string', async () => { assert.strictEqual(await executeWithResult({error: 'errorMessage'}), 'Error: errorMessage'); }); it('should throw an ExecutionError when the debugger is paused', async () => { assert.strictEqual( await executeWithResult({error: 'errorMessage'}, true), 'Error: Cannot evaluate JavaScript because the execution is paused on a breakpoint.'); }); it('should throw an ExecutionError with the description of the exception if response included exception details', async () => { assert.strictEqual( await executeWithResult({ object: mockRemoteObject(), exceptionDetails: mockExceptionDetails({description: 'Error description'}), }), 'Error: Error description'); }); it('should throw a SideEffectError when the resulted exception starts with possible side effect error', async () => { try { await executeWithResult({ object: mockRemoteObject(), exceptionDetails: mockExceptionDetails({description: 'EvalError: Possible side-effect in debug-evaluate'}), }); assert.fail('not reachable'); } catch (err) { assert.instanceOf(err, Freestyler.SideEffectError); assert.strictEqual(err.message, 'EvalError: Possible side-effect in debug-evaluate'); } }); }); describeWithRealConnection('serialization', () => { async function executionContextForTest() { const targetManager = SDK.TargetManager.TargetManager.instance(); const target = targetManager.rootTarget(); const runtimeModel = target!.model(SDK.RuntimeModel.RuntimeModel); return getExecutionContext(runtimeModel!); } async function executeForTest(action: string, throwOnSideEffect = false) { const functionDeclaration = `async function ($0) { try { ${action} ; return ((typeof data !== "undefined") ? data : undefined); } catch (error) { return error; } }`; return Freestyler.EvaluateAction.execute( functionDeclaration, [], await executionContextForTest(), {throwOnSideEffect}); } it('should serialize primitive values correctly', async () => { assert.strictEqual(await executeForTest('const data = "string"'), '\'string\''); assert.strictEqual(await executeForTest('const data = 999n'), '999n'); assert.strictEqual(await executeForTest('const data = true'), 'true'); assert.strictEqual(await executeForTest('const data = undefined'), 'undefined'); assert.strictEqual(await executeForTest('const data = 42'), '42'); assert.strictEqual(await executeForTest('const data = Symbol("sym")'), 'Symbol(sym)'); }); describe('HTMLElement', () => { it('should work with plain nodes', async () => { const serializedElement = await executeForTest(` const el = document.createElement('div'); const data = el; `); assert.strictEqual(serializedElement, '"<div></div>"'); }); it('should serialize node with classes', async () => { const serializedElement = await executeForTest(` const el = document.createElement('div'); el.classList.add('section'); el.classList.add('section-main'); const data = el; `); assert.strictEqual(serializedElement, '"<div class=\\"section section-main\\"></div>"'); }); it('should serialize node with id', async () => { const serializedElement = await executeForTest(` const el = document.createElement('div'); el.id = 'promotion-section'; const data = el; `); assert.strictEqual(serializedElement, '"<div id=\\"promotion-section\\"></div>"'); }); it('should serialize node with class and id', async () => { const serializedElement = await executeForTest(` const el = document.createElement('div'); el.id = 'promotion-section'; el.classList.add('section'); const data = el; `); assert.strictEqual(serializedElement, '"<div id=\\"promotion-section\\" class=\\"section\\"></div>"'); }); it('should serialize node with children', async () => { const serializedElement = await executeForTest(` const el = document.createElement('div'); const p = document.createElement('p'); el.appendChild(p); const data = el; `); assert.strictEqual(serializedElement, '"<div>...</div>"'); }); }); it('should serialize arrays correctly', async () => { assert.strictEqual(await executeForTest('const data = []'), '[]'); assert.strictEqual(await executeForTest('const data = [1]'), '[1]'); assert.strictEqual(await executeForTest('const data = [1, 2]'), '[1,2]'); assert.strictEqual(await executeForTest('const data = [{key: 1}]'), '[{"key":1}]'); }); it('should serialize objects correctly', async () => { assert.strictEqual(await executeForTest('const object = {key: "str"}; const data = object;'), '{"key":"str"}'); assert.strictEqual( await executeForTest('const object = {key: "str", secondKey: "str2"}; const data = object;'), '{"key":"str","secondKey":"str2"}'); assert.strictEqual(await executeForTest('const object = {key: 1}; const data = object;'), '{"key":1}'); }); it('should not continue serializing cycles', async () => { assert.strictEqual( await executeForTest(` const obj = { a: 1 }; obj.itself = obj; const data = obj; `), '{"a":1,"itself":"(cycle)"}'); }); it('should not include number keys for CSSStyleDeclaration', async () => { const result = await executeForTest('const data = getComputedStyle(document.body)'); const parsedResult = JSON.parse(result); assert.isUndefined(parsedResult[0]); }); it('should not trigger a side-effect for returning data', async () => { assert.deepEqual(await executeForTest('const data = {}', true), '{}'); }); it('should not trigger a side-effect on errors', async () => { assert.deepEqual(await executeForTest('throw new Error("test")', true), 'Error: test'); }); it('should not trigger a side-effect on syntax errors', async () => { assert.deepEqual(await executeForTest('const data = {;', true), 'Error: SyntaxError: Unexpected token \';\''); }); }); });