electron-userland / spectron

DEPRECATED: 🔎 Test Electron apps using ChromeDriver
http://electronjs.org/spectron
MIT License
1.68k stars 229 forks source link

Async IPC does not return to sender #98

Open davej opened 8 years ago

davej commented 8 years ago

Easiest explained with code:

In the main process:

ipcMain.on('ping', e => {
  console.log('ping received');
  e.sender.send('pong');
});

In Spectron:

const {ipcRenderer} = app.electron;
ipcRenderer.on('pong', () => {
  console.log('pong received');
});
ipcRenderer.send('ping');

The ping gets received by the main process and it sends the pong without error but the pong never gets received in Spectron.


The synchronous version seems to work ok in Spectron (you just need to unwrap the promise). The following works ok:

ipcMain.on('ping', e => { e.returnValue('pong'); });
console.log(await app.electron.ipcRenderer.sendSync('ping')); // logs 'pong'
davej commented 8 years ago

Also see: https://github.com/electron/spectron/issues/91

kevinsawicki commented 8 years ago

The parameters to spectron helpers must be JSON serializable since they are sent remotely to ChromeDriver.

So passing a function to app.electron.ipcRenderer.on will not work correctly since it can not be serialized. This limitation needs to be explained better in the docs.

This specific API could be probably be addressed via something like #47 though

wojtkowiak commented 7 years ago

@kevinsawicki I have tried to do it in the way you did it in https://github.com/electron/spectron/pull/47 but with no luck.

    t.context.app.client.executeAsync(function (requireName, done) {
        var ipc = window[requireName]('electron').ipcRenderer;
        ipc.on('localStorage__getAll___response', function () {
            console.log('yep');
            done('yhy');
        });
    }, t.context.app.api.requireName).then(res => console.log('output', res.value));
    await app.electron.ipcRenderer.send('localStorage__getAll');
    await wait(5000);

    console.log(await app.client.getMainProcessLogs());
    console.log(await app.client.getRenderProcessLogs());

Output:

[ '',
  'getall received',
  'getall answered',
  '[42740:1026/164502:INFO:CONSOLE(5)] "yep", source:  (5)' ]
[]
  √ test » getAll (10.8s)
Unhandled Rejection: tests\functional\test.js
  Error: asynchronous script timeout: result was not received in 5 seconds
    execute(<Function>, "ipcRenderer", "send", localStorage__getAll, "require") - D:/projekty/meteor-desktop-localstorage/node_modules/spectron/lib/api.js:223:21

You can see that console.log('yep') was called in the renderer logs. So the done callback was called for sure. Why is it complaining about not receiving result for ipcRenderer", "send. Without the part with t.context.app.client.executeAsync it does not complain though. It seems like it would mix those two - I mean that doing app.electron.ipcRenderer.send influences somehow executeAsync.

wojtkowiak commented 7 years ago

Ok, I have managed to work it out differently by sending and waiting response in the same executeAsync. Here is what I've worked out so far, works well and I will probably leave it at this:

    t.context.app.client.addCommand('sendIpcAndWaitForResponse', function async(requireName, eventToSend, eventArgs, eventToListenTo) {
        return this.executeAsync(function ($requireName, $eventToSend, $eventArgs, $eventToListenTo, done) {
            const ipc = window[$requireName]('electron').ipcRenderer;
            ipc.once($eventToListenTo, function eventCallback() {
                done(Array.prototype.slice.call(arguments));
            });
            $eventArgs.unshift($eventToSend);
            ipc.send.apply(ipc, $eventArgs);
        }, requireName, eventToSend, eventArgs, eventToListenTo);
    });

    const result = await t.context.app.client
        .sendIpcAndWaitForResponse(
            t.context.app.api.requireName,
            'ipcEventToSend',
            ["some", "args", "to", "send"],
            'ipcEventToListenTo'
        );

@kevinsawicki if you want I can add a PR with waitForIpcEvent and sendIpcAndWaitForResponse - just let me know and please point me to correct file in which this could be defined.