summaryrefslogtreecommitdiffstats
path: root/devtools/client/webconsole/test/browser/browser_webconsole_context_menu_export_console_output.js
blob: 757b4a5ae7e2243db323dd97b79e3803c14647dc (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
/* 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(`
    <html>
      <head>
        <meta charset="utf-8">
        <script type="text/javascript" src="test.js"></script>
      </head>
      <body>Test "Export All" context menu entry</body>
    </html>`);
});

httpServer.registerPathHandler("/test.js", function (request, response) {
  response.setHeader("Content-Type", "application/javascript");
  response.write(`
    window.logStuff = function() {
      function wrapper() {
        console.log("hello");
        console.log("myObject:", {a: 1}, "myArray:", ["b", "c"]);
        console.log(new Error("error object"));
        console.trace("myConsoleTrace");
        console.info("world", "!");
        /* add enough messages to trigger virtualization */
        for (let i = 0; i < 100; i++) {
          console.log("item-"+i);
        }
      }
      wrapper();
    };
  `);
});

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

const { MockFilePicker } = SpecialPowers;
MockFilePicker.init(window.browsingContext);
MockFilePicker.returnValue = MockFilePicker.returnOK;

var FileUtils = ChromeUtils.importESModule(
  "resource://gre/modules/FileUtils.sys.mjs"
).FileUtils;

// Test the export visible messages to clipboard of the webconsole copies the expected
// clipboard text for different log messages to find if everything is copied to clipboard.

add_task(async function testExportToClipboard() {
  // Clear clipboard content.
  SpecialPowers.clipboardCopyString("");
  // Display timestamp to make sure we export them (there's a container query that would
  // hide them in the regular case, which we don't want).
  await pushPref("devtools.webconsole.timestampMessages", true);

  const hud = await openNewTabAndConsole(TEST_URI);
  await clearOutput(hud);

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

  info("Test export to clipboard ");
  // Let's wait until we have all the logged messages.
  const lastMessage = await waitFor(() =>
    findConsoleAPIMessage(hud, "item-99")
  );

  const clipboardText = await exportAllToClipboard(hud, lastMessage);
  ok(true, "Clipboard text was found and saved");

  checkExportedText(clipboardText);

  info("Test export to file");
  const fileText = await exportAllToFile(hud, lastMessage);
  checkExportedText(fileText);
});

function checkExportedText(text) {
  // Here we should have:
  //   -----------------------------------------------------
  //   hello test.js:4:17
  //   -----------------------------------------------------
  //   myObject:
  //   Object { a: 1 }
  //    myArray:
  //   Array [ "b", "c"]
  //   test.js:5:17
  //   -----------------------------------------------------
  //   Error: error object
  //       wrapper test.js:5
  //       logStuff test.js:14
  //   test.js:6:17
  //   -----------------------------------------------------
  //   console.trace() myConsoleTrace test.js:7:9
  //       wrapper test.js:7
  //       logStuff test.js:14
  //   -----------------------------------------------------
  //   world ! test.js:8:17
  //   -----------------------------------------------------
  //   item-0 test.js:11:19
  //   -----------------------------------------------------
  //   item-1 test.js:11:19
  //   -----------------------------------------------------
  //   […]
  //   -----------------------------------------------------
  //   item-99 test.js:11:19
  //   -----------------------------------------------------
  info("Check if all messages where exported as expected");
  let lines = text.split("\n").map(line => line.replace(/\r$/, ""));

  is(lines.length, 115, "There's 115 lines of text");
  is(lines.at(-1), "", "Last line is empty");

  info("Check that timestamp are displayed");
  const timestampRegex = /^\d{2}:\d{2}:\d{2}\.\d{3} /;
  // only check the first message
  ok(timestampRegex.test(lines[0]), "timestamp are included in the messages");
  lines = lines.map(l => l.replace(timestampRegex, ""));

  info("Check simple text message");
  is(lines[0], "hello test.js:4:17", "Simple log has expected text");

  info("Check multiple logged items message");
  is(lines[1], `myObject: `);
  is(lines[2], `Object { a: 1 }`);
  is(lines[3], ` myArray: `);
  is(lines[4], `Array [ "b", "c" ]`);
  is(lines[5], `test.js:5:17`);

  info("Check logged error object");
  is(lines[6], `Error: error object`);
  is(lines[7], `    wrapper ${TEST_URI}test.js:6`);
  is(lines[8], `    logStuff ${TEST_URI}test.js:14`);
  is(lines[9], `test.js:6:17`);

  info("Check console.trace message");
  is(lines[10], `console.trace() myConsoleTrace test.js:7:17`);
  is(lines[11], `    wrapper ${TEST_URI}test.js:7`);
  is(lines[12], `    logStuff ${TEST_URI}test.js:14`);

  info("Check console.info message");
  is(lines[13], `world ! test.js:8:17`);

  const numberMessagesStartIndex = 14;
  for (let i = 0; i < 100; i++) {
    is(
      lines[numberMessagesStartIndex + i],
      `item-${i} test.js:11:19`,
      `Got expected text for line ${numberMessagesStartIndex + i}`
    );
  }
}

async function exportAllToFile(hud, message) {
  const menuPopup = await openContextMenu(hud, message);
  const exportFile = menuPopup.querySelector("#console-menu-export-file");
  ok(exportFile, "copy menu item is enabled");

  const nsiFile = new FileUtils.File(
    PathUtils.join(PathUtils.tempDir, `export_console_${Date.now()}.log`)
  );
  MockFilePicker.setFiles([nsiFile]);
  exportFile.click();
  info("Exporting to file");

  menuPopup.hidePopup();

  // The file may not be ready yet.
  await waitFor(() => IOUtils.exists(nsiFile.path));
  const buffer = await IOUtils.read(nsiFile.path);
  return new TextDecoder().decode(buffer);
}

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

  const clipboardText = await waitForClipboardPromise(
    () => exportClipboard.click(),
    data => data.includes("hello")
  );

  menuPopup.hidePopup();
  return clipboardText;
}