Quellcodebibliothek Statistik Leitseite products/Sources/formale Sprachen/C/Firefox/toolkit/content/tests/browser/   (Firefox Browser Version 153.0.1©)  Datei vom 27.6.2026 mit Größe 35 kB image not shown  

Quelle  browser_about_logging.js

  Sprache: JAVA
 

const PAGE = "about:logging";

const { PromptTestUtils } = ChromeUtils.importESModule(
  "resource://testing-common/PromptTestUtils.sys.mjs"
);

function clearLoggingPrefs() {
  for (let pref of Services.prefs.getBranch("logging.").getChildList("")) {
    if (pref === "config.clear_on_startup") {
      // Do not reset logging.config.clear_on_startup which is set by the
      // testing framework.
      continue;
    }
    info(`Clearing: ${pref}`);
    Services.prefs.clearUserPref("logging." + pref);
  }

  // Clear devtools.performance.recording preferences that may be set by about:logging
  const devtoolsPrefs = [
    "devtools.performance.recording.preset",
    "devtools.performance.recording.entries",
    "devtools.performance.recording.threads",
    "devtools.performance.recording.features",
    "devtools.performance.popup.intro-displayed",
  ];

  for (let pref of devtoolsPrefs) {
    Services.prefs.clearUserPref(pref);
  }
}

function clearUploadedProfilesDB(content) {
  return new Promise(resolve => {
    const deleteRequest = content.indexedDB.deleteDatabase(
      "aboutLoggingProfiles"
    );
    deleteRequest.onsuccess = deleteRequest.onerror = resolve;
  });
}

/**
 * This function will select a node from the XPath.
 * This function has been copied from the devtools' performance panel's tests.
 *
 * @returns {HTMLElement?}
 */

function getElementByXPath(document, path) {
  return document.evaluate(
    path,
    document,
    null,
    XPathResult.FIRST_ORDERED_NODE_TYPE,
    null
  ).singleNodeValue;
}

/**
 * This function looks inside of a document for some element that contains
 * the given text. It runs in a loop every requestAnimationFrame until it
 * finds the element. If it doesn't find the element it throws an error.
 * This function has been copied from the devtools' performance panel's tests.
 *
 * @param {HTMLDocument} document
 * @param {string} text
 * @returns {Promise<HTMLElement>}
 */

async function getElementFromDocumentByText(document, text) {
  // Fallback on aria-label if there are no results for the text xpath.
  const xpath = `//*[contains(text(), '${text}')] | //*[contains(@aria-label, '${text}')]`;
  return TestUtils.waitForCondition(() => {
    const element = getElementByXPath(document, xpath);
    if (element && BrowserTestUtils.isVisible(element)) {
      return element;
    }
    return null;
  }, `Trying to find a visible element with the text "${text}".`);
}

// Before running, save any MOZ_LOG environment variable that might be preset,
// and restore them at the end of this test.
add_setup(async function saveRestoreLogModules() {
  await SpecialPowers.pushPrefEnv({
    set: [["test.wait300msAfterTabSwitch"true]],
  });

  let savedLogModules = Services.env.get("MOZ_LOG");
  Services.env.set("MOZ_LOG""");
  registerCleanupFunction(() => {
    clearLoggingPrefs();
    info(" -- Restoring log modules: " + savedLogModules);
    Services.env.set("MOZ_LOG", savedLogModules);
  });
});

// Test that some UI elements are disabled in some cirumstances.
add_task(async function testElementsDisabled() {
  // This test needs a MOZ_LOG env var set.
  Services.env.set("MOZ_LOG""example:4");
  await BrowserTestUtils.withNewTab(PAGE, async browser => {
    await SpecialPowers.spawn(browser, [], async () => {
      let $ = content.document.querySelector.bind(content.document);
      Assert.ok(
        $("#set-log-modules-button").disabled,
        "Because a MOZ_LOG env var is set by the harness, it should be impossible to set new log modules."
      );
    });
  });
  Services.env.set("MOZ_LOG""");

  await BrowserTestUtils.withNewTab(
    PAGE + "?modules=example:5&output=profiler",
    async browser => {
      await SpecialPowers.spawn(browser, [], async () => {
        let $ = content.document.querySelector.bind(content.document);
        Assert.ok(
          !$("#some-elements-unavailable").hidden,
          "If a log modules are configured via URL params, a warning should be visible."
        );
        Assert.ok(
          $("#set-log-modules-button").disabled,
          "If a log modules are configured via URL params, some in-page elements should be disabled (button)."
        );
        Assert.ok(
          $("#log-modules").disabled,
          "If a log modules are configured via URL params, some in-page elements should be disabled (input)."
        );
        Assert.ok(
          $("#logging-preset-dropdown").disabled,
          "If a log modules are configured via URL params, some in-page elements should be disabled (dropdown)."
        );
        Assert.ok(
          $("#radio-logging-profiler").disabled &&
            $("#radio-logging-file").disabled,
          "If the ouptut type is configured via URL param, the radio buttons should be disabled."
        );
      });
    }
  );
  await BrowserTestUtils.withNewTab(
    PAGE + "?preset=media-playback",
    async browser => {
      await SpecialPowers.spawn(browser, [], async () => {
        let $ = content.document.querySelector.bind(content.document);
        Assert.ok(
          !$("#some-elements-unavailable").hidden,
          "If a preset is selected via URL, a warning should be displayed."
        );
        Assert.ok(
          $("#set-log-modules-button").disabled,
          "If a preset is selected via URL, some in-page elements should be disabled (button)."
        );
        Assert.ok(
          $("#log-modules").disabled,
          "If a preset is selected via URL, some in-page elements should be disabled (input)."
        );
        Assert.ok(
          $("#logging-preset-dropdown").disabled,
          "If a preset is selected via URL, some in-page elements should be disabled (dropdown)."
        );
      });
    }
  );
  clearLoggingPrefs();
});

// Test URL parameters
const modulesInURL = "example:4,otherexample:5";
const presetInURL = "media-playback";
const threadsInURL = "example,otherexample";
const profilerPresetInURL = "media";
add_task(async function testURLParameters() {
  await BrowserTestUtils.withNewTab(
    PAGE + "?modules=" + modulesInURL,
    async browser => {
      await SpecialPowers.spawn(browser, [modulesInURL], async modulesInURL => {
        let $ = content.document.querySelector.bind(content.document);
        Assert.ok(
          !$("#some-elements-unavailable").hidden,
          "If modules are selected via URL, a warning should be displayed."
        );
        var inInputSorted = $("#log-modules").value.split(",").sort().join(",");
        var modulesSorted = modulesInURL.split(",").sort().join(",");
        Assert.equal(
          modulesSorted,
          inInputSorted,
          "When selecting modules via URL params, the log modules aren't immediately set"
        );
      });
    }
  );
  await BrowserTestUtils.withNewTab(
    {
      gBrowser,
      url: PAGE + "?preset=" + presetInURL,
    },
    async browser => {
      await SpecialPowers.spawn(browser, [presetInURL], async presetInURL => {
        let $ = content.document.querySelector.bind(content.document);
        Assert.ok(
          !$("#some-elements-unavailable").hidden,
          "If a preset is selected via URL, a warning should be displayed."
        );
        var inInputSorted = $("#log-modules").value.split(",").sort().join(",");
        var presetSorted = content
          .presets()
          [presetInURL].modules.split(",")
          .sort()
          .join(",");
        Assert.equal(
          inInputSorted,
          presetSorted,
          "When selecting a preset via URL params, the correct log modules are reflected in the input."
        );
      });
    }
  );
  await BrowserTestUtils.withNewTab(
    {
      gBrowser,
      url: PAGE + "?profiler-preset=" + profilerPresetInURL,
    },
    async browser => {
      await SpecialPowers.spawn(browser, [profilerPresetInURL], async inURL => {
        let $ = content.document.querySelector.bind(content.document);
        // Threads override doesn't have a UI element, the warning shouldn't
        // be displayed.
        Assert.ok(
          $("#some-elements-unavailable").hidden,
          "When overriding the profiler preset, no warning is displayed on the page."
        );
        var inSettings = content.settings().profilerPreset;
        Assert.equal(
          inSettings,
          inURL,
          "When overriding the profiler preset via URL param, the correct preset is set in the logging manager settings."
        );
      });
    }
  );
  await BrowserTestUtils.withNewTab(PAGE + "?profilerstacks", async browser => {
    await SpecialPowers.spawn(browser, [], async () => {
      let $ = content.document.querySelector.bind(content.document);
      Assert.ok(
        !$("#some-elements-unavailable").hidden,
        "If the profiler stacks config is set via URL, a warning should be displayed."
      );
      Assert.ok(
        $("#with-profiler-stacks-checkbox").disabled,
        "If the profiler stacks config is set via URL, its checkbox should be disabled."
      );

      Assert.ok(
        Services.prefs.getBoolPref("logging.config.profilerstacks"),
        "The preference for profiler stacks is set initially, as a result of parsing the URL parameter"
      );

      $("#radio-logging-file").click();
      $("#radio-logging-profiler").click();

      Assert.ok(
        $("#with-profiler-stacks-checkbox").disabled,
        "If the profiler stacks config is set via URL, its checkbox should be disabled even after clicking around."
      );
    });
  });
  await BrowserTestUtils.withNewTab(
    {
      gBrowser,
      url: PAGE + "?invalid-param",
    },
    async browser => {
      await SpecialPowers.spawn(browser, [profilerPresetInURL], async () => {
        let $ = content.document.querySelector.bind(content.document);
        Assert.ok(
          !$("#error").hidden,
          "When an invalid URL param is passed in, the page displays a warning."
        );
      });
    }
  );
  clearLoggingPrefs();
});

// Test various things related to presets: that it's populated correctly, that
// setting presets work in terms of UI, but also that it sets the logging.*
// prefs correctly.
add_task(async function testAboutLoggingPresets() {
  await BrowserTestUtils.withNewTab(PAGE, async browser => {
    await SpecialPowers.spawn(browser, [], async () => {
      let $ = content.document.querySelector.bind(content.document);
      let presetsDropdown = $("#logging-preset-dropdown");
      Assert.equal(
        Object.keys(content.presets()).length,
        presetsDropdown.childNodes.length,
        "Presets populated."
      );

      Assert.equal(presetsDropdown.value, "networking");
      $("#set-log-modules-button").click();
      Assert.ok(
        $("#no-log-modules").hidden && !$("#current-log-modules").hidden,
        "When log modules are set, they are visible."
      );
      var lengthModuleListNetworking = $("#log-modules").value.length;
      var lengthCurrentModuleListNetworking = $("#current-log-modules")
        .innerText.length;
      Assert.notEqual(
        lengthModuleListNetworking,
        0,
        "When setting a profiler preset, the module string is non-empty (input)."
      );
      Assert.notEqual(
        lengthCurrentModuleListNetworking,
        0,
        "When setting a profiler preset, the module string is non-empty (selected modules)."
      );

      // Change preset
      presetsDropdown.value = "media-playback";
      presetsDropdown.dispatchEvent(new content.Event("change"));

      // Check the following after "onchange".
      // eslint-disable-next-line mozilla/no-arbitrary-setTimeout
      await new Promise(resolve => content.setTimeout(resolve, 0));

      Assert.equal(
        presetsDropdown.value,
        "media-playback",
        "Selecting another preset is reflected in the page"
      );
      $("#set-log-modules-button").click();
      Assert.ok(
        $("#no-log-modules").hidden && !$("#current-log-modules").hidden,
        "When other log modules are set, they are still visible"
      );
      Assert.notEqual(
        $("#log-modules").value.length,
        0,
        "When setting a profiler preset, the module string is non-empty (input)."
      );
      Assert.notEqual(
        $("#current-log-modules").innerText.length,
        0,
        "When setting a profiler preset, the module string is non-empty (selected modules)."
      );
      Assert.notEqual(
        $("#log-modules").value.length,
        lengthModuleListNetworking,
        "When setting another profiler preset, the module string changes (input)."
      );
      let currentLogModulesString = $("#current-log-modules").innerText;
      Assert.notEqual(
        currentLogModulesString.length,
        lengthCurrentModuleListNetworking,

        "When setting another profiler preset, the module string changes (selected modules)."
      );

      // After setting some log modules via the preset dropdown, verify
      // that they have been reflected to logging.* preferences.
      var activeLogModules = [];
      let children = Services.prefs.getBranch("logging.").getChildList("");
      for (let pref of children) {
        if (pref.startsWith("config.")) {
          continue;
        }

        try {
          let value = Services.prefs.getIntPref(`logging.${pref}`);
          activeLogModules.push(`${pref}:${value}`);
        } catch (e) {
          console.error(e);
        }
      }
      let mod;
      while ((mod = activeLogModules.pop())) {
        Assert.ok(
          currentLogModulesString.includes(mod),
          `${mod} was effectively set`
        );
      }
    });
  });
  clearLoggingPrefs();
});

// Test various things around the profiler stacks feature
add_task(async function testProfilerStacks() {
  // Check the initial state before changing anything.
  Assert.ok(
    !Services.prefs.getBoolPref("logging.config.profilerstacks"false),
    "The preference for profiler stacks isn't set initially"
  );
  await BrowserTestUtils.withNewTab(PAGE, async browser => {
    await SpecialPowers.spawn(browser, [], async () => {
      let $ = content.document.querySelector.bind(content.document);
      const checkbox = $("#with-profiler-stacks-checkbox");
      Assert.ok(
        !checkbox.checked,
        "The profiler stacks checkbox isn't checked at load time."
      );
      checkbox.checked = true;
      checkbox.dispatchEvent(new content.Event("change"));
      Assert.ok(
        Services.prefs.getBoolPref("logging.config.profilerstacks"),
        "The preference for profiler stacks is now set to true"
      );
      checkbox.checked = false;
      checkbox.dispatchEvent(new content.Event("change"));
      Assert.ok(
        !Services.prefs.getBoolPref("logging.config.profilerstacks"),
        "The preference for profiler stacks is now back to false"
      );

      $("#radio-logging-file").click();
      Assert.ok(
        checkbox.disabled,
        "The profiler stacks checkbox is disabled when the output type is 'file'"
      );
      $("#radio-logging-profiler").click();
      Assert.ok(
        !checkbox.disabled,
        "The profiler stacks checkbox is enabled when the output type is 'profiler'"
      );
    });
  });
  clearLoggingPrefs();
});

// Here we test that starting and stopping log collection to the Firefox
// Profiler opens a new tab. We don't actually check the content of the profile.
add_task(async function testProfilerOpens() {
  await BrowserTestUtils.withNewTab(PAGE, async browser => {
    let profilerOpenedPromise = BrowserTestUtils.waitForNewTab(
      gBrowser,
      "https://example.com/",
      false
    );
    SpecialPowers.spawn(browser, [], async () => {
      let $ = content.document.querySelector.bind(content.document);
      // Override the URL the profiler uses to avoid hitting external
      // resources (and crash).
      await SpecialPowers.pushPrefEnv({
        set: [
          ["devtools.performance.recording.ui-base-url""https://example.com"],
          ["devtools.performance.recording.ui-base-url-path""/"],
        ],
      });
      $("#radio-logging-file").click();
      $("#radio-logging-profiler").click();
      $("#logging-preset-dropdown").value = "networking";
      $("#logging-preset-dropdown").dispatchEvent(new content.Event("change"));
      $("#set-log-modules-button").click();
      $("#toggle-logging-button").click();
      // Wait for the profiler to start. This can be very slow.
      await content.profilerPromise();

      // Wait for some time for good measure while the profiler collects some
      // data. We don't really care about the data itself.
      // eslint-disable-next-line mozilla/no-arbitrary-setTimeout
      await new Promise(resolve => content.setTimeout(resolve, 1000));
      $("#toggle-logging-button").click();
    });
    let tab = await profilerOpenedPromise;
    Assert.ok(true"Profiler tab opened after profiling");
    await BrowserTestUtils.removeTab(tab);
  });
  clearLoggingPrefs();
});

// Same test, outputing to a file, with network logging, while opening and
// closing a tab. We only check that the file exists and has a non-zero size.
add_task(async function testLogFileFound() {
  await BrowserTestUtils.withNewTab(PAGE, async browser => {
    await SpecialPowers.spawn(browser, [], async () => {
      // Clear any previous log file.
      let $ = content.document.querySelector.bind(content.document);
      $("#radio-logging-file").click();
      $("#log-file").value = "";
      $("#log-file").dispatchEvent(new content.Event("change"));
      $("#set-log-file-button").click();

      Assert.ok(
        !$("#no-log-file").hidden,
        "When a log file hasn't been set, it's indicated as such."
      );
    });
  });
  await BrowserTestUtils.withNewTab(PAGE, async browser => {
    let logPath = await SpecialPowers.spawn(browser, [], async () => {
      let $ = content.document.querySelector.bind(content.document);
      $("#radio-logging-file").click();
      // Set the log file (use the default path)
      $("#set-log-file-button").click();
      var logPath = $("#current-log-file").innerText;
      // Set log modules for networking
      $("#logging-preset-dropdown").value = "networking";
      $("#logging-preset-dropdown").dispatchEvent(new content.Event("change"));
      $("#set-log-modules-button").click();
      return logPath;
    });

    // No need to start or stop logging when logging to a file. Just open
    // a tab, any URL will do. Wait for this tab to be loaded so we're sure
    // something (anything) has happened in necko.
    let tab = await BrowserTestUtils.openNewForegroundTab(
      gBrowser,
      "https://example.com",
      true /* waitForLoad */
    );
    await BrowserTestUtils.removeTab(tab);
    let logDirectory = PathUtils.parent(logPath);
    let logBasename = PathUtils.filename(logPath);
    let entries = await IOUtils.getChildren(logDirectory);
    let foundNonEmptyLogFile = false;
    for (let entry of entries) {
      if (entry.includes(logBasename)) {
        info("-- Log file found: " + entry);
        let fileinfo = await IOUtils.stat(entry);
        foundNonEmptyLogFile |= fileinfo.size > 0;
      }
    }
    Assert.ok(foundNonEmptyLogFile, "Found at least one non-empty log file.");
  });
  clearLoggingPrefs();
});

// Roughly test the Android-specific UI
add_task(async function testAndroidUI() {
  await SpecialPowers.pushPrefEnv({
    set: [
      ["toolkit.aboutLogging.uploadProfileToCloud"true],
      [
        "toolkit.aboutlogging.uploadProfileUrl",
        "https://api.profiler.firefox.com/browser/toolkit/content/tests/browser/browser_about_logging_server.sjs",
      ],
      // The value "2" tells Downloads.getPreferredDownloadsDirectory to use the
      // pref "browser.download.dir" below.
      ["browser.download.folderList"2],
      // We use a path in the temp directory for the test.
      ["browser.download.dir", Services.dirsvc.get("TmpD", Ci.nsIFile).path],
    ],
  });
  await BrowserTestUtils.withNewTab(PAGE, async browser => {
    const document = browser.contentDocument;
    const window = browser.contentWindow;

    info("Make sure the profiler option is selected.");
    EventUtils.synthesizeMouseAtCenter(
      await getElementFromDocumentByText(
        document,
        "Logging to the Firefox Profiler"
      ),
      {},
      window
    );

    info("Start logging");
    const loggingButton = await getElementFromDocumentByText(
      document,
      "Start Logging"
    );
    EventUtils.synthesizeMouseAtCenter(loggingButton, {}, window);

    // Wait for the profiler to start. This can be very slow.
    await content.profilerPromise();

    info(
      "The profiler is started. Let's wait 1 second so that it can capture some data."
    );

    // Wait for some time for good measure while the profiler collects some
    // data. We don't really care about the data itself.
    // eslint-disable-next-line mozilla/no-arbitrary-setTimeout
    await new Promise(resolve => content.setTimeout(resolve, 1000));

    info("Stop logging");
    EventUtils.synthesizeMouseAtCenter(loggingButton, {}, window);

    ok(
      await getElementFromDocumentByText(
        document,
        "The profile data has been captured."
      ),
      "The information about the profile data capture is displayed."
    );

    info("Click the save button");
    const saveButton = await getElementFromDocumentByText(document, "Save");
    EventUtils.synthesizeMouseAtCenter(saveButton, {}, window);

    const savedText = await getElementFromDocumentByText(document, "Saved to");
    ok(savedText, "The text path is being displayed");

    // Extract the file path from the l10n arguments
    const savedPath = JSON.parse(savedText.getAttribute("data-l10n-args")).path;
    info(`Profile saved to: ${savedPath}`);

    Assert.ok(
      savedPath && !!savedPath.length,
      "Saved path should not be empty"
    );

    const fileinfo = await IOUtils.stat(savedPath);
    Assert.greater(
      fileinfo.size,
      0,
      `The profile has been saved to ${savedPath} and has a positive size.`
    );
    info("Cleaning up the saved file.");
    await IOUtils.remove(savedPath);

    await info("Click the upload button");
    const uploadButton = await getElementFromDocumentByText(document, "Upload");
    EventUtils.synthesizeMouseAtCenter(uploadButton, {}, window);
    ok(
      await getElementFromDocumentByText(document, "Uploading"),
      "Some text is displayed while uploading."
    );
    const uploadedText = await getElementFromDocumentByText(
      document,
      "Uploaded to"
    );
    const uploadedUrl = uploadedText.querySelector("a").href;
    is(
      uploadedUrl,
      "https://profiler.firefox.com/public/24j1wmckznh8sj22zg1tsmg47dyfdtprj0g41s8",
      "The profiler URL is displayed."
    );

    // Test the error case
    info("Test the error case, uploading to a 404");
    await SpecialPowers.pushPrefEnv({
      set: [
        [
          "toolkit.aboutlogging.uploadProfileUrl",
          "https://api.profiler.firefox.com/NONEXISTENT",
        ],
      ],
    });
    EventUtils.synthesizeMouseAtCenter(uploadButton, {}, window);
    const errorText = await getElementFromDocumentByText(
      document,
      "An error happened while uploading the profile"
    );
    is(
      errorText.textContent,
      "An error happened while uploading the profile: Error: xhr onload with status != 200, xhr.statusText: Not Found",
      "The error is output to the user."
    );

    await clearUploadedProfilesDB(content);
  });
});

add_task(async function testCopyToClipboard() {
  await BrowserTestUtils.withNewTab(
    PAGE,
    async browser => {
      const document = browser.contentDocument;
      const window = browser.contentWindow;
      // Open the menu, click on the item to copy to the clipboard
      var menuButton = document.querySelector("#open-menu-button");
      EventUtils.synthesizeMouseAtCenter(menuButton, {}, window);
      var copyAction = await getElementFromDocumentByText(
        document,
        "Copy current settings as URL"
      );
      EventUtils.synthesizeMouseAtCenter(copyAction, {}, window);
      // In theory, we could wait for the toast, and check that the clipboard
      // has been filled with reasonnable data. In practice the CI machines
      // are too slow and miss the toast, so we're repeatedly checking the
      // content of the clipboard instead.
      var copiedString = await TestUtils.waitForCondition(() => {
        const xferable = Cc[
          "@mozilla.org/widget/transferable;1"
        ].createInstance(Ci.nsITransferable);
        xferable.init(null);
        xferable.addDataFlavor("text/plain");
        Services.clipboard.getData(xferable, Ci.nsIClipboard.kGlobalClipboard);
        let data = {};
        let type = {};
        try {
          xferable.getAnyTransferData(type, data);
          data = data.value.QueryInterface(Ci.nsISupportsString).data;
        } catch {
          data = "";
        }
        if (data.startsWith("about:logging")) {
          return data;
        }
        return false;
      });
      Assert.stringMatches(
        copiedString,
        /^about:logging\?/,
        `about:logging URL copied successfully ${copiedString}`
      );
    },
    "Waiting to have clipboard data"
  );
});

// Test the uploaded profiles functionality.
add_task(async function testUploadedProfilesFeatures() {
  await SpecialPowers.pushPrefEnv({
    set: [
      ["toolkit.aboutLogging.uploadProfileToCloud"true],
      [
        "toolkit.aboutlogging.uploadProfileUrl",
        "https://api.profiler.firefox.com/browser/toolkit/content/tests/browser/browser_about_logging_server.sjs",
      ],
      [
        "toolkit.aboutlogging.deleteProfileUrl",
        "https://api.profiler.firefox.com/browser/toolkit/content/tests/browser/browser_about_logging_server.sjs?token",
      ],
    ],
  });

  await BrowserTestUtils.withNewTab(PAGE, async browser => {
    await clearUploadedProfilesDB(content);
    const document = browser.contentDocument;
    const window = browser.contentWindow;

    // Test UI elements presence and initial state
    await SpecialPowers.spawn(browser, [], async () => {
      const $ = content.document.querySelector.bind(content.document);

      await ContentTaskUtils.waitForCondition(
        () => $("#uploaded-profiles-section"),
        "Uploaded profiles section should be present"
      );

      const section = $("#uploaded-profiles-section");
      Assert.ok(section, "Uploaded profiles section should exist");

      // Check that the section has the correct title
      const title = section.querySelector(
        "h2[data-l10n-id='about-logging-uploaded-profiles-title']"
      );
      Assert.ok(title, "Uploaded profiles title should be present");

      // Check that the "no profiles" message is shown initially
      const noProfilesMessage = $("#no-uploaded-profiles");
      Assert.ok(noProfilesMessage, "No profiles message should exist");
      Assert.ok(
        !noProfilesMessage.hidden,
        "No profiles message should be visible initially"
      );

      // Check that the profiles list container exists
      const profilesList = $("#uploaded-profiles-list");
      Assert.ok(profilesList, "Profiles list container should exist");
      Assert.equal(
        profilesList.children.length,
        0,
        "Profiles list should be empty initially"
      );
    });

    // Create first profile by actually using the UI
    info("Make sure the profiler option is selected.");
    EventUtils.synthesizeMouseAtCenter(
      await getElementFromDocumentByText(
        document,
        "Logging to the Firefox Profiler"
      ),
      {},
      window
    );

    info("Start logging for first profile");
    const loggingButton = await getElementFromDocumentByText(
      document,
      "Start Logging"
    );
    EventUtils.synthesizeMouseAtCenter(loggingButton, {}, window);

    // Wait for the profiler to start. This can be very slow.
    await content.profilerPromise();

    info("Wait for first profile to collect some data");
    // eslint-disable-next-line mozilla/no-arbitrary-setTimeout
    await new Promise(resolve => content.setTimeout(resolve, 100));

    info("Stop logging for first profile");
    EventUtils.synthesizeMouseAtCenter(loggingButton, {}, window);

    ok(
      await getElementFromDocumentByText(
        document,
        "The profile data has been captured."
      ),
      "The information about the profile data capture is displayed."
    );

    info("Upload first profile");
    const uploadButton = await getElementFromDocumentByText(document, "Upload");
    EventUtils.synthesizeMouseAtCenter(uploadButton, {}, window);

    ok(
      await getElementFromDocumentByText(document, "Uploading"),
      "Some text is displayed while uploading."
    );

    const uploadedText = await getElementFromDocumentByText(
      document,
      "Uploaded to"
    );
    const uploadedUrl = uploadedText.querySelector("a").href;
    is(
      uploadedUrl,
      "https://profiler.firefox.com/public/24j1wmckznh8sj22zg1tsmg47dyfdtprj0g41s8",
      "The profiler URL is displayed for first profile."
    );

    // Wait for the profile to be saved to IndexedDB
    // Now that we tested the real user path by capturing a profile, let's add
    // another profile to the DB quickly for testing purposes.
    const { getAllUploadedProfiles, saveUploadedProfile } =
      ChromeUtils.importESModule(
        "chrome://global/content/aboutLogging/profileStorage.mjs"
      );
    await TestUtils.waitForCondition(async () => {
      const profiles = await getAllUploadedProfiles();
      return profiles.length >= 1;
    }, "Uploaded profile should be saved to IndexedDB");

    const testProfile = {
      jwtToken: "test.jwt.token",
      profileToken: "test-hash-123",
      profileUrl: "https://profiler.firefox.com/public/test-hash-123",
      uploadDate: new Date(),
      profileName: "Test Profile",
    };

    const profileId = await saveUploadedProfile(testProfile);
    Assert.strictEqual(
      typeof profileId,
      "number",
      "Profile ID should be a number"
    );

    // Test that profiles are displayed and sorted correctly
    const { initialProfileCount, firstProfileName } = await SpecialPowers.spawn(
      browser,
      [],
      async () => {
        const $ = content.document.querySelector.bind(content.document);

        // Wait for the uploaded profiles manager to be initialized
        await ContentTaskUtils.waitForCondition(
          () => content.gUploadedProfilesManager,
          "UploadedProfilesManager should be initialized"
        );

        // Manually refresh to ensure we have the latest data
        await content.gUploadedProfilesManager.refresh();

        // Wait for profiles to appear in UI with a more robust check
        await ContentTaskUtils.waitForCondition(() => {
          const profileItems = $("#uploaded-profiles-list").children;
          const noProfilesMessage = $("#no-uploaded-profiles");
          return profileItems.length >= 2 && noProfilesMessage.hidden;
        }, "Both profiles should appear in the UI and no-profiles message should be hidden");

        const profilesList = $("#uploaded-profiles-list");
        const noProfilesMessage = $("#no-uploaded-profiles");

        // Check that profiles are displayed
        Assert.ok(
          noProfilesMessage.hidden,
          "No profiles message should be hidden"
        );

        Assert.greaterOrEqual(
          profilesList.children.length,
          2,
          "Should have at least two profile items"
        );

        // Verify profiles are sorted by upload date (most recent first)
        const profileItems = Array.from(profilesList.children);

        // Get upload dates from the profile items
        const uploadDates = profileItems.map(item => {
          const dateElement = item.querySelector(".uploaded-profile-date");
          return dateElement ? new Date(dateElement.textContent) : new Date(0);
        });

        // Check that dates are in descending order (most recent first)
        for (let i = 0; i < uploadDates.length - 1; i++) {
          Assert.greaterOrEqual(
            uploadDates[i],
            uploadDates[i + 1],
            `Profile at index ${i} should have a more recent or equal upload date than profile at index ${i + 1}`
          );
        }

        // Test the delete button exists and has correct attributes
        const deleteButton = profileItems[0].querySelector(
          ".delete-profile-button"
        );
        Assert.ok(deleteButton, "Delete button should exist");
        Assert.equal(
          deleteButton.dataset.l10nId,
          "about-logging-delete-uploaded-profile",
          "Delete button should have correct l10n ID"
        );

        // Prepare for deletion test - get initial state and profile info
        const initialProfileCount = profileItems.length;
        const firstProfileId = parseInt(profileItems[0].dataset.profileId, 10);
        const firstProfileName = profileItems[0].querySelector(
          ".uploaded-profile-name"
        ).textContent;

        info(
          `Preparing to delete profile ID ${firstProfileId}: ${firstProfileName}`
        );

        return { initialProfileCount, firstProfileName };
      }
    );

    // Test actual deletion by clicking the delete button and handling the confirmation prompt
    info("Testing profile deletion with confirmation prompt");

    // Set up prompt handling for the confirmation dialog
    // Services.prompt.confirm from content creates a window modal prompt
    const promptPromise = PromptTestUtils.handleNextPrompt(
      browser,
      { modalType: Services.prompt.MODAL_TYPE_WINDOW },
      { buttonNumClick: 0 } // 0 = OK/Yes, 1 = Cancel/No
    );

    // Click the delete button to trigger the confirmation prompt
    await SpecialPowers.spawn(browser, [], async () => {
      const $ = content.document.querySelector.bind(content.document);
      const profileItems = Array.from($("#uploaded-profiles-list").children);
      const deleteButton = profileItems[0].querySelector(
        ".delete-profile-button"
      );
      deleteButton.click();
    });

    // Wait for the prompt to be handled
    await promptPromise;

    // Verify the profile was deleted
    await SpecialPowers.spawn(
      browser,
      [initialProfileCount, firstProfileName],
      async (initialProfileCount, firstProfileName) => {
        const $ = content.document.querySelector.bind(content.document);

        // Manually refresh the UI after deletion
        await content.gUploadedProfilesManager.refresh();

        // Wait for the profile to be deleted from the UI
        await ContentTaskUtils.waitForCondition(() => {
          const currentProfileItems = $("#uploaded-profiles-list").children;
          return currentProfileItems.length === initialProfileCount - 1;
        }, "Profile should be deleted from the UI");

        // Verify the profile was removed
        const remainingProfileItems = Array.from(
          $("#uploaded-profiles-list").children
        );
        Assert.equal(
          remainingProfileItems.length,
          initialProfileCount - 1,
          "Profile count should decrease by 1 after deletion"
        );

        // Verify the specific profile was deleted (check that the deleted profile name is no longer present)
        const remainingProfileNames = remainingProfileItems.map(
          item => item.querySelector(".uploaded-profile-name").textContent
        );
        Assert.ok(
          !remainingProfileNames.includes(firstProfileName),
          `Deleted profile "${firstProfileName}" should no longer be in the list`
        );
      }
    );

    await clearUploadedProfilesDB(content);
  });
});

Messung V0.5 in Prozent
C=88 H=92 G=89

¤ Dauer der Verarbeitung: 0.12 Sekunden  ¤

*© Formatika GbR, Deutschland






Wurzel

Suchen

PVS Prover

Isabelle Prover

NIST Cobol Testsuite

Cephes Mathematical Library

Vienna Development Method

Haftungshinweis

Die Informationen auf dieser Webseite wurden nach bestem Wissen sorgfältig zusammengestellt. Es wird jedoch weder Vollständigkeit, noch Richtigkeit, noch Qualität der bereit gestellten Informationen zugesichert.

Bemerkung:

Die farbliche Syntaxdarstellung und die Messung sind noch experimentell.