I'm using Electron + Nightmare.js to do unit tests.
I need to copy a string to the clibboard > focus a element > paste the content. Then the test is about if my JavaScript is handling behaving properly.
I read in the electron docs about the clipboard api,
and copy/paste in webview, but not sure how that integrates with the Nightmare.js API, probably in a .action
as suggested in this issue.
A example would be:
import nightmare from 'nightmare'
nightmare.action('copyPaste', function(name, options, parent, win, renderer, done) {
// some magic here
});
// and then
let res = await page
.wait('.my-element-to-render')
.copyPaste(blob)
.evaluate(() => {
return document.querySelector('.my-element').value;
}).end();
expect(res).to.equal('my pasted string');
Any pointers or experience with this?
From the arguments I get from nightmare.action
what is the equivalent to <webview>
so I can call its copy/paste method?
Copy / Paste isn't working in Electron. This is due to the lack of the application’s menu with keybindings to the native clipboard. You can fix that with this code of JS.
You should also checkout this GitHub repository - its a clean HowTo fix this problem.
CODESNIPPET
var app = require("app");
var BrowserWindow = require("browser-window");
var Menu = require("menu");
var mainWindow = null;
app.on("window-all-closed", function(){
app.quit();
});
app.on("ready", function () {
mainWindow = new BrowserWindow({
width: 980,
height: 650,
"min-width": 980,
"min-height": 650
});
mainWindow.openDevTools();
mainWindow.loadUrl("file://" + __dirname + "/index.html");
mainWindow.on("closed", function () {
mainWindow = null;
});
// Create the Application's main menu
var template = [{
label: "Application",
submenu: [
{ label: "About Application", selector: "orderFrontStandardAboutPanel:" },
{ type: "separator" },
{ label: "Quit", accelerator: "Command+Q", click: function() { app.quit(); }}
]}, {
label: "Edit",
submenu: [
{ label: "Undo", accelerator: "CmdOrCtrl+Z", selector: "undo:" },
{ label: "Redo", accelerator: "Shift+CmdOrCtrl+Z", selector: "redo:" },
{ type: "separator" },
{ label: "Cut", accelerator: "CmdOrCtrl+X", selector: "cut:" },
{ label: "Copy", accelerator: "CmdOrCtrl+C", selector: "copy:" },
{ label: "Paste", accelerator: "CmdOrCtrl+V", selector: "paste:" },
{ label: "Select All", accelerator: "CmdOrCtrl+A", selector: "selectAll:" }
]}
];
Menu.setApplicationMenu(Menu.buildFromTemplate(template));
});
Ok, after many hours got it working!
The trick is to use the click
event that Nightmare.js can simulate and use that to trigger copy and paste in the browser.
The copy/paste logic is:
.evaluate((blob) => {
var editor = document.querySelector('.the-element-to-paste-to');
editor.innerHTML = ''; // so we have a clean paste
window.addEventListener('copy', function (e){ // this will fire because of "document.execCommand('copy')"
e.clipboardData.setData('text/html', blob);
e.preventDefault();
return false;
});
var mockClick = document.createElement('button');
mockClick.id = 'mockClick';
mockClick.addEventListener('click', function(e){ // this will fire first
document.execCommand("copy");
editor.focus();
document.execCommand('paste');
});
document.body.appendChild(mockClick);
}, myTextThatIWantToPaste)
Nightmare.js will take care of triggering the click and then we get a copy/paste with our own text.
My whole test is now like:
it('should handle pasted test', async function () {
let page = visit('/index.html');
let res = await page
.wait('.the-element-to-paste-to')
.evaluate((blob) => {
var editor = document.querySelector('.the-element-to-paste-to');
editor.innerHTML = ''; // so we have a clean paste
window.addEventListener('copy', function (e){
e.clipboardData.setData('text/html', blob);
e.preventDefault();
return false;
});
var mockClick = document.createElement('button');
mockClick.id = 'mockClick';
mockClick.addEventListener('click', function(e){
document.execCommand("copy");
editor.focus();
document.execCommand('paste');
});
document.body.appendChild(mockClick);
}, tableBlob)
.click('#mockClick') // <---- this is the trigger to the click
.wait(100)
.evaluate(() => {
var editor = document.querySelector('.the-element-to-paste-to');
return {
someBold: editor.querySelector('strong').innerHTML,
someItalic: editor.querySelector('em').innerHTML,
someUnderlined: editor.querySelector('u').innerHTML,
someRows: editor.querySelectorAll('table tr').length,
someColumns: editor.querySelectorAll('table tr:first-child td').length,
}
}).end();
expect(res.someBold).toEqual('Col1 Row 1');
expect(res.someItalic).toEqual('Col2 Row 2');
expect(res.someUnderlined).toEqual('Col3 Row 3');
expect(res.someRows).toEqual(3);
expect(res.someColumns).toEqual(3);
});
Use the plain old JavaScript to handle the key-down events.
Snippet:
if (
process.env.NODE_ENV === 'development' ||
process.env.DEBUG_PROD === 'true'
) {
mainWindow.webContents.on('context-menu', (e, props) => {
const { x, y } = props;
Menu.buildFromTemplate([
{
label: 'Inspect element',
click: () => {
mainWindow.inspectElement(x, y);
}
}
]).popup(mainWindow);
});
mainWindow.webContents.on('devtools-opened', () => {
mainWindow.webContents.devToolsWebContents.executeJavaScript(`
window.addEventListener('keydown', function (e) {
if (e.keyCode === 88 && e.metaKey) {
document.execCommand('cut');
}
else if (e.keyCode === 67 && e.metaKey) {
document.execCommand('copy');
}
else if (e.keyCode === 86 && e.metaKey) {
document.execCommand('paste');
}
else if (e.keyCode === 65 && e.metaKey) {
document.execCommand('selectAll');
}
else if (e.keyCode === 90 && e.metaKey) {
document.execCommand('undo');
}
else if (e.keyCode === 89 && e.metaKey) {
document.execCommand('redo');
}
});
`);
});
mainWindow.openDevTools();
}
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With