Commit d11015fe authored by Gregory Mierzwinski's avatar Gregory Mierzwinski
Browse files

Bug 1713815 - Add support for interactive browsertime tests....

Bug 1713815 - Add support for interactive browsertime tests. r=jmaher,perftest-reviewers,AlexandruIonescu

This patch adds support for interactive browsertime tests (initially used for responsiveness tests).

Differential Revision: https://phabricator.services.mozilla.com/D126695
parent b1d459ea
Loading
Loading
Loading
Loading
+4 −1
Original line number Diff line number Diff line
@@ -320,9 +320,12 @@ def main(log, args):
        for site in browsertime_json:
            for video in site["files"]["video"]:
                count += 1
                name = job["test_name"]
                if "alias" in site["info"] and site["info"]["alias"].strip() != "":
                    name = "%s.%s" % (name, site["info"]["alias"])
                jobs.append(
                    Job(
                        test_name=job["test_name"],
                        test_name=name,
                        extra_options=len(job["extra_options"]) > 0
                        and job["extra_options"]
                        or jobs_json["extra_options"],
+7 −0
Original line number Diff line number Diff line
@@ -7,6 +7,8 @@ import json
import platform
from pathlib import Path

from manifestparser.util import evaluate_list_from_string

from mozperftest.test.browsertime import add_options, add_option

options = [
@@ -80,4 +82,9 @@ def before_runs(env):
        add_option(env, "browsertime.screenshot", "true")
        add_option(env, "browsertime.testName", test_site.get("name"))

        # pack array into string for transport to javascript, is there a better way?
        cmds = evaluate_list_from_string(test_site.get("test_cmds", "[]"))
        parsed_cmds = [":::".join([str(i) for i in item]) for item in cmds if item]
        add_option(env, "browsertime.commands", ";;;".join(parsed_cmds))

        print("Recording %s to file: %s" % (test_site.get("url"), recording_file))
+78 −1
Original line number Diff line number Diff line
@@ -4,7 +4,7 @@
/* eslint-env node */
"use strict";

async function test(context, commands) {
async function pageload_test(context, commands) {
  let testUrl = context.options.browsertime.url;
  let secondaryUrl = context.options.browsertime.secondary_url;
  let testName = context.options.browsertime.testName;
@@ -25,6 +25,83 @@ async function test(context, commands) {

  // Wait for browser to settle
  await commands.wait.byTime(1000);
}

async function get_command_function(cmd, commands) {
  /*
  Converts a string such as `measure.start` into the actual
  function that is found in the `commands` module.

  XXX: Find a way to share this function between
  perftest_record.js and browsertime_interactive.js
  */
  if (cmd == "") {
    throw new Error("A blank command was given.");
  } else if (cmd.endsWith(".")) {
    throw new Error(
      "An extra `.` was found at the end of this command: " + cmd
    );
  }

  // `func` will hold the actual method that needs to be called,
  // and the `parent_mod` is the context required to run the `func`
  // method. Without that context, `this` becomes undefined in the browsertime
  // classes.
  let func = null;
  let parent_mod = null;
  for (let func_part of cmd.split(".")) {
    if (func_part == "") {
      throw new Error(
        "An empty function part was found in the command: " + cmd
      );
    }

    if (func === null) {
      parent_mod = commands;
      func = commands[func_part];
    } else if (func !== undefined) {
      parent_mod = func;
      func = func[func_part];
    } else {
      break;
    }
  }

  if (func == undefined) {
    throw new Error(
      "The given command could not be found as a function: " + cmd
    );
  }

  return [func, parent_mod];
}

async function interactive_test(input_cmds, context, commands) {
  let cmds = input_cmds.split(";;;");

  await commands.navigate("about:blank");

  for (let cmdstr of cmds) {
    let [cmd, ...args] = cmdstr.split(":::");
    let [func, parent_mod] = await get_command_function(cmd, commands);

    try {
      await func.call(parent_mod, ...args);
    } catch (e) {
      context.log.info(
        `Exception found while running \`commands.${cmd}(${args})\`: ` + e
      );
    }
  }
}

async function test(context, commands) {
  let input_cmds = context.options.browsertime.commands;
  if (input_cmds !== undefined) {
    await interactive_test(input_cmds, context, commands);
  } else {
    await pageload_test(context, commands);
  }
  return true;
}

+104 −0
Original line number Diff line number Diff line
/* 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/. */

/* eslint-env node */

async function get_command_function(cmd, commands) {
  /*
  Converts a string such as `measure.start` into the actual
  function that is found in the `commands` module.

  XXX: Find a way to share this function between
  perftest_record.js and browsertime_interactive.js
  */
  if (cmd == "") {
    throw new Error("A blank command was given.");
  } else if (cmd.endsWith(".")) {
    throw new Error(
      "An extra `.` was found at the end of this command: " + cmd
    );
  }

  // `func` will hold the actual method that needs to be called,
  // and the `parent_mod` is the context required to run the `func`
  // method. Without that context, `this` becomes undefined in the browsertime
  // classes.
  let func = null;
  let parent_mod = null;
  for (let func_part of cmd.split(".")) {
    if (func_part == "") {
      throw new Error(
        "An empty function part was found in the command: " + cmd
      );
    }

    if (func === null) {
      parent_mod = commands;
      func = commands[func_part];
    } else if (func !== undefined) {
      parent_mod = func;
      func = func[func_part];
    } else {
      break;
    }
  }

  if (func == undefined) {
    throw new Error(
      "The given command could not be found as a function: " + cmd
    );
  }

  return [func, parent_mod];
}

module.exports = async function(context, commands) {
  context.log.info("Starting an interactive browsertime test");
  let page_cycles = context.options.browsertime.page_cycles;
  let post_startup_delay = context.options.browsertime.post_startup_delay;
  let input_cmds = context.options.browsertime.commands;

  context.log.info(
    "Waiting for %d ms (post_startup_delay)",
    post_startup_delay
  );
  await commands.wait.byTime(post_startup_delay);

  // unpack commands from python
  let cmds = input_cmds.split(";;;");

  // let pages_visited = 0;
  for (let count = 0; count < page_cycles; count++) {
    context.log.info("Navigating to about:blank w/nav, count: " + count);
    await commands.navigate("about:blank");

    let pages_visited = [];
    for (let cmdstr of cmds) {
      let [cmd, ...args] = cmdstr.split(":::");

      if (cmd == "measure.start") {
        if (args[0] != "") {
          pages_visited.push(args[0]);
        }
      }

      let [func, parent_mod] = await get_command_function(cmd, commands);

      try {
        await func.call(parent_mod, ...args);
      } catch (e) {
        context.log.info(
          `Exception found while running \`commands.${cmd}(${args})\`: `
        );
        context.log.info(e.stack);
      }
    }

    // Log the number of pages visited for results parsing
    context.log.info("[] metrics: pages_visited: " + pages_visited);
  }

  context.log.info("Browsertime pageload ended.");
  return true;
};
+2 −1
Original line number Diff line number Diff line
@@ -23,7 +23,7 @@ module.exports = async function(context, commands) {
      context.log.info("Navigating to secondary url:" + secondary_url);
      await commands.navigate(secondary_url);
    } else {
      context.log.info("Navigating to about:blank");
      context.log.info("Navigating to about:blank, count: " + count);
      await commands.navigate("about:blank");
    }

@@ -34,6 +34,7 @@ module.exports = async function(context, commands) {
    context.log.info("Cycle %d, starting the measure", count);
    await commands.measure.start(test_url);
  }

  context.log.info("Browsertime pageload ended.");
  return true;
};
Loading