diff --git a/devtools/client/webconsole/reducers/messages.js b/devtools/client/webconsole/reducers/messages.js index 2993483c17eb..e7a9525ca2b8 100644 --- a/devtools/client/webconsole/reducers/messages.js +++ b/devtools/client/webconsole/reducers/messages.js @@ -151,8 +151,10 @@ function addMessage(newMessage, state, filtersState, prefsState) { // Add the new message with a reference to the parent group. const parentGroups = getParentGroups(currentGroup, groupsById); - newMessage.groupId = currentGroup; - newMessage.indent = parentGroups.length; + if (!isWarningGroup(newMessage)) { + newMessage.groupId = currentGroup; + newMessage.indent = parentGroups.length; + } ensureExecutionPoint(state, newMessage); @@ -188,12 +190,27 @@ function addMessage(newMessage, state, filtersState, prefsState) { ) { // Then we put it in the visibleMessages properties, at the position of the first // warning message inside the warningGroup. - // TODO [Bug 1534927]: It should be added before the outermost console.group message - // a warning message could be in. - const index = state - .visibleMessages - .indexOf(state.warningGroupsById.get(warningGroupMessageId)[0]); - state.visibleMessages.splice(index, 1, warningGroupMessageId); + // If that first warning message is in a console.group, we place it before the + // outermost console.group message. + const firstWarningMessageId = state.warningGroupsById.get(warningGroupMessageId)[0]; + const firstWarningMessage = state.messagesById.get(firstWarningMessageId); + const outermostGroupId = getOutermostGroup(firstWarningMessage, groupsById); + const groupIndex = state.visibleMessages.indexOf(outermostGroupId); + const warningMessageIndex = state.visibleMessages.indexOf(firstWarningMessageId); + + if (groupIndex > -1) { + // We remove the warning message + if (warningMessageIndex > -1) { + state.visibleMessages.splice(warningMessageIndex, 1); + } + + // And we put the warning group before the console.group + state.visibleMessages.splice(groupIndex, 0, warningGroupMessageId); + } else { + // If the warning message is not in a console.group, we replace it by the + // warning group message. + state.visibleMessages.splice(warningMessageIndex, 1, warningGroupMessageId); + } } } @@ -415,11 +432,20 @@ function messages(state = MessageState(), action, filtersState, prefsState) { // If the message is a group if (isGroupType(messagesById.get(messageId).type)) { - // Hide all its children - closeState.visibleMessages = visibleMessages.filter(id => - getParentGroups(messagesById.get(id).groupId, groupsById) - .includes(messageId) === false - ); + // Hide all its children, unless they're in a warningGroup. + closeState.visibleMessages = visibleMessages.filter((id, i, arr) => { + const message = messagesById.get(id); + const warningGroupMessage = + messagesById.get(getParentWarningGroupMessageId(message)); + + // If the message is in a warning group, then we return its current visibility. + if (shouldGroupWarningMessages(warningGroupMessage, closeState, prefsState)) { + return arr.includes(id); + } + + const parentGroups = getParentGroups(message.groupId, groupsById); + return parentGroups.includes(messageId) === false; + }); } else if (isWarningGroup(messagesById.get(messageId))) { // If the message was a warningGroup, we hide all the messages in the group. const groupMessages = closeState.warningGroupsById.get(messageId); @@ -551,6 +577,14 @@ function getParentGroups(currentGroup, groupsById) { return groups; } +function getOutermostGroup(message, groupsById) { + const groups = getParentGroups(message.groupId, groupsById); + if (groups.length === 0) { + return null; + } + return groups[groups.length - 1]; +} + /** * Remove all top level messages that exceeds message limit. * Also populate an array of all backend actors associated with these @@ -734,10 +768,14 @@ function getMessageVisibility(message, { prefsState, checkGroup = true, }) { - // Do not display the message if it's in closed group. + const warningGroupMessage = + messagesState.messagesById.get(getParentWarningGroupMessageId(message)); + + // Do not display the message if it's in closed group and not in a warning group. if ( checkGroup && !isInOpenedGroup(message, messagesState.groupsById, messagesState.messagesUiById) + && !shouldGroupWarningMessages(warningGroupMessage, messagesState, prefsState) ) { return { visible: false, @@ -1183,6 +1221,10 @@ function getLastMessageId(state) { * @param {PrefsState} prefsState */ function shouldGroupWarningMessages(warningGroupMessage, messagesState, prefsState) { + if (!warningGroupMessage) { + return false; + } + // Only group if the preference is ON. if (!prefsState.groupWarnings) { return false; @@ -1190,7 +1232,11 @@ function shouldGroupWarningMessages(warningGroupMessage, messagesState, prefsSta // We group warning messages if there are at least 2 messages that could go in it. const warningGroup = messagesState.warningGroupsById.get(warningGroupMessage.id); - return warningGroup && warningGroup.length > 1; + if (!warningGroup || !Array.isArray(warningGroup)) { + return false; + } + + return warningGroup.length > 1; } exports.messages = messages; diff --git a/devtools/client/webconsole/test/mochitest/browser.ini b/devtools/client/webconsole/test/mochitest/browser.ini index fe2e6cffc301..bca3774603d1 100644 --- a/devtools/client/webconsole/test/mochitest/browser.ini +++ b/devtools/client/webconsole/test/mochitest/browser.ini @@ -399,5 +399,6 @@ tags = trackingprotection [browser_webconsole_visibility_messages.js] [browser_webconsole_warn_about_replaced_api.js] [browser_webconsole_warning_group_content_blocking.js] +[browser_webconsole_warning_groups_outside_console_group.js] [browser_webconsole_warning_groups.js] [browser_webconsole_websocket.js] diff --git a/devtools/client/webconsole/test/mochitest/browser_webconsole_warning_group_content_blocking.js b/devtools/client/webconsole/test/mochitest/browser_webconsole_warning_group_content_blocking.js index 46d061eea044..9bace1b7193f 100644 --- a/devtools/client/webconsole/test/mochitest/browser_webconsole_warning_group_content_blocking.js +++ b/devtools/client/webconsole/test/mochitest/browser_webconsole_warning_group_content_blocking.js @@ -63,7 +63,7 @@ add_task(async function testContentBlockingMessage() { "The badge has the expected text"); checkConsoleOutputForWarningGroup(hud, [ - `▶︎ ${CONTENT_BLOCKING_GROUP_LABEL} 2`, + `▶︎⚠ ${CONTENT_BLOCKING_GROUP_LABEL} 2`, ]); info("Open the group"); @@ -71,7 +71,7 @@ add_task(async function testContentBlockingMessage() { await waitFor(() => findMessage(hud, "http://tracking.example.com/?1")); checkConsoleOutputForWarningGroup(hud, [ - `▼︎ ${CONTENT_BLOCKING_GROUP_LABEL} 2`, + `▼︎⚠ ${CONTENT_BLOCKING_GROUP_LABEL} 2`, `| The resource at \u201chttp://tracking.example.com/?1&${now}\u201d was blocked`, `| The resource at \u201chttp://tracking.example.com/?2&${now}\u201d was blocked`, ]); @@ -154,7 +154,7 @@ async function testStorageAccessBlockedGrouping(getWarningMessage) { "The badge has the expected text"); checkConsoleOutputForWarningGroup(hud, [ - `▶︎ ${CONTENT_BLOCKING_GROUP_LABEL} 2`, + `▶︎⚠ ${CONTENT_BLOCKING_GROUP_LABEL} 2`, ]); info("Open the group"); @@ -162,7 +162,7 @@ async function testStorageAccessBlockedGrouping(getWarningMessage) { await waitFor(() => findMessage(hud, TRACKER_IMG)); checkConsoleOutputForWarningGroup(hud, [ - `▼︎ ${CONTENT_BLOCKING_GROUP_LABEL} 2`, + `▼︎⚠ ${CONTENT_BLOCKING_GROUP_LABEL} 2`, `| ${getWarningMessage(TRACKER_IMG + "?1&" + now)}`, `| ${getWarningMessage(TRACKER_IMG + "?2&" + now)}`, ]); diff --git a/devtools/client/webconsole/test/mochitest/browser_webconsole_warning_groups.js b/devtools/client/webconsole/test/mochitest/browser_webconsole_warning_groups.js index 3879bb26b0b4..9532229d356f 100644 --- a/devtools/client/webconsole/test/mochitest/browser_webconsole_warning_groups.js +++ b/devtools/client/webconsole/test/mochitest/browser_webconsole_warning_groups.js @@ -52,7 +52,7 @@ add_task(async function testContentBlockingMessage() { "The badge has the expected text"); checkConsoleOutputForWarningGroup(hud, [ - `▶︎ ${CONTENT_BLOCKING_GROUP_LABEL}`, + `▶︎⚠ ${CONTENT_BLOCKING_GROUP_LABEL}`, `simple message 1`, ]); @@ -60,7 +60,7 @@ add_task(async function testContentBlockingMessage() { await logString(hud, "simple message 2"); checkConsoleOutputForWarningGroup(hud, [ - `▶︎ ${CONTENT_BLOCKING_GROUP_LABEL}`, + `▶︎⚠ ${CONTENT_BLOCKING_GROUP_LABEL}`, `simple message 1`, `simple message 2`, ]); @@ -70,7 +70,7 @@ add_task(async function testContentBlockingMessage() { await waitFor(() => node.querySelector(".warning-group-badge").textContent == "3"); checkConsoleOutputForWarningGroup(hud, [ - `▶︎ ${CONTENT_BLOCKING_GROUP_LABEL}`, + `▶︎⚠ ${CONTENT_BLOCKING_GROUP_LABEL}`, `simple message 1`, `simple message 2`, ]); @@ -80,7 +80,7 @@ add_task(async function testContentBlockingMessage() { await waitFor(() => findMessage(hud, BLOCKED_URL)); checkConsoleOutputForWarningGroup(hud, [ - `▼︎ ${CONTENT_BLOCKING_GROUP_LABEL}`, + `▼︎⚠ ${CONTENT_BLOCKING_GROUP_LABEL}`, `| ${BLOCKED_URL}?1`, `| ${BLOCKED_URL}?2`, `| ${BLOCKED_URL}?3`, @@ -96,7 +96,7 @@ add_task(async function testContentBlockingMessage() { ok(true, "The new tracking protection message is displayed"); checkConsoleOutputForWarningGroup(hud, [ - `▼︎ ${CONTENT_BLOCKING_GROUP_LABEL}`, + `▼︎⚠ ${CONTENT_BLOCKING_GROUP_LABEL}`, `| ${BLOCKED_URL}?1`, `| ${BLOCKED_URL}?2`, `| ${BLOCKED_URL}?3`, @@ -125,7 +125,7 @@ add_task(async function testContentBlockingMessage() { await logString(hud, "simple message 3"); checkConsoleOutputForWarningGroup(hud, [ - `▼︎ ${CONTENT_BLOCKING_GROUP_LABEL}`, + `▼︎⚠ ${CONTENT_BLOCKING_GROUP_LABEL}`, `| ${BLOCKED_URL}?1`, `| ${BLOCKED_URL}?2`, `| ${BLOCKED_URL}?3`, @@ -146,7 +146,7 @@ add_task(async function testContentBlockingMessage() { "The badge has the expected text"); checkConsoleOutputForWarningGroup(hud, [ - `▼︎ ${CONTENT_BLOCKING_GROUP_LABEL}`, + `▼︎⚠ ${CONTENT_BLOCKING_GROUP_LABEL}`, `| ${BLOCKED_URL}?1`, `| ${BLOCKED_URL}?2`, `| ${BLOCKED_URL}?3`, @@ -154,7 +154,7 @@ add_task(async function testContentBlockingMessage() { `simple message 1`, `simple message 2`, `Navigated to`, - `▶︎ ${CONTENT_BLOCKING_GROUP_LABEL}`, + `▶︎⚠ ${CONTENT_BLOCKING_GROUP_LABEL}`, `simple message 3`, ]); @@ -163,7 +163,7 @@ add_task(async function testContentBlockingMessage() { await waitFor(() => findMessages(hud, BLOCKED_URL).length === 6); checkConsoleOutputForWarningGroup(hud, [ - `▼︎ ${CONTENT_BLOCKING_GROUP_LABEL}`, + `▼︎⚠ ${CONTENT_BLOCKING_GROUP_LABEL}`, `| ${BLOCKED_URL}?1`, `| ${BLOCKED_URL}?2`, `| ${BLOCKED_URL}?3`, @@ -171,7 +171,7 @@ add_task(async function testContentBlockingMessage() { `simple message 1`, `simple message 2`, `Navigated to`, - `▼︎ ${CONTENT_BLOCKING_GROUP_LABEL}`, + `▼︎⚠ ${CONTENT_BLOCKING_GROUP_LABEL}`, `| ${BLOCKED_URL}?5`, `| ${BLOCKED_URL}?6`, `simple message 3`, @@ -182,7 +182,7 @@ add_task(async function testContentBlockingMessage() { await waitFor(() => findMessages(hud, BLOCKED_URL).length === 4); checkConsoleOutputForWarningGroup(hud, [ - `▼︎ ${CONTENT_BLOCKING_GROUP_LABEL}`, + `▼︎⚠ ${CONTENT_BLOCKING_GROUP_LABEL}`, `| ${BLOCKED_URL}?1`, `| ${BLOCKED_URL}?2`, `| ${BLOCKED_URL}?3`, @@ -190,7 +190,7 @@ add_task(async function testContentBlockingMessage() { `simple message 1`, `simple message 2`, `Navigated to`, - `▶︎ ${CONTENT_BLOCKING_GROUP_LABEL}`, + `▶︎⚠ ${CONTENT_BLOCKING_GROUP_LABEL}`, `simple message 3`, ]); @@ -199,7 +199,7 @@ add_task(async function testContentBlockingMessage() { await waitFor(() => node.querySelector(".warning-group-badge").textContent == "3"); checkConsoleOutputForWarningGroup(hud, [ - `▼︎ ${CONTENT_BLOCKING_GROUP_LABEL}`, + `▼︎⚠ ${CONTENT_BLOCKING_GROUP_LABEL}`, `| ${BLOCKED_URL}?1`, `| ${BLOCKED_URL}?2`, `| ${BLOCKED_URL}?3`, @@ -207,7 +207,7 @@ add_task(async function testContentBlockingMessage() { `simple message 1`, `simple message 2`, `Navigated to`, - `▶︎ ${CONTENT_BLOCKING_GROUP_LABEL}`, + `▶︎⚠ ${CONTENT_BLOCKING_GROUP_LABEL}`, `simple message 3`, ]); }); diff --git a/devtools/client/webconsole/test/mochitest/browser_webconsole_warning_groups_outside_console_group.js b/devtools/client/webconsole/test/mochitest/browser_webconsole_warning_groups_outside_console_group.js new file mode 100644 index 000000000000..7dbdf874b1d3 --- /dev/null +++ b/devtools/client/webconsole/test/mochitest/browser_webconsole_warning_groups_outside_console_group.js @@ -0,0 +1,201 @@ +/* Any copyright is dedicated to the Public Domain. + * http://creativecommons.org/publicdomain/zero/1.0/ */ + +// Test that warning groups are not created outside console.group. + +"use strict"; +requestLongerTimeout(2); + +const TEST_FILE = + "browser/devtools/client/webconsole/test/mochitest/test-warning-groups.html"; +const TEST_URI = "http://example.com/" + TEST_FILE; + +const TRACKER_URL = "http://tracking.example.org/"; +const BLOCKED_URL = TRACKER_URL + + "browser/devtools/client/webconsole/test/mochitest/test-image.png"; + +const {UrlClassifierTestUtils} = ChromeUtils.import("resource://testing-common/UrlClassifierTestUtils.jsm"); +UrlClassifierTestUtils.addTestTrackers(); +registerCleanupFunction(function() { + UrlClassifierTestUtils.cleanupTestTrackers(); +}); + +// Tracking protection preferences +pushPref("privacy.trackingprotection.enabled", true); + +add_task(async function testContentBlockingMessage() { + const CONTENT_BLOCKING_GROUP_LABEL = "Content blocked messages"; + + // Enable groupWarning and persist log + await pushPref("devtools.webconsole.groupWarningMessages", true); + + const hud = await openNewTabAndConsole(TEST_URI); + + info("Log a console.group"); + const onGroupMessage = waitForMessage(hud, "myGroup"); + let onInGroupMessage = waitForMessage(hud, "log in group"); + ContentTask.spawn(gBrowser.selectedBrowser, null, function() { + content.wrappedJSObject.console.group("myGroup"); + content.wrappedJSObject.console.log("log in group"); + }); + const {node: consoleGroupMessageNode} = await onGroupMessage; + await onInGroupMessage; + + checkConsoleOutputForWarningGroup(hud, [ + `▼ myGroup`, + `| log in group`, + ]); + + info("Log a tracking protection message to check a single message isn't grouped"); + const now = Date.now(); + let onContentBlockingWarningMessage = waitForMessage(hud, BLOCKED_URL, ".warn"); + emitStorageAccessBlockedMessage(now); + await onContentBlockingWarningMessage; + + checkConsoleOutputForWarningGroup(hud, [ + `▼ myGroup`, + `| log in group`, + `| ${BLOCKED_URL}?${now}-1`, + ]); + + info("Collapse the console.group"); + consoleGroupMessageNode.querySelector(".arrow").click(); + await waitFor(() => !findMessage(hud, "log in group")); + + checkConsoleOutputForWarningGroup(hud, [ + `▶︎ myGroup`, + ]); + + info("Expand the console.group"); + consoleGroupMessageNode.querySelector(".arrow").click(); + await waitFor(() => findMessage(hud, "log in group")); + + checkConsoleOutputForWarningGroup(hud, [ + `▼ myGroup`, + `| log in group`, + `| ${BLOCKED_URL}?${now}-1`, + ]); + + info("Log a second tracking protection message to check that it causes the grouping"); + const onContentBlockingWarningGroupMessage = + waitForMessage(hud, CONTENT_BLOCKING_GROUP_LABEL, ".warn"); + emitStorageAccessBlockedMessage(now); + const {node: warningGroupNode} = await onContentBlockingWarningGroupMessage; + + checkConsoleOutputForWarningGroup(hud, [ + `▶︎⚠ ${CONTENT_BLOCKING_GROUP_LABEL}`, + `▼ myGroup`, + `| log in group`, + ]); + + info("Open the warning group"); + warningGroupNode.querySelector(".arrow").click(); + await waitFor(() => findMessage(hud, BLOCKED_URL)); + + checkConsoleOutputForWarningGroup(hud, [ + `▼︎⚠ ${CONTENT_BLOCKING_GROUP_LABEL}`, + `| ${BLOCKED_URL}?${now}-1`, + `| ${BLOCKED_URL}?${now}-2`, + `▼ myGroup`, + `| log in group`, + ]); + + info("Log a new tracking protection message to check it appears inside the group"); + onContentBlockingWarningMessage = + waitForMessage(hud, BLOCKED_URL, ".warn"); + emitStorageAccessBlockedMessage(now); + await onContentBlockingWarningMessage; + ok(true, "The new tracking protection message is displayed"); + + checkConsoleOutputForWarningGroup(hud, [ + `▼︎⚠ ${CONTENT_BLOCKING_GROUP_LABEL}`, + `| ${BLOCKED_URL}?${now}-1`, + `| ${BLOCKED_URL}?${now}-2`, + `| ${BLOCKED_URL}?${now}-3`, + `▼ myGroup`, + `| log in group`, + ]); + + info("Log a simple message to check if it goes into the console.group"); + onInGroupMessage = waitForMessage(hud, "log in group"); + ContentTask.spawn(gBrowser.selectedBrowser, null, function() { + content.wrappedJSObject.console.log("second log in group"); + }); + await onInGroupMessage; + + checkConsoleOutputForWarningGroup(hud, [ + `▼︎⚠ ${CONTENT_BLOCKING_GROUP_LABEL}`, + `| ${BLOCKED_URL}?${now}-1`, + `| ${BLOCKED_URL}?${now}-2`, + `| ${BLOCKED_URL}?${now}-3`, + `▼ myGroup`, + `| log in group`, + `| second log in group`, + ]); + + info("Collapse the console.group"); + consoleGroupMessageNode.querySelector(".arrow").click(); + await waitFor(() => !findMessage(hud, "log in group")); + + checkConsoleOutputForWarningGroup(hud, [ + `▼︎⚠ ${CONTENT_BLOCKING_GROUP_LABEL}`, + `| ${BLOCKED_URL}?${now}-1`, + `| ${BLOCKED_URL}?${now}-2`, + `| ${BLOCKED_URL}?${now}-3`, + `▶︎ myGroup`, + ]); + + info("Close the warning group"); + warningGroupNode.querySelector(".arrow").click(); + await waitFor(() => !findMessage(hud, BLOCKED_URL)); + + checkConsoleOutputForWarningGroup(hud, [ + `▶︎⚠ ${CONTENT_BLOCKING_GROUP_LABEL}`, + `▶︎ myGroup`, + ]); + + info("Open the console group"); + consoleGroupMessageNode.querySelector(".arrow").click(); + await waitFor(() => findMessage(hud, "log in group")); + + checkConsoleOutputForWarningGroup(hud, [ + `▶︎⚠ ${CONTENT_BLOCKING_GROUP_LABEL}`, + `▼ myGroup`, + `| log in group`, + `| second log in group`, + ]); + + info("Collapse the console.group"); + consoleGroupMessageNode.querySelector(".arrow").click(); + await waitFor(() => !findMessage(hud, "log in group")); + + checkConsoleOutputForWarningGroup(hud, [ + `▶︎⚠ ${CONTENT_BLOCKING_GROUP_LABEL}`, + `▶︎ myGroup`, + ]); + + info("Open the warning group"); + warningGroupNode.querySelector(".arrow").click(); + await waitFor(() => findMessage(hud, BLOCKED_URL)); + + checkConsoleOutputForWarningGroup(hud, [ + `▼︎⚠ ${CONTENT_BLOCKING_GROUP_LABEL}`, + `| ${BLOCKED_URL}?${now}-1`, + `| ${BLOCKED_URL}?${now}-2`, + `| ${BLOCKED_URL}?${now}-3`, + `▶︎ myGroup`, + ]); +}); + +let cpt = 0; +/** + * Emit a Content Blocking message. This is done by loading an image from an origin + * tagged as tracker. The image is loaded with a incremented counter query parameter + * each time so we can get the warning message. + */ +function emitStorageAccessBlockedMessage(prefix) { + const url = `${BLOCKED_URL}?${prefix}-${++cpt}`; + ContentTask.spawn(gBrowser.selectedBrowser, url, function(innerURL) { + content.wrappedJSObject.loadImage(innerURL); + }); +} diff --git a/devtools/client/webconsole/test/mochitest/head.js b/devtools/client/webconsole/test/mochitest/head.js index b69e9bf7d876..26d557ee7d78 100644 --- a/devtools/client/webconsole/test/mochitest/head.js +++ b/devtools/client/webconsole/test/mochitest/head.js @@ -1245,7 +1245,7 @@ function isScrolledToBottom(container) { * @param {Array} expectedMessages: An array of string representing the messages * from the output. This can only be a part of the string of the * message. - * Start the string with "▶︎ " or "▼ " to indicate that the + * Start the string with "▶︎⚠ " or "▼⚠ " to indicate that the * message is a warningGroup (with respectively an open or * collapsed arrow). * Start the string with "|︎ " to indicate that the message is @@ -1254,23 +1254,65 @@ function isScrolledToBottom(container) { function checkConsoleOutputForWarningGroup(hud, expectedMessages) { const messages = findMessages(hud, ""); is(messages.length, expectedMessages.length, "Got the expected number of messages"); + + const isInWarningGroup = index => { + const message = expectedMessages[index]; + if (!message.startsWith("|")) { + return false; + } + const groups = expectedMessages.slice(0, index) + .reverse() + .filter(m => !m.startsWith("|")); + if (groups.length === 0) { + ok(false, "Unexpected structure: an indented message isn't in a group"); + } + + return groups[0].startsWith("▶︎⚠") || groups[0].startsWith("▼⚠"); + }; + expectedMessages.forEach((expectedMessage, i) => { const message = messages[i]; + info(`Checking "${expectedMessage}"`); + + // Collapsed Warning group + if (expectedMessage.startsWith("▶︎⚠")) { + is(message.querySelector(".arrow").getAttribute("aria-expanded"), "false", + "There's a collapsed arrow"); + is(message.querySelector(".indent").getAttribute("data-indent"), "0", + "The warningGroup has the expected indent"); + expectedMessage = expectedMessage.replace("▶︎⚠ ", ""); + } + + // Expanded Warning group + if (expectedMessage.startsWith("▼︎⚠")) { + is(message.querySelector(".arrow").getAttribute("aria-expanded"), "true", + "There's an expanded arrow"); + is(message.querySelector(".indent").getAttribute("data-indent"), "0", + "The warningGroup has the expected indent"); + expectedMessage = expectedMessage.replace("▼︎⚠ ", ""); + } + + // Collapsed console.group if (expectedMessage.startsWith("▶︎")) { is(message.querySelector(".arrow").getAttribute("aria-expanded"), "false", "There's a collapsed arrow"); expectedMessage = expectedMessage.replace("▶︎ ", ""); } + // Expanded console.group if (expectedMessage.startsWith("▼")) { is(message.querySelector(".arrow").getAttribute("aria-expanded"), "true", "There's an expanded arrow"); - expectedMessage = expectedMessage.replace("▼︎ ", ""); + expectedMessage = expectedMessage.replace("▼ ", ""); } + // In-group message if (expectedMessage.startsWith("|")) { - is(message.querySelector(".indent.warning-indent").getAttribute("data-indent"), "1", - "The message has the expected indent"); + if (isInWarningGroup(i)) { + is(message.querySelector(".indent.warning-indent").getAttribute("data-indent"), + "1", "The message has the expected indent"); + } + expectedMessage = expectedMessage.replace("| ", ""); }