Migrate devtools-active-dom-objects heap layout test

Bug:  1110817
Change-Id: I38bb4d748f5f70388b7b0808090498929462928a
Reviewed-on: https://chromium-review.googlesource.com/c/devtools/devtools-frontend/+/2406156
Commit-Queue: Jack Franklin <jacktfranklin@chromium.org>
Reviewed-by: Sigurd Schneider <sigurds@chromium.org>
diff --git a/test/e2e/helpers/memory-helpers.ts b/test/e2e/helpers/memory-helpers.ts
index 4dbd4a9..11f9859 100644
--- a/test/e2e/helpers/memory-helpers.ts
+++ b/test/e2e/helpers/memory-helpers.ts
@@ -5,7 +5,7 @@
 import {assert} from 'chai';
 import * as puppeteer from 'puppeteer';
 
-import {platform} from '../../shared/helper.js';
+import {$, platform, waitForElementWithTextContent} from '../../shared/helper.js';
 import {$$, click, getBrowserAndPages, pasteText, waitFor, waitForFunction, waitForNone} from '../../shared/helper.js';
 
 
@@ -174,3 +174,15 @@
 export async function waitForRetainerChain(expectedRetainers: Array<string>) {
   await waitForFunction(assertRetainerChain.bind(null, expectedRetainers));
 }
+
+export async function changeViewViaDropdown(newPerspective: string) {
+  const perspectiveDropdownSelector = 'select[aria-label="Perspective"]';
+  const dropdown = await $(perspectiveDropdownSelector) as puppeteer.ElementHandle<HTMLSelectElement>;
+
+  const optionToSelect = await waitForElementWithTextContent(newPerspective, dropdown);
+  const optionValue = await optionToSelect.evaluate(opt => opt.getAttribute('value'));
+  if (!optionValue) {
+    throw new Error(`Could not find heap snapshot perspective option: ${newPerspective}`);
+  }
+  dropdown.select(optionValue);
+}
diff --git a/test/e2e/memory/memory_test.ts b/test/e2e/memory/memory_test.ts
index 94c437b..74442bc 100644
--- a/test/e2e/memory/memory_test.ts
+++ b/test/e2e/memory/memory_test.ts
@@ -3,10 +3,9 @@
 // found in the LICENSE file.
 
 import {assert} from 'chai';
-
-import {$$, goToResource} from '../../shared/helper.js';
+import {$$, click, getBrowserAndPages, goToResource, waitForElementsWithTextContent, waitForElementWithTextContent, waitForFunction} from '../../shared/helper.js';
 import {describe, it} from '../../shared/mocha-extensions.js';
-import {findSearchResult, navigateToMemoryTab, setSearchFilter, takeHeapSnapshot, waitForNonEmptyHeapSnapshotData, waitForRetainerChain, waitForSearchResultNumber} from '../helpers/memory-helpers.js';
+import {changeViewViaDropdown, findSearchResult, navigateToMemoryTab, setSearchFilter, takeHeapSnapshot, waitForNonEmptyHeapSnapshotData, waitForRetainerChain, waitForSearchResultNumber} from '../helpers/memory-helpers.js';
 
 describe('The Memory Panel', async () => {
   it('Loads content', async () => {
@@ -69,4 +68,23 @@
       'Window /',
     ]);
   });
+
+  it('Puts all ActiveDOMObjects with pending activities into one group', async () => {
+    await goToResource('memory/dom-objects.html');
+    await navigateToMemoryTab();
+    await takeHeapSnapshot();
+    await waitForNonEmptyHeapSnapshotData();
+    await changeViewViaDropdown('Containment');
+    const pendingActiviesElement = await waitForElementWithTextContent('Pending activities');
+
+    // Focus and then expand the pending activities row to show its children
+    await click(pendingActiviesElement);
+    const {frontend} = getBrowserAndPages();
+    await frontend.keyboard.press('ArrowRight');
+
+    await waitForFunction(async () => {
+      const pendingActiviesChildren = await waitForElementsWithTextContent('MediaQueryList');
+      return pendingActiviesChildren.length === 2;
+    });
+  });
 });
diff --git a/test/e2e/resources/memory/BUILD.gn b/test/e2e/resources/memory/BUILD.gn
index adef41d..969e46d 100644
--- a/test/e2e/resources/memory/BUILD.gn
+++ b/test/e2e/resources/memory/BUILD.gn
@@ -8,6 +8,7 @@
   sources = [
     "default.html",
     "detached-node.html",
+    "dom-objects.html",
     "event-listeners.html",
   ]
 }
diff --git a/test/e2e/resources/memory/dom-objects.html b/test/e2e/resources/memory/dom-objects.html
new file mode 100644
index 0000000..d8245d9
--- /dev/null
+++ b/test/e2e/resources/memory/dom-objects.html
@@ -0,0 +1,16 @@
+<!--
+  Copyright 2020 The Chromium Authors. All rights reserved.
+  Use of this source code is governed by a BSD-style license that can be
+  found in the LICENSE file.
+-->
+<h1>Memory Panel (Heap profiler) Test</h1>
+<script>
+    function onChange(e) {
+      console.log('onChange ' + e.matches);
+    }
+    var m = window.matchMedia('(min-width: 1400px)');
+    m.addListener(onChange);
+    m = window.matchMedia('(min-height: 1800px)');
+    m.addListener(onChange);
+    console.log('Created 2 MediaQueryList elements');
+</script>
diff --git a/test/shared/helper.ts b/test/shared/helper.ts
index 43f6f60..5a8f11e 100644
--- a/test/shared/helper.ts
+++ b/test/shared/helper.ts
@@ -177,6 +177,19 @@
   return element;
 };
 
+/**
+ * Search for all elements based on their textContent
+ *
+ * @param textContent The text content to search for.
+ * @param root The root of the search.
+ */
+export const $$textContent = async (textContent: string, root?: puppeteer.JSHandle) => {
+  const {frontend} = getBrowserAndPages();
+  const rootElement = root ? root as puppeteer.ElementHandle : frontend;
+  const element = await rootElement.$$('pierceShadowText/' + textContent);
+  return element;
+};
+
 export const timeout = (duration: number) => new Promise(resolve => setTimeout(resolve, duration));
 
 export const waitFor = async (selector: string, root?: puppeteer.JSHandle, asyncScope = new AsyncScope()) => {
@@ -204,6 +217,18 @@
                              }, asyncScope));
     };
 
+export const waitForElementsWithTextContent =
+    (textContent: string, root?: puppeteer.JSHandle, asyncScope = new AsyncScope()) => {
+      return asyncScope.exec(() => waitForFunction(async () => {
+                               const elems = await $$textContent(textContent, root);
+                               if (elems && elems.length) {
+                                 return elems;
+                               }
+
+                               return undefined;
+                             }, asyncScope));
+    };
+
 export const waitForFunction = async<T>(fn: () => Promise<T|undefined>, asyncScope = new AsyncScope()): Promise<T> => {
   return await asyncScope.exec(async () => {
     while (true) {