Bug 1537598 - Test Column Breakpoints.
authorJason Laster <jlaster@mozilla.com>
Wed, 10 Apr 2019 13:47:32 +0000
changeset 468762 205b23b72638bfa3ede3942b3ba035ddb59ce8dc
parent 468761 e18f4ceca46b6495c991df728897a2de340c48be
child 468763 98f1b0d8ff4b89601746b9bf561139e7234718e7
push id35850
push userdvarga@mozilla.com
push dateWed, 10 Apr 2019 21:52:56 +0000
treeherdermozilla-central@9d3dbe3fef26 [default view] [failures only]
perfherder[talos] [build metrics] [platform microbench] (compared to previous push)
bugs1537598
milestone68.0a1
first release with
nightly linux32
nightly linux64
nightly mac
nightly win32
nightly win64
last release without
nightly linux32
nightly linux64
nightly mac
nightly win32
nightly win64
Bug 1537598 - Test Column Breakpoints. Differential Revision: https://phabricator.services.mozilla.com/D26827
devtools/client/debugger/test/mochitest/.eslintrc
devtools/client/debugger/test/mochitest/browser_dbg-breakpoints-columns.js
devtools/client/debugger/test/mochitest/browser_dbg-breakpoints-cond.js
devtools/client/debugger/test/mochitest/helpers.js
--- a/devtools/client/debugger/test/mochitest/.eslintrc
+++ b/devtools/client/debugger/test/mochitest/.eslintrc
@@ -46,25 +46,29 @@
     "BrowserToolboxProcess": false,
     "OS": false,
     "selectors": false,
     "waitForNextDispatch": false,
     "waitForDispatch": false,
     "waitForThreadEvents": false,
     "waitForState": false,
     "waitForElement": false,
+    "waitForAllElements": false,
     "waitForElementWithSelector": false,
     "waitForPaused": false,
     "waitForSources": false,
     "waitForSource": false,
     "waitForLoadedSource": false,
     "waitForSelectedSource": false,
     "waitForBreakpoint": false,
     "waitForBreakpointCount": false,
+    "waitForCondition": false,
+    "waitForLog": false,
     "isPaused": false,
+    "assertClass": false,
     "assertSourceCount": false,
     "assertEditorBreakpoint": false,
     "assertBreakpointSnippet": false,
     "assertEmptyLines": false,
     "assertPausedLocation": false,
     "assertHighlightLocation": false,
     "createDebuggerContext": false,
     "initDebugger": false,
@@ -87,17 +91,19 @@
     "toggleCallStack": false,
     "toggleScopes": false,
     "isVisibleWithin": false,
     "clickElement": false,
     "clickElementWithSelector": false,
     "clickDOMElement": false,
     "altClickElement": false,
     "rightClickElement": false,
+    "rightClickEl": false,
     "clickGutter": false,
+    "typeInPanel": false,
     "selectMenuItem": false,
     "selectContextMenuItem": false,
     "togglePauseOnExceptions": false,
     "type": false,
     "pressKey": false,
     "synthesizeContextMenuEvent": false,
     "EXAMPLE_URL": false,
     "waitUntil": false
--- a/devtools/client/debugger/test/mochitest/browser_dbg-breakpoints-columns.js
+++ b/devtools/client/debugger/test/mochitest/browser_dbg-breakpoints-columns.js
@@ -1,111 +1,104 @@
-/* Any copyright is dedicated to the Public Domain.
- * http://creativecommons.org/publicdomain/zero/1.0/ */
+/* 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/>. */
 
-function getColumnBreakpointElements(dbg) {
-  return findAllElementsWithSelector(dbg, ".column-breakpoint");
+async function enableFirstBreakpoint(dbg) {
+  getCM(dbg).setCursor({ line: 32, ch: 0 });
+  await addBreakpoint(dbg, "long", 32);
+  const bpMarkers = await waitForAllElements(dbg, "columnBreakpoints");
+
+  ok(bpMarkers.length === 2, "2 column breakpoints");
+  assertClass(bpMarkers[0], "active");
+  assertClass(bpMarkers[1], "active", false);
 }
 
-async function assertConditionalBreakpointIsFocused(dbg) {
-  const input = findElement(dbg, "conditionalPanelInput");
-  await waitForElementFocus(dbg, input);
-}
+async function enableSecondBreakpoint(dbg) {
+  let bpMarkers = await waitForAllElements(dbg, "columnBreakpoints");
 
-function waitForElementFocus(dbg, el) {
-  const doc = dbg.win.document;
-  return waitFor(() => doc.activeElement == el && doc.hasFocus());
-}
+  bpMarkers[1].click();
+  await waitForBreakpointCount(dbg, 2);
 
-function hasCondition(marker) {
-  return marker.classList.contains("has-condition");
+  bpMarkers = findAllElements(dbg, "columnBreakpoints");
+  assertClass(bpMarkers[1], "active");
+  await waitForAllElements(dbg, "breakpointItems", 2);
 }
 
 async function setConditionalBreakpoint(dbg, index, condition) {
-  const {
-      addConditionalBreakpoint,
-      editConditionalBreakpoint
-  } = selectors.gutterContextMenu;
-  // Make this work with either add or edit menu items
-  const selector = `${addConditionalBreakpoint},${editConditionalBreakpoint}`;
+  let bpMarkers = await waitForAllElements(dbg, "columnBreakpoints");
+  rightClickEl(dbg, bpMarkers[index]);
+  selectContextMenuItem(dbg, selectors.addConditionItem);
+  await typeInPanel(dbg, condition);
+  await waitForCondition(dbg, condition);
+
+  bpMarkers = await waitForAllElements(dbg, "columnBreakpoints");
+  assertClass(bpMarkers[index], "has-condition");
+}
 
-  rightClickElement(dbg, "breakpointItem", index);
-  selectContextMenuItem(dbg, selector);
-  await waitForElement(dbg, "conditionalPanelInput");
-  await assertConditionalBreakpointIsFocused(dbg);
+async function setLogPoint(dbg, index, expression) {
+  let bpMarkers = await waitForAllElements(dbg, "columnBreakpoints");
+  rightClickEl(dbg, bpMarkers[index]);
 
-  // Position cursor reliably at the end of the text.
-  pressKey(dbg, "End");
-  type(dbg, condition);
-  pressKey(dbg, "Enter");
+  selectContextMenuItem(dbg, selectors.addLogItem);
+  await typeInPanel(dbg, expression);
+  await waitForLog(dbg, expression);
+
+  bpMarkers = await waitForAllElements(dbg, "columnBreakpoints");
+  assertClass(bpMarkers[index], "has-log");
 }
 
-function removeBreakpointViaContext(dbg, index) {
-  rightClickElement(dbg, "breakpointItem", index);
-  selectContextMenuItem(dbg, "#node-menu-delete-self");
+async function disableBreakpoint(dbg, index) {
+  rightClickElement(dbg, "columnBreakpoints");
+  selectContextMenuItem(dbg, selectors.disableItem);
+
+  await waitForState(dbg, state => {
+    const bp = dbg.selectors.getBreakpointsList(state)[index];
+    return bp.disabled;
+  });
+
+  const bpMarkers = await waitForAllElements(dbg, "columnBreakpoints");
+  assertClass(bpMarkers[0], "disabled");
 }
 
-// Test enabling and disabling a breakpoint using the check boxes
+async function removeFirstBreakpoint(dbg) {
+  let bpMarkers = await waitForAllElements(dbg, "columnBreakpoints");
+
+  bpMarkers[0].click();
+  bpMarkers = await waitForAllElements(dbg, "columnBreakpoints");
+  assertClass(bpMarkers[0], "active", false);
+}
+
+async function removeAllBreakpoints(dbg, line, count) {
+  clickGutter(dbg, 32);
+  await waitForBreakpointCount(dbg, 0);
+
+  ok(findAllElements(dbg, "columnBreakpoints").length == 0);
+}
+
 add_task(async function() {
   const dbg = await initDebugger("doc-scripts.html", "simple1");
-  await pushPref("devtools.debugger.features.column-breakpoints", false);
-
-  if(!Services.prefs.getBoolPref("devtools.debugger.features.column-breakpoints")) {
-    ok(true, "This test only applies when column breakpoints are on");
-    return;
-  }
+  await selectSource(dbg, "long");
 
-  await selectSource(dbg, "simple1");
-
-  // Scroll down to desired line so that column breakpoints render
-  getCM(dbg).setCursor({ line: 15, ch: 0 });
-
-  // Create a breakpoint at 15:undefined
-  await addBreakpoint(dbg, "simple1", 15);
+  info("1. Add a column breakpoint on line 32");
+  await enableFirstBreakpoint(dbg);
 
-  // Wait for column breakpoint markers
-  await waitForElementWithSelector(dbg, ".column-breakpoint");
+  info("2. Click on the second breakpoint on line 32");
+  await enableSecondBreakpoint(dbg);
 
-  let columnBreakpointMarkers = getColumnBreakpointElements(dbg);
-  ok(
-    columnBreakpointMarkers.length === 2,
-      "2 column breakpoint markers display"
-  );
-
-  // Create a breakpoint at 15:8
-  columnBreakpointMarkers[0].click();
-
-  // Create a breakpoint at 15:28
-  columnBreakpointMarkers[1].click();
+  info("3. Add a condition to the first breakpoint");
+  await setConditionalBreakpoint(dbg, 0, "foo");
 
-  // Wait for breakpoints in right panel to render
-  await waitForState(dbg, state => {
-    return dbg.win.document.querySelectorAll(".breakpoints-list .breakpoint").length === 3;
-  })
-
-  // Scroll down in secondary pane so element we want to right-click is showing
-  dbg.win.document.querySelector(".secondary-panes").scrollTop = 100;
+  info("4. Add a log to the first breakpoint");
+  await setLogPoint(dbg, 0, "bar");
 
-  // Set a condition at 15:8
-  await setConditionalBreakpoint(dbg, 4, "Eight");
-
-  // Ensure column breakpoint is yellow
-  await waitForElementWithSelector(dbg, ".column-breakpoint.has-condition");
-
-  // Remove the breakpoint from 15:undefined via the secondary pane context menu
-  removeBreakpointViaContext(dbg, 3);
+  info("5. Disable the first breakpoint");
+  await disableBreakpoint(dbg, 0);
 
-  // Ensure that there's still a marker on line 15
-  await waitForState(dbg, state => dbg.selectors.getBreakpointCount(state) == 2);
-  await waitForElementWithSelector(dbg, ".column-breakpoint.has-condition");
-  columnBreakpointMarkers = getColumnBreakpointElements(dbg);
-  ok(hasCondition(columnBreakpointMarkers[0]), "First column breakpoint has conditional style");
+  info("6. Remove the first breakpoint");
+  await removeFirstBreakpoint(dbg);
 
-  // Remove the breakpoint from 15:8
-  removeBreakpointViaContext(dbg, 3);
+  info("7. Add a condition to the second breakpoint");
+  await setConditionalBreakpoint(dbg, 1, "foo2");
 
-  // Ensure there's still a marker and it has no condition
-  await waitForState(dbg, state => dbg.selectors.getBreakpointCount(state) == 1);
-  await waitForElementWithSelector(dbg, ".column-breakpoint");
-
-  // Ensure the first column breakpoint has no conditional style
-  await waitFor(() => !hasCondition(getColumnBreakpointElements(dbg)[0]));
+  info("8. test removing the breakpoints by clicking in the gutter");
+  await removeAllBreakpoints(dbg, 32, 0);
 });
--- a/devtools/client/debugger/test/mochitest/browser_dbg-breakpoints-cond.js
+++ b/devtools/client/debugger/test/mochitest/browser_dbg-breakpoints-cond.js
@@ -26,128 +26,91 @@ function assertEditorBreakpoint(
   const hasLogClass = getLineEl(dbg, line).classList.contains("has-log");
 
   ok(
     hasLogClass === hasLog,
     `Breakpoint log ${hasLog ? "exists" : "does not exist"} on line ${line}`
   );
 }
 
-function waitForElementFocus(dbg, el) {
-  const doc = dbg.win.document;
-  return waitFor(() => doc.activeElement == el && doc.hasFocus());
-}
-
-function waitForBreakpointWithCondition(dbg, url, line, cond) {
-  return waitForState(dbg, () => {
-    const bp = findBreakpoint(dbg, url, line);
-    return (
-      bp && bp.options.condition && (!cond || bp.options.condition == cond)
-    );
-  });
-}
-
-function waitForBreakpointWithLog(dbg, url, line) {
-  return waitForState(dbg, () => {
-    const bp = findBreakpoint(dbg, url, line);
-    return bp && bp.options.logValue;
-  });
-}
-
 function waitForBreakpointWithoutCondition(dbg, url, line) {
   return waitForState(dbg, () => {
     const bp = findBreakpoint(dbg, url, line);
     return bp && !bp.options.condition;
   });
 }
 
 async function setConditionalBreakpoint(dbg, index, condition) {
-  const {
-    addConditionalBreakpoint,
-    editConditionalBreakpoint
-  } = selectors.gutterContextMenu;
   // Make this work with either add or edit menu items
-  const selector = `${addConditionalBreakpoint},${editConditionalBreakpoint}`;
-
+  const { addConditionItem, editConditionItem } = selectors;
+  const selector = `${addConditionItem},${editConditionItem}`;
   rightClickElement(dbg, "gutter", index);
   selectContextMenuItem(dbg, selector);
-  await waitForElement(dbg, "conditionalPanelInput");
-
-  // Position cursor reliably at the end of the text.
-  pressKey(dbg, "End");
-  type(dbg, condition);
-  pressKey(dbg, "Enter");
+  typeInPanel(dbg, condition);
 }
 
 async function setLogPoint(dbg, index, value) {
-  const { addLogPoint, editLogPoint } = selectors.gutterContextMenu;
-
-  // Make this work with either add or edit menu items
-  const selector = `${addLogPoint},${editLogPoint}`;
-
   rightClickElement(dbg, "gutter", index);
-  selectContextMenuItem(dbg, selector);
-  await waitForElement(dbg, "conditionalPanelInput");
-
-  // Position cursor reliably at the end of the text.
-  pressKey(dbg, "End");
-  type(dbg, value);
-  pressKey(dbg, "Enter");
+  selectContextMenuItem(
+    dbg,
+    `${selectors.addLogItem},${selectors.editLogItem}`
+  );
+  await typeInPanel(dbg, value);
 }
 
 add_task(async function() {
   const dbg = await initDebugger("doc-scripts.html", "simple2");
   await pushPref("devtools.debugger.features.column-breakpoints", true);
   await pushPref("devtools.debugger.features.log-points", true);
 
   await selectSource(dbg, "simple2");
   await waitForSelectedSource(dbg, "simple2");
 
+  info("Set condition `1`");
   await setConditionalBreakpoint(dbg, 5, "1");
-  await waitForDispatch(dbg, "SET_BREAKPOINT");
-  await waitForBreakpointWithCondition(dbg, "simple2", 5);
+  await waitForCondition(dbg, 1);
 
   let bp = findBreakpoint(dbg, "simple2", 5);
   is(bp.options.condition, "1", "breakpoint is created with the condition");
   await assertEditorBreakpoint(dbg, 5, { hasCondition: true });
 
   info("Edit the conditional breakpoint set above");
   await setConditionalBreakpoint(dbg, 5, "2");
-  await waitForBreakpointWithCondition(dbg, "simple2", 5, "12");
+  await waitForCondition(dbg, 12);
 
   bp = findBreakpoint(dbg, "simple2", 5);
   is(bp.options.condition, "12", "breakpoint is created with the condition");
   await assertEditorBreakpoint(dbg, 5, { hasCondition: true });
 
   clickElement(dbg, "gutter", 5);
   await waitForDispatch(dbg, "REMOVE_BREAKPOINT");
   bp = findBreakpoint(dbg, "simple2", 5);
   is(bp, null, "breakpoint was removed");
   await assertEditorBreakpoint(dbg, 5);
 
   info("Adding a condition to a breakpoint");
   clickElement(dbg, "gutter", 5);
   await waitForDispatch(dbg, "SET_BREAKPOINT");
   await setConditionalBreakpoint(dbg, 5, "1");
-  await waitForBreakpointWithCondition(dbg, "simple2", 5);
+  await waitForCondition(dbg, 1);
 
   bp = findBreakpoint(dbg, "simple2", 5);
   is(bp.options.condition, "1", "breakpoint is created with the condition");
   await assertEditorBreakpoint(dbg, 5, { hasCondition: true });
 
   rightClickElement(dbg, "breakpointItem", 2);
   info('select "remove condition"');
   selectContextMenuItem(dbg, selectors.breakpointContextMenu.removeCondition);
   await waitForBreakpointWithoutCondition(dbg, "simple2", 5);
   bp = findBreakpoint(dbg, "simple2", 5);
   is(bp.options.condition, undefined, "breakpoint condition removed");
 
   info('Add "log point"');
   await setLogPoint(dbg, 5, "44");
-  await waitForBreakpointWithLog(dbg, "simple2", 5);
+  await waitForLog(dbg, 44);
   await assertEditorBreakpoint(dbg, 5, { hasLog: true });
 
   bp = findBreakpoint(dbg, "simple2", 5);
   is(bp.options.logValue, "44", "breakpoint condition removed");
 
   await altClickElement(dbg, "gutter", 6);
   bp = await waitForBreakpoint(dbg, "simple2", 6);
   is(bp.options.logValue, "displayName", "logPoint has default value");
--- a/devtools/client/debugger/test/mochitest/helpers.js
+++ b/devtools/client/debugger/test/mochitest/helpers.js
@@ -199,21 +199,34 @@ function waitForSource(dbg, url) {
   );
 }
 
 async function waitForElement(dbg, name, ...args) {
   await waitUntil(() => findElement(dbg, name, ...args));
   return findElement(dbg, name, ...args);
 }
 
+async function waitForAllElements(dbg, name, count = 1) {
+  await waitUntil(() => findAllElements(dbg, name).length >= count);
+  return findAllElements(dbg, name);
+}
+
 async function waitForElementWithSelector(dbg, selector) {
   await waitUntil(() => findElementWithSelector(dbg, selector));
   return findElementWithSelector(dbg, selector);
 }
 
+function assertClass(el, className, exists = true) {
+  if (exists) {
+    ok(el.classList.contains(className), `${className} class exists`);
+  } else {
+    ok(!el.classList.contains(className), `${className} class does not exist`);
+  }
+}
+
 function waitForSelectedLocation(dbg, line) {
   return waitForState(dbg, state => {
     const location = dbg.selectors.getSelectedLocation(state);
     return location && location.line == line;
   });
 }
 
 function waitForSelectedSource(dbg, url) {
@@ -482,16 +495,32 @@ async function waitForPaused(dbg, url) {
       isPaused(dbg) && !!getSelectedScope(state, getCurrentThread(state)),
     "paused"
   );
 
   await waitForLoadedScopes(dbg);
   await waitForSelectedSource(dbg, url);
 }
 
+function waitForCondition(dbg, condition) {
+  return waitForState(dbg, state =>
+    dbg.selectors
+      .getBreakpointsList(state)
+      .find(bp => bp.options.condition == condition)
+  );
+}
+
+function waitForLog(dbg, logValue) {
+  return waitForState(dbg, state =>
+    dbg.selectors
+      .getBreakpointsList(state)
+      .find(bp => bp.options.logValue == logValue)
+  );
+}
+
 /*
  * useful for when you want to see what is happening
  * e.g await waitForever()
  */
 function waitForever() {
   return new Promise(r => {});
 }
 
@@ -1185,32 +1214,31 @@ const selectors = {
     disableAll: "#node-menu-disable-all",
     disableOthers: "#node-menu-disable-others",
     enableSelf: "#node-menu-enable-self",
     enableOthers: "#node-menu-enable-others",
     remove: "#node-menu-delete-self",
     removeOthers: "#node-menu-delete-other",
     removeCondition: "#node-menu-remove-condition"
   },
+  columnBreakpoints: ".column-breakpoint",
   scopes: ".scopes-list",
   scopeNode: i => `.scopes-list .tree-node:nth-child(${i}) .object-label`,
   scopeValue: i =>
     `.scopes-list .tree-node:nth-child(${i}) .object-delimiter + *`,
   frame: i => `.frames [role="list"] [role="listitem"]:nth-child(${i})`,
   frames: '.frames [role="list"] [role="listitem"]',
   gutter: i => `.CodeMirror-code *:nth-child(${i}) .CodeMirror-linenumber`,
-  // These work for bobth the breakpoint listing and gutter marker
-  gutterContextMenu: {
-    addConditionalBreakpoint:
-      "#node-menu-add-condition, #node-menu-add-conditional-breakpoint",
-    editConditionalBreakpoint:
-      "#node-menu-edit-condition, #node-menu-edit-conditional-breakpoint",
-    addLogPoint: "#node-menu-add-log-point",
-    editLogPoint: "#node-menu-edit-log-point"
-  },
+  addConditionItem:
+    "#node-menu-add-condition, #node-menu-add-conditional-breakpoint",
+  editConditionItem:
+    "#node-menu-edit-condition, #node-menu-edit-conditional-breakpoint",
+  addLogItem: "#node-menu-add-log-point",
+  editLogItem: "#node-menu-edit-log-point",
+  disableItem: "#node-menu-disable-breakpoint",
   menuitem: i => `menupopup menuitem:nth-child(${i})`,
   pauseOnExceptions: ".pause-exceptions",
   breakpoint: ".CodeMirror-code > .new-breakpoint",
   highlightLine: ".CodeMirror-code > .highlight-line",
   debugLine: ".new-debug-line",
   debugErrorLine: ".new-debug-line-error",
   codeMirror: ".CodeMirror",
   resume: ".resume.active",
@@ -1334,22 +1362,22 @@ function altClickElement(dbg, elementNam
   el.scrollIntoView();
 
   return EventUtils.synthesizeMouseAtCenter(el, { altKey: true }, dbg.win);
 }
 
 function rightClickElement(dbg, elementName, ...args) {
   const selector = getSelector(elementName, ...args);
   const doc = dbg.win.document;
+  return rightClickEl(dbg, doc.querySelector(selector));
+}
 
-  return EventUtils.synthesizeMouseAtCenter(
-    doc.querySelector(selector),
-    { type: "contextmenu" },
-    dbg.win
-  );
+function rightClickEl(dbg, el) {
+  const doc = dbg.win.document;
+  EventUtils.synthesizeMouseAtCenter(el, { type: "contextmenu" }, dbg.win);
 }
 
 async function clickGutter(dbg, line) {
   const el = await codeMirrorGutterElement(dbg, line);
   clickDOMElement(dbg, el);
 }
 
 function selectContextMenuItem(dbg, selector) {
@@ -1358,16 +1386,25 @@ function selectContextMenuItem(dbg, sele
 
   // there are several context menus, we want the one with the menu-api
   const popup = doc.querySelector('menupopup[menu-api="true"]');
 
   const item = popup.querySelector(selector);
   return EventUtils.synthesizeMouseAtCenter(item, {}, dbg.toolbox.win);
 }
 
+async function typeInPanel(dbg, text) {
+  await waitForElement(dbg, "conditionalPanelInput");
+
+  // Position cursor reliably at the end of the text.
+  pressKey(dbg, "End");
+  type(dbg, text);
+  pressKey(dbg, "Enter");
+}
+
 /**
  * Toggles the debugger call stack accordian.
  *
  * @memberof mochitest/actions
  * @param {Object} dbg
  * @return {Promise}
  * @static
  */