<!DOCTYPE HTML>
<html lang="en">
<head>
  <meta charset="utf8">
  <title>Test for JavaScript terminal functionality</title>
  <script src="chrome://mochikit/content/tests/SimpleTest/SimpleTest.js"></script>
  <script type="text/javascript" src="common.js"></script>
  <!-- Any copyright is dedicated to the Public Domain.
     - http://creativecommons.org/publicdomain/zero/1.0/ -->
</head>
<body>
<p>Test for JavaScript terminal autocomplete functionality</p>

<script class="testbody" type="text/javascript">
  "use strict";

  SimpleTest.waitForExplicitFinish();
  const {
    MAX_AUTOCOMPLETE_ATTEMPTS,
    MAX_AUTOCOMPLETIONS
  } = require("devtools/shared/webconsole/js-property-provider");
  const RESERVED_JS_KEYWORDS = require("devtools/shared/webconsole/reserved-js-words");


  addEventListener("load", startTest);

  async function startTest() {
    // First run the tests with a tab as a target.
    let {state} = await attachConsoleToTab(["PageError"]);
    await performTests({state, isWorker: false});

    // Then run the tests with a worker as a target.
    state = (await attachConsoleToWorker(["PageError"])).state;
    await performTests({state, isWorker: true});

    SimpleTest.finish();
  }

  async function performTests({state, isWorker}) {
    // Set up the global variables needed to test autocompletion in the target.
    const script = `
      // This is for workers so autocomplete acts the same
      if (!this.window) {
        window = this;
      }

      window.foobarObject = Object.create(null);
      window.foobarObject.foo = 1;
      window.foobarObject.foobar = 2;
      window.foobarObject.foobaz = 3;
      window.foobarObject.omg = 4;
      window.foobarObject.omgfoo = 5;
      window.foobarObject.strfoo = "foobarz";
      window.foobarObject.omgstr = "foobarz" +
        (new Array(${DevToolsServer.LONG_STRING_LENGTH})).join("abb");
      window.largeObject1 = Object.create(null);
      for (let i = 0; i < ${MAX_AUTOCOMPLETE_ATTEMPTS + 1}; i++) {
        window.largeObject1['a' + i] = i;
      }

      window.largeObject2 = Object.create(null);
      for (let i = 0; i < ${MAX_AUTOCOMPLETIONS * 2}; i++) {
        window.largeObject2['a' + i] = i;
      }

      window.proxy1 = new Proxy({foo: 1}, {
        getPrototypeOf() { throw new Error() }
      });
      window.proxy2 = new Proxy(Object.create(Object.create(null, {foo:{}})), {
        ownKeys() { throw new Error() }
      });
      window.emojiObject = Object.create(null);
      window.emojiObject["😎"] = "😎";

      window.insensitiveTestCase = Object.create(null, Object.getOwnPropertyDescriptors({
        PROP: "",
        Prop: "",
        prop: "",
        PRÖP: "",
        pröp: "",
      }));

      window.elementAccessTestCase = Object.create(null, Object.getOwnPropertyDescriptors({
        bar: "",
        BAR: "",
        dataTest: "",
        "data-test": "",
        'da"ta"test': "",
        'da\`ta\`test': "",
        "da'ta'test": "",
      }));

      window.varify = true;

      var Cu_Sandbox = Cu ? Cu.Sandbox : null;
    `;
    await evaluateExpression(state.webConsoleFront, script);

    const tests = [
      doAutocomplete1,
      doAutocomplete2,
      doAutocomplete3,
      doAutocomplete4,
      doAutocompleteLarge1,
      doAutocompleteLarge2,
      doAutocompleteProxyThrowsPrototype,
      doAutocompleteProxyThrowsOwnKeys,
      doAutocompleteDotSurroundedBySpaces,
      doAutocompleteAfterOr,
      doInsensitiveAutocomplete,
      doElementAccessAutocomplete,
      doAutocompleteAfterOperator,
      dontAutocompleteAfterDeclaration,
      doKeywordsAutocomplete,
      dontAutocomplete,
    ];

    if (!isWorker) {
      // `Cu` is not defined in workers, then we can't test `Cu.Sandbox`
      tests.push(doAutocompleteSandbox);
      // Some cases are handled in worker context because we can't use parser.js.
      // See Bug 1507181.
      tests.push(
        doAutocompleteArray,
        doAutocompleteString,
        doAutocompleteCommands,
        doAutocompleteBracketSurroundedBySpaces,
      );
    }

    for (const test of tests) {
      await test(state.webConsoleFront);
    }

    // Null out proxy1 and proxy2: the proxy handlers use scripted functions
    // that can keep the debugger sandbox alive longer than necessary via their
    // environment chain (due to the webconsole helper functions defined there).
    await evaluateExpression(state.webConsoleFront, `this.proxy1 = null; this.proxy2 = null;`);

    await closeDebugger(state);
  }

  async function doAutocomplete1(webConsoleFront) {
    info("test autocomplete for 'window.foo'");
    const response = await webConsoleFront.autocomplete("window.foo");
    const matches = response.matches;

    is(response.matchProp, "foo", "matchProp");
    is(matches.length, 1, "matches.length");
    is(matches[0], "foobarObject", "matches[0]");
  }

  async function doAutocomplete2(webConsoleFront) {
    info("test autocomplete for 'window.foobarObject.'");
    const response = await webConsoleFront.autocomplete("window.foobarObject.");
    const matches = response.matches;

    ok(!response.matchProp, "matchProp");
    is(matches.length, 7, "matches.length");
    checkObject(matches,
      ["foo", "foobar", "foobaz", "omg", "omgfoo", "omgstr", "strfoo"]);
  }

  async function doAutocomplete3(webConsoleFront) {
    // Check that completion suggestions are offered inside the string.
    info("test autocomplete for 'dump(window.foobarObject.)'");
    const response = await webConsoleFront.autocomplete("dump(window.foobarObject.)", 25);
    const matches = response.matches;

    ok(!response.matchProp, "matchProp");
    is(matches.length, 7, "matches.length");
    checkObject(matches,
      ["foo", "foobar", "foobaz", "omg", "omgfoo", "omgstr", "strfoo"]);
  }

  async function doAutocomplete4(webConsoleFront) {
    // Check that completion requests can have no suggestions.
    info("test autocomplete for 'dump(window.foobarObject.)'");
    const response = await webConsoleFront.autocomplete("dump(window.foobarObject.)");
    ok(!response.matchProp, "matchProp");
    is(response.matches, null, "matches is null");
  }

  async function doAutocompleteLarge1(webConsoleFront) {
    // Check that completion requests with too large objects will
    // have no suggestions.
    info("test autocomplete for 'window.largeObject1.'");
    const response = await webConsoleFront.autocomplete("window.largeObject1.");
    ok(!response.matchProp, "matchProp");
    info (response.matches.join("|"));
    is(response.matches.length, 0, "Bailed out with too many properties");
  }

  async function doAutocompleteLarge2(webConsoleFront) {
    // Check that completion requests with pretty large objects will
    // have MAX_AUTOCOMPLETIONS suggestions
    info("test autocomplete for 'window.largeObject2.'");
    const response = await webConsoleFront.autocomplete("window.largeObject2.");
    ok(!response.matchProp, "matchProp");
    is(response.matches.length, MAX_AUTOCOMPLETIONS, "matches.length is MAX_AUTOCOMPLETIONS");
  }

  async function doAutocompleteProxyThrowsPrototype(webConsoleFront) {
    // Check that completion provides own properties even if [[GetPrototypeOf]] throws.
    info("test autocomplete for 'window.proxy1.'");
    const response = await webConsoleFront.autocomplete("window.proxy1.");
    ok(!response.matchProp, "matchProp");
    is(response.matches.length, 14, "matches.length");
    ok(response.matches.includes("foo"), "matches has own property for proxy with throwing getPrototypeOf trap");
  }

  async function doAutocompleteProxyThrowsOwnKeys(webConsoleFront) {
    // Check that completion provides inherited properties even if [[OwnPropertyKeys]] throws.
    info("test autocomplete for 'window.proxy2.'");
    const response = await webConsoleFront.autocomplete("window.proxy2.");
    ok(!response.matchProp, "matchProp");
    is(response.matches.length, 1, "matches.length");
    checkObject(response.matches, ["foo"]);
  }

  async function doAutocompleteSandbox(webConsoleFront) {
    // Check that completion provides inherited properties even if [[OwnPropertyKeys]] throws.
    info("test autocomplete for 'Cu_Sandbox.'");
    const response = await webConsoleFront.autocomplete("Cu_Sandbox.");
    ok(!response.matchProp, "matchProp");
    const keys = Object.getOwnPropertyNames(Object.prototype).sort();
    is(response.matches.length, keys.length, "matches.length");
    // checkObject(response.matches, keys);
    is(response.matches.join(" - "), keys.join(" - "));
  }

  async function doAutocompleteArray(webConsoleFront) {
    info("test autocomplete for [1,2,3]");
    const response = await webConsoleFront.autocomplete("[1,2,3].");
    let {matches} = response;

    ok(!!matches.length, "There are completion results for the array");
    ok(matches.includes("length") && matches.includes("filter"),
      "Array autocomplete contains expected results");

    info("test autocomplete for '[] . '");
    matches = (await webConsoleFront.autocomplete("[] . ")).matches;
    ok(matches.length > 1);
    ok(matches.includes("length") && matches.includes("filter"),
      "Array autocomplete contains expected results");
    ok(!matches.includes("copy"), "Array autocomplete does not contain helpers");

    info("test autocomplete for '[1,2,3]['");
    matches = (await webConsoleFront.autocomplete("[1,2,3][")).matches;
    ok(matches.length > 1);
    ok(matches.includes('"length"') && matches.includes('"filter"'),
      "Array autocomplete contains expected results, surrounded by quotes");

    info("test autocomplete for '[1,2,3]['");
    matches = (await webConsoleFront.autocomplete("[1,2,3]['")).matches;
    ok(matches.length > 1);
    ok(matches.includes("'length'") && matches.includes("'filter'"),
      "Array autocomplete contains expected results, surrounded by quotes");

    info("test autocomplete for '[1,2,3][l");
    matches = (await webConsoleFront.autocomplete("[1,2,3][l")).matches;
    ok(matches.length >= 1);
    ok(matches.includes('"length"'),
      "Array autocomplete contains expected results, surrounded by quotes");

    info("test autocomplete for '[1,2,3]['l");
    matches = (await webConsoleFront.autocomplete("[1,2,3]['l")).matches;
    ok(matches.length >= 1);
    ok(matches.includes("'length'"),
      "Array autocomplete contains expected results, surrounded by quotes");
  }

  async function doAutocompleteString(webConsoleFront) {
    info(`test autocomplete for "foo".`);
    const response = await webConsoleFront.autocomplete(`"foo".`);
    let {matches} = response;

    ok(!!matches.length, "There are completion results for the string");
    ok(matches.includes("substr") && matches.includes("trim"),
      "String autocomplete contains expected results");

    info("test autocomplete for `foo`[");
    matches = (await webConsoleFront.autocomplete("`foo`[")).matches;
    ok(matches.length > 1, "autocomplete string with bracket works");
    ok(matches.includes('"substr"') && matches.includes('"trim"'),
      "String autocomplete contains expected results, surrounded by quotes");
  }

  async function doAutocompleteDotSurroundedBySpaces(webConsoleFront) {
    info("test autocomplete for 'window.foobarObject\n  .'");
    let {matches} = await webConsoleFront.autocomplete("window.foobarObject\n  .");
    is(matches.length, 7);
    checkObject(matches,
      ["foo", "foobar", "foobaz", "omg", "omgfoo", "omgstr", "strfoo"]);

    info("test autocomplete for 'window.foobarObject\n  .o'");
    matches = (await webConsoleFront.autocomplete("window.foobarObject\n  .o")).matches;
    is(matches.length, 3);
    checkObject(matches, ["omg", "omgfoo", "omgstr"]);

    info("test autocomplete for 'window.foobarObject\n  .\n  s'");
    matches = (await webConsoleFront.autocomplete("window.foobarObject\n  .\n  s")).matches;
    is(matches.length, 1);
    checkObject(matches, ["strfoo"]);

    info("test autocomplete for 'window.foobarObject\n  .  '");
    matches = (await webConsoleFront.autocomplete("window.foobarObject\n  .  ")).matches;
    is(matches.length, 7);
    checkObject(matches,
      ["foo", "foobar", "foobaz", "omg", "omgfoo", "omgstr", "strfoo"]);

    matches =
      (await webConsoleFront.autocomplete("window.foobarObject.  foo ; window.foo")).matches;
    is(matches.length, 1);
    checkObject(matches, ["foobarObject"]);
  }

  async function doAutocompleteBracketSurroundedBySpaces(webConsoleFront) {
    const wrap = (arr, quote = `"`) => arr.map(x => `${quote}${x}${quote}`);
    let matches = await getAutocompleteMatches(webConsoleFront, "window.foobarObject\n  [")
    is(matches.length, 7);
    checkObject(matches,
      wrap(["foo", "foobar", "foobaz", "omg", "omgfoo", "omgstr", "strfoo"]));

    matches = await getAutocompleteMatches(webConsoleFront, "window.foobarObject\n  ['o")
    is(matches.length, 3);
    checkObject(matches, wrap(["omg", "omgfoo", "omgstr"], "'"));

    matches = await getAutocompleteMatches(webConsoleFront, "window.foobarObject\n  [\n  s");
    is(matches.length, 1);
    checkObject(matches, [`"strfoo"`]);

    matches = await getAutocompleteMatches(webConsoleFront, "window.foobarObject\n  [  ");
    is(matches.length, 7);
    checkObject(matches,
      wrap(["foo", "foobar", "foobaz", "omg", "omgfoo", "omgstr", "strfoo"]));

    matches = await getAutocompleteMatches(webConsoleFront, "window.emojiObject  [   '");
    is(matches.length, 1);
    checkObject(matches, [`'😎'`]);
  }

  async function doAutocompleteAfterOr(webConsoleFront) {
    info("test autocomplete for 'true || foo'");
    const {matches} = await webConsoleFront.autocomplete("true || foobar");
    is(matches.length, 1, "autocomplete returns expected results");
    is(matches.join("-"), "foobarObject");
  }

  async function doInsensitiveAutocomplete(webConsoleFront) {
    info("test autocomplete for 'window.insensitiveTestCase.'");
    let {matches} = await webConsoleFront.autocomplete("window.insensitiveTestCase.");
    is(matches.join("-"), "prop-pröp-Prop-PROP-PRÖP",
      "autocomplete returns the expected items, in the expected order");

    info("test autocomplete for 'window.insensitiveTestCase.p'");
    matches = (await webConsoleFront.autocomplete("window.insensitiveTestCase.p")).matches;
    is(matches.join("-"), "prop-pröp-Prop-PROP-PRÖP",
      "autocomplete is case-insensitive when first letter is lowercased");

    info("test autocomplete for 'window.insensitiveTestCase.pRoP'");
    matches = (await webConsoleFront.autocomplete("window.insensitiveTestCase.pRoP")).matches;
    is(matches.join("-"), "prop-Prop-PROP",
      "autocomplete is case-insensitive when first letter is lowercased");

    info("test autocomplete for 'window.insensitiveTestCase.P'");
    matches = (await webConsoleFront.autocomplete("window.insensitiveTestCase.P")).matches;
    is(matches.join("-"), "Prop-PROP-PRÖP",
      "autocomplete is case-sensitive when first letter is uppercased");

    info("test autocomplete for 'window.insensitiveTestCase.PROP'");
    matches = (await webConsoleFront.autocomplete("window.insensitiveTestCase.PROP")).matches;
    is(matches.join("-"), "PROP",
      "autocomplete is case-sensitive when first letter is uppercased");

    info("test autocomplete for 'window.insensitiveTestCase.prö'");
    matches = (await webConsoleFront.autocomplete("window.insensitiveTestCase.prö")).matches;
    is(matches.join("-"), "pröp-PRÖP", "expected result with lowercase diacritic");

    info("test autocomplete for 'window.insensitiveTestCase.PRÖ'");
    matches = (await webConsoleFront.autocomplete("window.insensitiveTestCase.PRÖ")).matches;
    is(matches.join("-"), "PRÖP", "expected result with uppercase diacritic");
  }

  async function doElementAccessAutocomplete(webConsoleFront) {
    info("test autocomplete for 'window.elementAccessTestCase['");
    let res = (await webConsoleFront.autocomplete("window.elementAccessTestCase["));
    is(
      res.matches.join("|"),
      `"bar"|"da'ta'test"|"da\\"ta\\"test"|"da\`ta\`test"|"data-test"|"dataTest"|"BAR"`,
      "autocomplete returns the expected items, wrapped in quotes");
    is(res.isElementAccess, true);

    info("test autocomplete for 'window.elementAccessTestCase[d'");
    res = await webConsoleFront.autocomplete("window.elementAccessTestCase[d");
    is(
      res.matches.join("|"),
      `"da'ta'test"|"da\\"ta\\"test"|"da\`ta\`test"|"data-test"|"dataTest"`,
      "autocomplete returns the expected filtered items");
    is(res.isElementAccess, true);

    info(`test autocomplete for 'window.elementAccessTestCase["d'`);
    res = await webConsoleFront.autocomplete(`window.elementAccessTestCase["d`);
    is(
      res.matches.join("|"),
      `"da'ta'test"|"da\\"ta\\"test"|"da\`ta\`test"|"data-test"|"dataTest"`,
      "autocomplete returns the expected items, wrapped in quotes");
    is(res.isElementAccess, true);

    info(`test autocomplete for 'window.elementAccessTestCase["data-`);
    res = await webConsoleFront.autocomplete(`window.elementAccessTestCase["data-`);
    is(res.matches.join("|"), `"data-test"`,
      "autocomplete returns the expected items, wrapped in quotes");
    is(res.isElementAccess, true);

    info(`test autocomplete for 'window.elementAccessTestCase['d'`);
    res = await webConsoleFront.autocomplete(`window.elementAccessTestCase['d`);
    is(
      res.matches.join("|"),
      `'da"ta"test'|'da\\'ta\\'test'|'da\`ta\`test'|'data-test'|'dataTest'`,
      "autocomplete returns the expected items, wrapped in the same quotes the user entered");
    is(res.isElementAccess, true);

    info("test autocomplete for 'window.elementAccessTestCase[`d'");
    res = await webConsoleFront.autocomplete("window.elementAccessTestCase[`d");
    is(
      res.matches.join("|"),
      "`da'ta'test`|`da\"ta\"test`|`da\\`ta\\`test`|`data-test`|`dataTest`",
      "autocomplete returns the expected items, wrapped in the same quotes the user entered");
    is(res.isElementAccess, true);

    info(`test autocomplete for '['`);
    res = await webConsoleFront.autocomplete(`[`);
    is(res.matches, null, "it does not return anything");

    info(`test autocomplete for '[1,2,3'`);
    res = await webConsoleFront.autocomplete(`[1,2,3`);
    is(res.matches, null, "it does not return anything");

    info(`test autocomplete for '["'`);
    res = await webConsoleFront.autocomplete(`["`);
    is(res.matches, null, "it does not return anything");

    info(`test autocomplete for '[;'`);
    res = await webConsoleFront.autocomplete(`[;`);
    is(res.matches, null, "it does not return anything");
  }

  async function doAutocompleteCommands(webConsoleFront) {
    info("test autocomplete for 'c'");
    let matches = (await webConsoleFront.autocomplete("c")).matches;
    ok(matches.includes("clear"), "commands are returned");

    info("test autocomplete for 's'");
    matches = (await webConsoleFront.autocomplete("s")).matches;
    is(matches.includes("screenshot"), false, "screenshot is not returned");

    info("test autocomplete for ':s'");
    matches = (await webConsoleFront.autocomplete(":s")).matches;
    is(matches.includes(":screenshot"), true, "screenshot is returned");

    info("test autocomplete for 'window.c'");
    matches = (await webConsoleFront.autocomplete("window.c")).matches;
    ok(!matches.includes("clear"), "commands are not returned");

    info("test autocomplete for 'window[c'");
    matches = (await webConsoleFront.autocomplete("window[c")).matches;
    ok(!matches.includes("clear"), "commands are not returned");

    info(`test autocomplete for 'window["c'`);
    matches = (await webConsoleFront.autocomplete(`window["c`)).matches;
    ok(!matches.includes("clear"), "commands are not returned");

    info(`test autocomplete for 'window["c'`);
    matches = (await webConsoleFront.autocomplete(`window["c`)).matches;
    ok(!matches.includes("clear"), "commands are not returned");

    info(`test autocomplete for 'window[";c'`);
    matches = (await webConsoleFront.autocomplete(`window[";c`)).matches;
    ok(!matches.includes("clear"), "commands are not returned");

    info(`test autocomplete for 'window[;c'`);
    matches = (await webConsoleFront.autocomplete(`window[;c`)).matches;
    ok(!matches.includes("clear"), "commands are not returned");
  }

  async function doAutocompleteAfterOperator(webConsoleFront) {
    const inputs = [
      "true;foob",
      "true,foob",
      "({key:foob",
      "a=foob",
      "if(a<foob",
      "if(a>foob",
      "1+foob",
      "1-foob",
      "++foob",
      "--foob",
      "1*foob",
      "2**foob",
      "1/foob",
      "1%foob",
      "1|foob",
      "1&foob",
      "1^foob",
      "~foob",
      "1<<foob",
      "1>>foob",
      "1>>>foob",
      "false||foob",
      "false&&foob",
      "x=true?foob",
      "x=false?1:foob",
      "!foob",
      "false??foob",
    ];

    for (const input of inputs) {
      info(`test autocomplete for "${input}"`);
      const matches = (await webConsoleFront.autocomplete(input)).matches;
      ok(matches.includes("foobarObject"), `Expected autocomplete result for ${input}"`);
    }
  }

  async function dontAutocompleteAfterDeclaration(webConsoleFront) {
    info("test autocomplete for 'var win'");
    let matches = (await webConsoleFront.autocomplete("var win")).matches;
    is(matches, null, "no autocompletion on a var declaration");

    info("test autocomplete for 'const win'");
    matches = (await webConsoleFront.autocomplete("const win")).matches;
    is(matches, null, "no autocompletion on a const declaration");

    info("test autocomplete for 'let win'");
    matches = (await webConsoleFront.autocomplete("let win")).matches;
    is(matches, null, "no autocompletion on a let declaration");

    info("test autocomplete for 'function win'");
    matches = (await webConsoleFront.autocomplete("function win")).matches;
    is(matches, null, "no autocompletion on a function declaration");

    info("test autocomplete for 'class win'");
    matches = (await webConsoleFront.autocomplete("class win")).matches;
    is(matches, null, "no autocompletion on a class declaration");

    info("test autocomplete for 'const win = win'");
    matches = (await webConsoleFront.autocomplete("const win = win")).matches;
    ok(matches.includes("window"), "autocompletion still happens after the `=` sign");

    info("test autocomplete for 'in var'");
    matches = (await webConsoleFront.autocomplete("in var")).matches;
    ok(matches.includes("varify"),
      "autocompletion still happens with a property name starting with 'var'");
}

async function doKeywordsAutocomplete(webConsoleFront) {
  info("test autocomplete for 'func'");
  let matches = (await webConsoleFront.autocomplete("func")).matches;
  ok(matches.includes("function"), "keywords are returned");

  info("test autocomplete for ':func'");
  matches = (await webConsoleFront.autocomplete(":func")).matches;
  is(!matches.includes("function"), true,
    "'function' is not returned when prefixed with ':'");

  info("test autocomplete for 'window.func'");
  matches = (await webConsoleFront.autocomplete("window.func")).matches;
  ok(!matches.includes("function"),
    "'function' is not returned when doing a property access");

  info("test autocomplete for 'window[func'");
  matches = (await webConsoleFront.autocomplete("window[func")).matches;
  ok(!matches.includes("function"),
    "'function' is not returned when doing an element access");
  }

  async function dontAutocomplete(webConsoleFront) {
    const inputs = [
      "",
      "       ",
      "\n",
      "\n  ",
      "  \n  ",
      "  \n",
      "true;",
      "true,",
      "({key:",
      "a=",
      "if(a<",
      "if(a>",
      "1+",
      "1-",
      "++",
      "--",
      "1*",
      "2**",
      "1/",
      "1%",
      "1|",
      "1&",
      "1^",
      "~",
      "1<<",
      "1>>",
      "1>>>",
      "false||",
      "false&&",
      "x=true?",
      "x=false?1:",
      "!",
      ...RESERVED_JS_KEYWORDS.map(keyword => `${keyword} `),
      ...RESERVED_JS_KEYWORDS.map(keyword => `${keyword}  `),
    ];
    for (const input of inputs) {
      info(`test autocomplete for "${input}"`);
      const matches = (await webConsoleFront.autocomplete(input)).matches;
      is(matches, null, `No autocomplete result for ${input}"`);
    }
  }

  async function getAutocompleteMatches(webConsoleFront, input) {
    info(`test autocomplete for "${input}"`);
    const res = (await webConsoleFront.autocomplete(input));
    return res.matches;
  }

  async function evaluateExpression(consoleFront, expression) {
    const onEvaluationResult = consoleFront.once("evaluationResult");
    await consoleFront.evaluateJSAsync({ text: expression });
    return onEvaluationResult;
  }

</script>
</body>
</html>
