summaryrefslogtreecommitdiffstats
path: root/devtools/client/webconsole/test/browser/browser_webconsole_context_menu_copy_entire_message.js
blob: b4f076ec671c11612b152c14bfc63c82af4638cb (plain)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
/* Any copyright is dedicated to the Public Domain.
 * http://creativecommons.org/publicdomain/zero/1.0/ */

"use strict";

const httpServer = createTestHTTPServer();
httpServer.registerPathHandler(`/`, function (request, response) {
  response.setStatusLine(request.httpVersion, 200, "OK");
  response.write(`
    <meta charset=utf8>
    <h1>Test "copy message" context menu entry</h1>
    <script type="text/javascript" src="test.js"></script>`);
});

httpServer.registerPathHandler("/test.js", function (request, response) {
  response.setHeader("Content-Type", "application/javascript");
  response.write(`
    window.logStuff = function() {
      console.log("simple text message");
      function wrapper() {
        console.log(new Error("error object"));
        console.trace();
        for (let i = 0; i < 2; i++) console.log("repeated")
        console.log(document.location + "?" + "z".repeat(100))
      }
      wrapper();
    };
    z.bar = "baz";
  `);
});

const TEST_URI = `http://localhost:${httpServer.identity.primaryPort}/`;

// RegExp that validates copied text for log lines.
const LOG_FORMAT_WITH_TIMESTAMP = /^[\d:.]+ .+/;
const PREF_MESSAGE_TIMESTAMP = "devtools.webconsole.timestampMessages";

// Test the Copy menu item of the webconsole copies the expected clipboard text for
// different log messages.

add_task(async function () {
  await pushPref(PREF_MESSAGE_TIMESTAMP, true);

  const hud = await openNewTabAndConsole(TEST_URI);

  info("Call the log function defined in the test page");
  await SpecialPowers.spawn(gBrowser.selectedBrowser, [], () => {
    content.wrappedJSObject.logStuff();
  });

  info("Test copy menu item with timestamp");
  await testMessagesCopy(hud, true);

  // Disable timestamp and wait until timestamp are not displayed anymore.
  await toggleConsoleSetting(
    hud,
    ".webconsole-console-settings-menu-item-timestamps"
  );
  await waitFor(
    () => hud.ui.outputNode.querySelector(".message .timestamp") === null
  );

  info("Test copy menu item without timestamp");
  await testMessagesCopy(hud, false);
});

async function testMessagesCopy(hud, timestamp) {
  const newLineString = "\n";

  info("Test copy menu item for the simple log");
  let message = await waitFor(() =>
    findConsoleAPIMessage(hud, "simple text message")
  );
  let clipboardText = await copyMessageContent(hud, message);
  ok(true, "Clipboard text was found and saved");

  info("Check copied text for simple log message");
  let lines = clipboardText.split(newLineString);
  is(lines.length, 2, "There are 2 lines in the copied text");
  is(lines[1], "", "The last line is an empty new line");
  is(
    lines[0],
    `${
      timestamp ? getTimestampText(message) + " " : ""
    }simple text message test.js:3:15`,
    "Line of simple log message has expected text"
  );
  if (timestamp) {
    ok(
      LOG_FORMAT_WITH_TIMESTAMP.test(lines[0]),
      "Log line has the right format:\n" + lines[0]
    );
  }

  info("Test copy menu item for the console.trace message");
  message = await waitFor(() => findConsoleAPIMessage(hud, "console.trace"));
  // Wait for the stacktrace to be rendered.
  await waitFor(() => message.querySelector(".frames"));
  clipboardText = await copyMessageContent(hud, message);
  ok(true, "Clipboard text was found and saved");

  info("Check copied text for the console.trace message");
  lines = clipboardText.split(newLineString);
  is(lines.length, 4, "There are 4 lines in the copied text");
  is(lines[lines.length - 1], "", "The last line is an empty new line");
  is(
    lines[0],
    `${
      timestamp ? getTimestampText(message) + " " : ""
    }console.trace() test.js:6:17`,
    "Stacktrace first line has the expected text"
  );
  if (timestamp) {
    ok(
      LOG_FORMAT_WITH_TIMESTAMP.test(lines[0]),
      "Log line has the right format:\n" + lines[0]
    );
  }
  is(
    lines[1],
    `    wrapper ${TEST_URI}test.js:6`,
    "Stacktrace first line has the expected text"
  );
  is(
    lines[2],
    `    logStuff ${TEST_URI}test.js:10`,
    "Stacktrace second line has the expected text"
  );

  info("Test copy menu item for the error message");
  message = await waitFor(() => findConsoleAPIMessage(hud, "Error:"));
  // Wait for the stacktrace to be rendered.
  await waitFor(() => message.querySelector(".frames"));
  clipboardText = await copyMessageContent(hud, message);
  ok(true, "Clipboard text was found and saved");
  lines = clipboardText.split(newLineString);
  is(
    lines[0],
    `${timestamp ? getTimestampText(message) + " " : ""}Error: error object`,
    "Error object first line has expected text"
  );
  if (timestamp) {
    ok(
      LOG_FORMAT_WITH_TIMESTAMP.test(lines[0]),
      "Log line has the right format:\n" + lines[0]
    );
  }
  is(
    lines[1],
    `    wrapper ${TEST_URI}test.js:5`,
    "Error Stacktrace first line has the expected text"
  );
  is(
    lines[2],
    `    logStuff ${TEST_URI}test.js:10`,
    "Error Stacktrace second line has the expected text"
  );

  info("Test copy menu item for the reference error message");
  message = await waitFor(() => findErrorMessage(hud, "ReferenceError:"));
  clipboardText = await copyMessageContent(hud, message);
  ok(true, "Clipboard text was found and saved");
  lines = clipboardText.split(newLineString);
  is(
    lines[0],
    (timestamp ? getTimestampText(message) + " " : "") +
      "Uncaught ReferenceError: z is not defined",
    "ReferenceError first line has expected text"
  );
  if (timestamp) {
    ok(
      LOG_FORMAT_WITH_TIMESTAMP.test(lines[0]),
      "Log line has the right format:\n" + lines[0]
    );
  }
  is(
    lines[1],
    `    <anonymous> ${TEST_URI}test.js:12`,
    "ReferenceError second line has expected text"
  );
  ok(
    !!message.querySelector(".learn-more-link"),
    "There is a Learn More link in the ReferenceError message"
  );
  is(
    clipboardText.toLowerCase().includes("Learn More"),
    false,
    "The Learn More text wasn't put in the clipboard"
  );

  message = await waitFor(() => findConsoleAPIMessage(hud, "repeated 2"));
  clipboardText = await copyMessageContent(hud, message);
  ok(true, "Clipboard text was found and saved");

  info("Test copy menu item for the message with the cropped URL");
  message = await waitFor(() => findConsoleAPIMessage(hud, "z".repeat(100)));
  ok(!!message.querySelector("a.cropped-url"), "URL is cropped");
  clipboardText = await copyMessageContent(hud, message);
  ok(
    clipboardText.startsWith(TEST_URI) + "?" + "z".repeat(100),
    "Full URL was copied to clipboard"
  );
}

function getTimestampText(messageEl) {
  return getSelectionTextFromElement(messageEl.querySelector(".timestamp"));
}

/**
 * Simple helper method to open the context menu on a given message, and click on the copy
 * menu item.
 */
async function copyMessageContent(hud, messageEl) {
  const menuPopup = await openContextMenu(hud, messageEl);
  const copyMenuItem = menuPopup.querySelector("#console-menu-copy");
  ok(copyMenuItem, "copy menu item is enabled");

  const text = await waitForClipboardPromise(
    () => copyMenuItem.click(),
    data => data
  );

  menuPopup.hidePopup();
  return text;
}

/**
 * Return the string representation, as if it was selected with the mouse and copied,
 * using the Selection API.
 *
 * @param {HTMLElement} el
 * @returns {String} the text representation of the element.
 */
function getSelectionTextFromElement(el) {
  const doc = el.ownerDocument;
  const win = doc.defaultView;
  const range = doc.createRange();
  range.selectNode(el);
  const selection = win.getSelection();
  selection.addRange(range);
  const selectionText = selection.toString();
  selection.removeRange(range);
  return selectionText;
}