зеркало из https://github.com/mozilla/gecko-dev.git
Bug 1611817 - Add mochitests for the performance-new panel in DevTools; r=julienw
Note that this patch also updates the setReactFriendlyInputValue function in head.js, as it was not working for selects. Differential Revision: https://phabricator.services.mozilla.com/D65151 --HG-- extra : moz-landing-system : lando
This commit is contained in:
Родитель
68c5d4cc04
Коммит
45cedbd5fa
|
@ -18,7 +18,9 @@ support-files =
|
|||
[browser_aboutprofiling-threads-behavior.js]
|
||||
[browser_aboutprofiling-presets.js]
|
||||
[browser_aboutprofiling-presets-custom.js]
|
||||
[browser_devtools-presets.js]
|
||||
[browser_devtools-record-capture.js]
|
||||
[browser_devtools-record-discard.js]
|
||||
[browser_webchannel-enable-menu-button.js]
|
||||
|
||||
[browser_popup-record-capture.js]
|
||||
[browser_popup-record-discard.js]
|
||||
|
|
|
@ -0,0 +1,45 @@
|
|||
/* This Source Code Form is subject to the terms of the Mozilla Public
|
||||
* License, v. 2.0. If a copy of the MPL was not distributed with this
|
||||
* file, You can obtain one at http://mozilla.org/MPL/2.0/. */
|
||||
"use strict";
|
||||
|
||||
add_task(async function test() {
|
||||
info("Test that about:profiling presets configure the profiler");
|
||||
|
||||
if (!Services.profiler.GetFeatures().includes("stackwalk")) {
|
||||
ok(true, "This platform does not support stackwalking, skip this test.");
|
||||
return;
|
||||
}
|
||||
|
||||
await withDevToolsPanel(async document => {
|
||||
{
|
||||
const presets = await getNearestInputFromText(document, "Settings");
|
||||
|
||||
is(presets.value, "web-developer", "The presets default to webdev mode.");
|
||||
ok(
|
||||
!(await devToolsActiveConfigurationHasFeature(document, "stackwalk")),
|
||||
"Stack walking is not used in Web Developer mode."
|
||||
);
|
||||
}
|
||||
|
||||
{
|
||||
const presets = await getNearestInputFromText(document, "Settings");
|
||||
setReactFriendlyInputValue(presets, "firefox-platform");
|
||||
is(
|
||||
presets.value,
|
||||
"firefox-platform",
|
||||
"The preset was changed to Firefox Platform"
|
||||
);
|
||||
ok(
|
||||
await devToolsActiveConfigurationHasFeature(document, "stackwalk"),
|
||||
"Stack walking is used in Firefox Platform mode."
|
||||
);
|
||||
}
|
||||
});
|
||||
|
||||
const { revertRecordingPreferences } = ChromeUtils.import(
|
||||
"resource://devtools/client/performance-new/popup/background.jsm.js"
|
||||
);
|
||||
|
||||
revertRecordingPreferences();
|
||||
});
|
|
@ -0,0 +1,47 @@
|
|||
/* This Source Code Form is subject to the terms of the Mozilla Public
|
||||
* License, v. 2.0. If a copy of the MPL was not distributed with this
|
||||
* file, You can obtain one at http://mozilla.org/MPL/2.0/. */
|
||||
|
||||
"use strict";
|
||||
|
||||
add_task(async function test() {
|
||||
info("Test that DevTools can capture profiles.");
|
||||
|
||||
await setProfilerFrontendUrl(
|
||||
"http://example.com/browser/devtools/client/performance-new/test/browser/fake-frontend.html"
|
||||
);
|
||||
|
||||
await withDevToolsPanel(async document => {
|
||||
{
|
||||
const button = await getActiveButtonFromText(document, "Start recording");
|
||||
info("Click the button to start recording");
|
||||
button.click();
|
||||
}
|
||||
|
||||
{
|
||||
const button = await getActiveButtonFromText(
|
||||
document,
|
||||
"Capture recording"
|
||||
);
|
||||
info("Click the button to capture the recording.");
|
||||
button.click();
|
||||
}
|
||||
|
||||
info(
|
||||
"If the DevTools successfully injects a profile into the page, then the " +
|
||||
"fake frontend will rename the title of the page."
|
||||
);
|
||||
|
||||
await checkTabLoadedProfile({
|
||||
initialTitle: "Waiting on the profile",
|
||||
successTitle: "Profile received",
|
||||
errorTitle: "Error",
|
||||
});
|
||||
});
|
||||
|
||||
const { revertRecordingPreferences } = ChromeUtils.import(
|
||||
"resource://devtools/client/performance-new/popup/background.jsm.js"
|
||||
);
|
||||
|
||||
revertRecordingPreferences();
|
||||
});
|
|
@ -0,0 +1,41 @@
|
|||
/* This Source Code Form is subject to the terms of the Mozilla Public
|
||||
* License, v. 2.0. If a copy of the MPL was not distributed with this
|
||||
* file, You can obtain one at http://mozilla.org/MPL/2.0/. */
|
||||
|
||||
"use strict";
|
||||
|
||||
add_task(async function test() {
|
||||
info("Test that DevTools can capture profiles.");
|
||||
|
||||
await setProfilerFrontendUrl(
|
||||
"http://example.com/browser/devtools/client/performance-new/test/browser/fake-frontend.html"
|
||||
);
|
||||
|
||||
await withDevToolsPanel(async document => {
|
||||
{
|
||||
const button = await getActiveButtonFromText(document, "Start recording");
|
||||
info("Click the button to start recording");
|
||||
button.click();
|
||||
}
|
||||
|
||||
{
|
||||
const button = await getActiveButtonFromText(
|
||||
document,
|
||||
"Cancel recording"
|
||||
);
|
||||
info("Click the button to discard to profile.");
|
||||
button.click();
|
||||
}
|
||||
|
||||
{
|
||||
const button = await getActiveButtonFromText(document, "Start recording");
|
||||
ok(Boolean(button), "The start recording button is available again.");
|
||||
}
|
||||
});
|
||||
|
||||
const { revertRecordingPreferences } = ChromeUtils.import(
|
||||
"resource://devtools/client/performance-new/popup/background.jsm.js"
|
||||
);
|
||||
|
||||
revertRecordingPreferences();
|
||||
});
|
|
@ -40,7 +40,14 @@
|
|||
if (
|
||||
profile &&
|
||||
typeof profile === 'object' &&
|
||||
profile instanceof ArrayBuffer
|
||||
(
|
||||
// The popup injects the compressed profile as an ArrayBuffer.
|
||||
(profile instanceof ArrayBuffer) ||
|
||||
// DevTools injects the profile as just the plain object, although
|
||||
// maybe in the future it could also do it as a compressed profile
|
||||
// to make this faster.
|
||||
Object.keys(profile).includes("threads")
|
||||
)
|
||||
) {
|
||||
// The profile looks good!
|
||||
document.title = successTitle;
|
||||
|
|
|
@ -208,7 +208,11 @@ async function toggleOpenProfilerPopup() {
|
|||
* @returns {Promise}
|
||||
*/
|
||||
function setProfilerFrontendUrl(url) {
|
||||
info("Setting the profiler URL to the fake frontend.");
|
||||
info(
|
||||
"Setting the profiler URL to the fake frontend. Note that this doesn't currently " +
|
||||
"support the WebChannels, so expect a few error messages about the WebChannel " +
|
||||
"URLs not being correct."
|
||||
);
|
||||
return SpecialPowers.pushPrefEnv({
|
||||
set: [
|
||||
// Make sure observer and testing function run in the same process
|
||||
|
@ -331,6 +335,41 @@ function withAboutProfiling(callback) {
|
|||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* Open DevTools and view the performance-new tab. After running the callback, clean
|
||||
* up the test.
|
||||
*
|
||||
* @template T
|
||||
* @param {(Document) => T} callback
|
||||
* @returns {Promise<T>}
|
||||
*/
|
||||
async function withDevToolsPanel(callback) {
|
||||
SpecialPowers.pushPrefEnv({
|
||||
set: [["devtools.performance.new-panel-enabled", "true"]],
|
||||
});
|
||||
|
||||
const { gDevTools } = require("devtools/client/framework/devtools");
|
||||
const { TargetFactory } = require("devtools/client/framework/target");
|
||||
|
||||
info("Create a new about:blank tab.");
|
||||
const tab = BrowserTestUtils.addTab(gBrowser, "about:blank");
|
||||
|
||||
info("Begin to open the DevTools and the performance-new panel.");
|
||||
const target = await TargetFactory.forTab(tab);
|
||||
const toolbox = await gDevTools.showToolbox(target, "performance");
|
||||
|
||||
const { document } = toolbox.getCurrentPanel().panelWin;
|
||||
|
||||
info("The performance-new panel is now open and ready to use.");
|
||||
await callback(document);
|
||||
|
||||
info("About to remove the about:blank tab");
|
||||
await toolbox.destroy();
|
||||
BrowserTestUtils.removeTab(tab);
|
||||
info("The about:blank tab is now removed.");
|
||||
await new Promise(resolve => setTimeout(resolve, 500));
|
||||
}
|
||||
|
||||
/**
|
||||
* Start and stop the profiler to get the current active configuration. This is
|
||||
* done programmtically through the nsIProfiler interface, rather than through click
|
||||
|
@ -389,8 +428,43 @@ function activeConfigurationHasThread(thread) {
|
|||
}
|
||||
|
||||
/**
|
||||
* Grabs the associated input from the element, or it walks up the DOM from a text
|
||||
* element and tries to query select an input.
|
||||
* Use user driven events to start the profiler, and then get the active configuration
|
||||
* of the profiler. This is similar to functions in the head.js file, but is specific
|
||||
* for the DevTools situation. The UI complains if the profiler stops unexpectedly.
|
||||
*
|
||||
* @param {Document} document
|
||||
* @param {string} feature
|
||||
* @returns {boolean}
|
||||
*/
|
||||
async function devToolsActiveConfigurationHasFeature(document, feature) {
|
||||
info("Get the active configuration of the profiler via user driven events.");
|
||||
const start = await getActiveButtonFromText(document, "Start recording");
|
||||
info("Click the button to start recording.");
|
||||
start.click();
|
||||
|
||||
// Get the cancel button first, so that way we know the profile has actually
|
||||
// been recorded.
|
||||
const cancel = await getActiveButtonFromText(document, "Cancel recording");
|
||||
|
||||
const { activeConfiguration } = Services.profiler;
|
||||
if (!activeConfiguration) {
|
||||
throw new Error(
|
||||
"Expected to find an active configuration for the profile."
|
||||
);
|
||||
}
|
||||
|
||||
info("Click the cancel button to discard the profile..");
|
||||
cancel.click();
|
||||
|
||||
// Wait until the start button is back.
|
||||
await getActiveButtonFromText(document, "Start recording");
|
||||
|
||||
return activeConfiguration.features.includes(feature);
|
||||
}
|
||||
|
||||
/**
|
||||
* Selects an element with some given text, then it walks up the DOM until it finds
|
||||
* an input or select element via a call to querySelector.
|
||||
*
|
||||
* @param {Document} document
|
||||
* @param {string} text
|
||||
|
@ -405,12 +479,40 @@ async function getNearestInputFromText(document, text) {
|
|||
// A non-label node
|
||||
let next = textElement;
|
||||
while ((next = next.parentElement)) {
|
||||
const input = next.querySelector("input");
|
||||
const input = next.querySelector("input, select");
|
||||
if (input) {
|
||||
return input;
|
||||
}
|
||||
}
|
||||
throw new Error("Could not find an input near text element.");
|
||||
throw new Error("Could not find an input or select near the text element.");
|
||||
}
|
||||
|
||||
/**
|
||||
* Grabs the closest button element from a given snippet of text, and make sure
|
||||
* the button is not disabled.
|
||||
*
|
||||
* @param {Document} document
|
||||
* @param {string} text
|
||||
* @param {HTMLButtonElement}
|
||||
*/
|
||||
async function getActiveButtonFromText(document, text) {
|
||||
// This could select a span inside the button, or the button itself.
|
||||
let button = await getElementFromDocumentByText(document, text);
|
||||
|
||||
while (button.tagName !== "button") {
|
||||
// Walk up until a button element is found.
|
||||
button = button.parentElement;
|
||||
if (!button) {
|
||||
throw new Error(`Unable to find a button from the text "${text}"`);
|
||||
}
|
||||
}
|
||||
|
||||
await waitUntil(
|
||||
() => !button.disabled,
|
||||
"Waiting until the button is not disabled."
|
||||
);
|
||||
|
||||
return button;
|
||||
}
|
||||
|
||||
/**
|
||||
|
@ -473,20 +575,21 @@ function withWebChannelTestDocument(callback) {
|
|||
/**
|
||||
* Set a React-friendly input value. Doing this the normal way doesn't work.
|
||||
*
|
||||
* See: https://github.com/facebook/react/issues/10135#issuecomment-314441175
|
||||
* See https://github.com/facebook/react/issues/10135#issuecomment-500929024
|
||||
*
|
||||
* @param {HTMLInputElement} input
|
||||
* @param {string} value
|
||||
*/
|
||||
function setReactFriendlyInputValue(element, value) {
|
||||
const valueSetter = Object.getOwnPropertyDescriptor(element, "value").set;
|
||||
const prototype = Object.getPrototypeOf(element);
|
||||
const prototypeValueSetter = Object.getOwnPropertyDescriptor(
|
||||
prototype,
|
||||
"value"
|
||||
).set;
|
||||
function setReactFriendlyInputValue(input, value) {
|
||||
const previousValue = input.value;
|
||||
|
||||
if (valueSetter && valueSetter !== prototypeValueSetter) {
|
||||
prototypeValueSetter.call(element, value);
|
||||
} else {
|
||||
valueSetter.call(element, value);
|
||||
input.value = value;
|
||||
|
||||
const tracker = input._valueTracker;
|
||||
if (tracker) {
|
||||
tracker.setValue(previousValue);
|
||||
}
|
||||
element.dispatchEvent(new Event("input", { bubbles: true }));
|
||||
|
||||
// 'change' instead of 'input', see https://github.com/facebook/react/issues/11488#issuecomment-381590324
|
||||
input.dispatchEvent(new Event("change", { bubbles: true }));
|
||||
}
|
||||
|
|
Загрузка…
Ссылка в новой задаче