blob: 0e93b0a1f09906b6b2f25405924175f749de3322 [file] [log] [blame]
// 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 * as Platform from '../../core/platform/platform.js';
import * as SDK from '../../core/sdk/sdk.js';
import type * as Protocol from '../../generated/protocol.js';
import {assertScreenshot, renderElementIntoDOM} from '../../testing/DOMHelpers.js';
import {describeWithEnvironment} from '../../testing/EnvironmentHelpers.js';
import * as UI from '../../ui/legacy/legacy.js';
import {render} from '../../ui/lit/lit.js';
import * as Network from './network.js';
const {urlString} = Platform.DevToolsPath;
describeWithEnvironment('RequestPayloadView', () => {
it('decodes headers', async () => {
const encoded = 'Test+%21%40%23%24%25%5E%26*%28%29_%2B+parameters.';
const parameterElement = document.createDocumentFragment();
render(Network.RequestPayloadView.RequestPayloadView.formatParameter(encoded, '', true), parameterElement);
assert.strictEqual(parameterElement.textContent?.trim(), 'Test !@#$%^&*()_+ parameters.');
});
it('does not decode headers if decodeParameters is false', async () => {
const encoded = 'Test+%21%40%23%24%25%5E%26*%28%29_%2B+parameters.';
const parameterElement = document.createDocumentFragment();
render(Network.RequestPayloadView.RequestPayloadView.formatParameter(encoded, '', false), parameterElement);
assert.strictEqual(parameterElement.textContent?.trim(), encoded);
});
it('adds the class name to the element', async () => {
const parameterElement = document.createDocumentFragment();
render(Network.RequestPayloadView.RequestPayloadView.formatParameter('test', 'test-class', true), parameterElement);
const div = parameterElement.firstElementChild;
assert.isNotNull(div);
assert.isTrue(div?.classList.contains('test-class'));
});
it('adds the empty-value class when value is empty', async () => {
const parameterElement = document.createDocumentFragment();
render(Network.RequestPayloadView.RequestPayloadView.formatParameter('', '', true), parameterElement);
const div = parameterElement.firstElementChild;
assert.isNotNull(div);
assert.isTrue(div?.classList.contains('empty-value'));
});
it('shows error message when decoding fails', async () => {
const invalidEncoded = '%E0%A4%A'; // Invalid URI sequence
const parameterElement = document.createDocumentFragment();
render(Network.RequestPayloadView.RequestPayloadView.formatParameter(invalidEncoded, '', true), parameterElement);
const errorSpan = parameterElement.querySelector('.payload-decode-error');
assert.isNotNull(errorSpan);
assert.strictEqual(errorSpan?.textContent, '(unable to decode value)');
});
it('displays query string parameters', async () => {
const request = SDK.NetworkRequest.NetworkRequest.create(
'requestId' as Protocol.Network.RequestId, urlString`https://example.com/api?foo=bar&baz=qux`, urlString``,
null, null, null);
const view = new Network.RequestPayloadView.RequestPayloadView();
view.request = request;
renderElementIntoDOM(view);
view.wasShown();
await assertScreenshot('network/request-payload-query-params.png');
});
it('displays form data parameters', async () => {
const request = SDK.NetworkRequest.NetworkRequest.create(
'requestId' as Protocol.Network.RequestId, urlString`https://example.com/api`, urlString``, null, null, null);
request.setRequestHeaders([{name: 'Content-Type', value: 'application/x-www-form-urlencoded'}]);
// Mock requestFormData to return URL-encoded form data.
sinon.stub(request, 'requestFormData').resolves('foo=bar&baz=qux');
const view = new Network.RequestPayloadView.RequestPayloadView();
view.request = request;
renderElementIntoDOM(view);
view.wasShown();
await view.updateComplete;
await assertScreenshot('network/request-payload-data-params.png');
});
it('toggles URL decoding', async () => {
const request = SDK.NetworkRequest.NetworkRequest.create(
'requestId' as Protocol.Network.RequestId, urlString`https://example.com/api?foo=bar%20baz`, urlString``, null,
null, null);
const view = new Network.RequestPayloadView.RequestPayloadView();
view.request = request;
renderElementIntoDOM(view);
view.wasShown();
await view.updateComplete;
const treeOutline = view.element.querySelector<HTMLElement>('.request-payload-tree');
assert.isNotNull(treeOutline);
const shadowRoot = treeOutline.shadowRoot;
assert.isNotNull(shadowRoot);
const getPayloadValues = () => {
return Array.from(shadowRoot.querySelectorAll('.payload-value')).map(el => el.textContent).join(' ');
};
// Initial state: Decoded
assert.include(getPayloadValues(), 'bar baz');
const toggleButton = shadowRoot.querySelectorAll<HTMLElement>('.payload-toggle').item(1);
assert.exists(toggleButton);
toggleButton.click();
await view.updateComplete;
// Take the screenshot before checking contents, this forces the widget to render.
await assertScreenshot('network/request-payload-url-decoding.png');
// Toggled state: Encoded
assert.include(getPayloadValues(), 'bar%20baz');
});
it('toggles between parsed and source view', async () => {
const request = SDK.NetworkRequest.NetworkRequest.create(
'requestId' as Protocol.Network.RequestId, urlString`https://example.com/api?foo=bar`, urlString``, null, null,
null);
const view = new Network.RequestPayloadView.RequestPayloadView();
view.request = request;
renderElementIntoDOM(view);
view.wasShown();
await view.updateComplete;
const treeOutline = view.element.querySelector<HTMLElement>('.request-payload-tree');
const shadowRoot = treeOutline?.shadowRoot;
assert.exists(shadowRoot);
const getTextContent = () => {
const names = Array.from(shadowRoot.querySelectorAll('.payload-name')).map(el => el.textContent);
const values = Array.from(shadowRoot.querySelectorAll('.payload-value')).map(el => el.textContent);
const widgets = Array.from(shadowRoot.querySelectorAll('devtools-widget')).map(el => el.textContent);
return [...names, ...values, ...widgets].join(' ');
};
// Initial state: Parsed (foo: bar)
const initialText = getTextContent();
assert.include(initialText, 'foo');
assert.include(initialText, 'bar');
// Find "View source" button.
const buttons = shadowRoot.querySelectorAll<HTMLElement>('.payload-toggle');
const viewSourceButton = Array.from(buttons).find(b => b.textContent?.includes('View source'));
assert.exists(viewSourceButton);
viewSourceButton.click();
await view.updateComplete;
await assertScreenshot('network/request-payload-url-source-view.png');
// Source state: "foo=bar"
const sourceText = getTextContent();
assert.include(sourceText, 'foo=bar');
// Toggle back
const viewParsedButton = Array.from(shadowRoot.querySelectorAll<HTMLElement>('.payload-toggle'))
.find(b => b.textContent?.includes('View parsed'));
assert.exists(viewParsedButton);
viewParsedButton.click();
await view.updateComplete;
const finalText = getTextContent();
assert.include(finalText, 'foo');
assert.include(finalText, 'bar');
});
it('truncates long source text and in a ShowMore widget', async () => {
const text = 'A'.repeat(3010);
const request = SDK.NetworkRequest.NetworkRequest.create(
'requestId' as Protocol.Network.RequestId, urlString`https://example.com/api?foo=${text}`, urlString``, null,
null, null);
const view = new Network.RequestPayloadView.RequestPayloadView();
view.request = request;
renderElementIntoDOM(view);
view.wasShown();
await view.updateComplete;
const treeOutline = view.element.querySelector<HTMLElement>('.request-payload-tree');
assert.exists(treeOutline);
const shadowRoot = treeOutline?.shadowRoot;
assert.exists(shadowRoot);
// Switch to View Source
const buttons = shadowRoot.querySelectorAll<HTMLElement>('.payload-toggle');
const viewSourceButton = Array.from(buttons).find(b => b.textContent?.includes('View source'));
assert.exists(viewSourceButton);
viewSourceButton.click();
await view.updateComplete;
const payloadValue = shadowRoot.querySelector('devtools-widget');
assert.exists(payloadValue);
const payloadValueWidget = UI.Widget.Widget.get(payloadValue);
assert.instanceOf(payloadValueWidget, Network.ShowMoreDetailsWidget.ShowMoreDetailsWidget);
assert.strictEqual(payloadValueWidget.text, `foo=${text}`);
await assertScreenshot('network/request-payload-show-more.png');
});
it('displays JSON payload and toggles between parsed and source view', async () => {
const request = SDK.NetworkRequest.NetworkRequest.create(
'requestId' as Protocol.Network.RequestId, urlString`https://example.com/api`, urlString``, null, null, null);
request.setRequestHeaders([{name: 'Content-Type', value: 'application/json'}]);
sinon.stub(request, 'requestFormData').resolves('{"foo": "bar"}');
const view = new Network.RequestPayloadView.RequestPayloadView();
view.request = request;
renderElementIntoDOM(view);
view.wasShown();
await view.updateComplete;
const treeOutline = view.element.querySelector<HTMLElement>('.request-payload-tree');
assert.exists(treeOutline);
const shadowRoot = treeOutline.shadowRoot;
assert.exists(shadowRoot);
const getButton = (text: string) => {
const buttons = shadowRoot.querySelectorAll<HTMLElement>('li:not(.hidden) .payload-toggle');
return Array.from(buttons).find(b => b.textContent?.includes(text));
};
// Initial state: Parsed.
// Check that "View source" button exists
const viewSourceButton = getButton('View source');
assert.exists(viewSourceButton);
await assertScreenshot('network/request-payload-json.png');
// Toggle to source
viewSourceButton?.click();
await view.updateComplete;
// Check for source text
const payloadValue = shadowRoot.querySelector('devtools-widget');
assert.exists(payloadValue);
const payloadValueWidget = UI.Widget.Widget.get(payloadValue);
assert.instanceOf(payloadValueWidget, Network.ShowMoreDetailsWidget.ShowMoreDetailsWidget);
// Check that "View parsed" button exists
const viewParsedButton = getButton('View parsed');
assert.exists(viewParsedButton);
await assertScreenshot('network/request-payload-json-source.png');
// Click "View parsed"
viewParsedButton?.click();
await view.updateComplete;
// Check that "View source" button exists again
assert.exists(getButton('View source'));
// And check that source text is gone
assert.isNull(shadowRoot.querySelector('.payload-value'));
});
});