api-debugger-spec.ts 8.0 KB

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