api-debugger-spec.ts 7.9 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218
  1. import { expect } from 'chai';
  2. import * as http from 'node:http';
  3. import * as path from 'node:path';
  4. import { BrowserWindow } from 'electron/main';
  5. import { closeAllWindows } from './lib/window-helpers';
  6. import { emittedUntil } from './lib/events-helpers';
  7. import { listen } from './lib/spec-helpers';
  8. import { once } from 'node:events';
  9. describe('debugger module', () => {
  10. const fixtures = path.resolve(__dirname, 'fixtures');
  11. let w: BrowserWindow;
  12. beforeEach(() => {
  13. w = new BrowserWindow({
  14. show: false,
  15. width: 400,
  16. height: 400
  17. });
  18. });
  19. afterEach(closeAllWindows);
  20. describe('debugger.attach', () => {
  21. it('succeeds when devtools is already open', async () => {
  22. await w.webContents.loadURL('about:blank');
  23. w.webContents.openDevTools();
  24. w.webContents.debugger.attach();
  25. expect(w.webContents.debugger.isAttached()).to.be.true();
  26. });
  27. it('fails when protocol version is not supported', done => {
  28. try {
  29. w.webContents.debugger.attach('2.0');
  30. } catch {
  31. expect(w.webContents.debugger.isAttached()).to.be.false();
  32. done();
  33. }
  34. });
  35. it('attaches when no protocol version is specified', async () => {
  36. w.webContents.debugger.attach();
  37. expect(w.webContents.debugger.isAttached()).to.be.true();
  38. });
  39. });
  40. describe('debugger.detach', () => {
  41. it('fires detach event', async () => {
  42. const detach = once(w.webContents.debugger, 'detach');
  43. w.webContents.debugger.attach();
  44. w.webContents.debugger.detach();
  45. const [, reason] = await detach;
  46. expect(reason).to.equal('target closed');
  47. expect(w.webContents.debugger.isAttached()).to.be.false();
  48. });
  49. it('doesn\'t disconnect an active devtools session', async () => {
  50. w.webContents.loadURL('about:blank');
  51. const detach = once(w.webContents.debugger, 'detach');
  52. w.webContents.debugger.attach();
  53. w.webContents.openDevTools();
  54. w.webContents.once('devtools-opened', () => {
  55. w.webContents.debugger.detach();
  56. });
  57. await detach;
  58. expect(w.webContents.debugger.isAttached()).to.be.false();
  59. expect((w as any).devToolsWebContents.isDestroyed()).to.be.false();
  60. });
  61. });
  62. describe('debugger.sendCommand', () => {
  63. let server: http.Server;
  64. afterEach(() => {
  65. if (server != null) {
  66. server.close();
  67. server = null as any;
  68. }
  69. });
  70. it('returns response', async () => {
  71. w.webContents.loadURL('about:blank');
  72. w.webContents.debugger.attach();
  73. const params = { expression: '4+2' };
  74. const res = await w.webContents.debugger.sendCommand('Runtime.evaluate', params);
  75. expect(res.wasThrown).to.be.undefined();
  76. expect(res.result.value).to.equal(6);
  77. w.webContents.debugger.detach();
  78. });
  79. it('returns response when devtools is opened', async () => {
  80. w.webContents.loadURL('about:blank');
  81. w.webContents.debugger.attach();
  82. const opened = once(w.webContents, 'devtools-opened');
  83. w.webContents.openDevTools();
  84. await opened;
  85. const params = { expression: '4+2' };
  86. const res = await w.webContents.debugger.sendCommand('Runtime.evaluate', params);
  87. expect(res.wasThrown).to.be.undefined();
  88. expect(res.result.value).to.equal(6);
  89. w.webContents.debugger.detach();
  90. });
  91. it('fires message event', async () => {
  92. const url = process.platform !== 'win32'
  93. ? `file://${path.join(fixtures, 'pages', 'a.html')}`
  94. : `file:///${path.join(fixtures, 'pages', 'a.html').replace(/\\/g, '/')}`;
  95. w.webContents.loadURL(url);
  96. w.webContents.debugger.attach();
  97. const message = emittedUntil(w.webContents.debugger, 'message',
  98. (event: Electron.Event, method: string) => method === 'Console.messageAdded');
  99. w.webContents.debugger.sendCommand('Console.enable');
  100. const [,, params] = await message;
  101. w.webContents.debugger.detach();
  102. expect((params as any).message.level).to.equal('log');
  103. expect((params as any).message.url).to.equal(url);
  104. expect((params as any).message.text).to.equal('a');
  105. });
  106. it('returns error message when command fails', async () => {
  107. w.webContents.loadURL('about:blank');
  108. w.webContents.debugger.attach();
  109. const promise = w.webContents.debugger.sendCommand('Test');
  110. await expect(promise).to.be.eventually.rejectedWith(Error, "'Test' wasn't found");
  111. w.webContents.debugger.detach();
  112. });
  113. it('handles valid unicode characters in message', async () => {
  114. server = http.createServer((req, res) => {
  115. res.setHeader('Content-Type', 'text/plain; charset=utf-8');
  116. res.end('\u0024');
  117. });
  118. const { url } = await listen(server);
  119. w.loadURL(url);
  120. // If we do this synchronously, it's fast enough to attach and enable
  121. // network capture before the load. If we do it before the loadURL, for
  122. // some reason network capture doesn't get enabled soon enough and we get
  123. // an error when calling `Network.getResponseBody`.
  124. w.webContents.debugger.attach();
  125. w.webContents.debugger.sendCommand('Network.enable');
  126. const [,, { requestId }] = await emittedUntil(w.webContents.debugger, 'message', (_event: any, method: string, params: any) =>
  127. method === 'Network.responseReceived' && params.response.url.startsWith('http://127.0.0.1'));
  128. await emittedUntil(w.webContents.debugger, 'message', (_event: any, method: string, params: any) =>
  129. method === 'Network.loadingFinished' && params.requestId === requestId);
  130. const { body } = await w.webContents.debugger.sendCommand('Network.getResponseBody', { requestId });
  131. expect(body).to.equal('\u0024');
  132. });
  133. it('does not crash for invalid unicode characters in message', async () => {
  134. w.webContents.debugger.attach();
  135. const loadingFinished = new Promise<void>(resolve => {
  136. w.webContents.debugger.on('message', (event, method) => {
  137. // loadingFinished indicates that page has been loaded and it did not
  138. // crash because of invalid UTF-8 data
  139. if (method === 'Network.loadingFinished') {
  140. resolve();
  141. }
  142. });
  143. });
  144. server = http.createServer((req, res) => {
  145. res.setHeader('Content-Type', 'text/plain; charset=utf-8');
  146. res.end('\uFFFF');
  147. });
  148. const { url } = await listen(server);
  149. w.webContents.debugger.sendCommand('Network.enable');
  150. w.loadURL(url);
  151. await loadingFinished;
  152. });
  153. it('uses empty sessionId by default', async () => {
  154. w.webContents.loadURL('about:blank');
  155. w.webContents.debugger.attach();
  156. const onMessage = once(w.webContents.debugger, 'message');
  157. await w.webContents.debugger.sendCommand('Target.setDiscoverTargets', { discover: true });
  158. const [, method, params, sessionId] = await onMessage;
  159. expect(method).to.equal('Target.targetCreated');
  160. expect(params.targetInfo.targetId).to.not.be.empty();
  161. expect(sessionId).to.be.empty();
  162. w.webContents.debugger.detach();
  163. });
  164. it('creates unique session id for each target', (done) => {
  165. w.webContents.loadFile(path.join(__dirname, 'fixtures', 'sub-frames', 'debug-frames.html'));
  166. w.webContents.debugger.attach();
  167. let session: String;
  168. w.webContents.debugger.on('message', (event, ...args) => {
  169. const [method, params, sessionId] = args;
  170. if (method === 'Target.targetCreated') {
  171. w.webContents.debugger.sendCommand('Target.attachToTarget', { targetId: params.targetInfo.targetId, flatten: true }).then(result => {
  172. session = result.sessionId;
  173. w.webContents.debugger.sendCommand('Debugger.enable', {}, result.sessionId);
  174. });
  175. }
  176. if (method === 'Debugger.scriptParsed') {
  177. expect(sessionId).to.equal(session);
  178. w.webContents.debugger.detach();
  179. done();
  180. }
  181. });
  182. w.webContents.debugger.sendCommand('Target.setDiscoverTargets', { discover: true });
  183. });
  184. });
  185. });