I'm creating a desktop application using electron and flask. When I attempt to send a selected path (selected by the user via a folder select dialog. Using electron I am able to get the full path and not something like C:/fake_path. This is because electron provides the full path via .files[0].path) to python using flask, I never receive a response or caught an error (the main.logPython function is never called in either branch following the rq).
However, if I include app.debug = True in the python file to turn on the Flask debugger, it runs as expected and prints out the path. Essentially the python code is not even being reached unless I put it into debug mode.
edit: I know this is true because I've attempted to run python code other than just the return (like creating a new file) and it's never run.
edit #2: It seems this problem is related to the admin.js file. If I attempt to make a similar connection in the main.js file I get the result that I expect, however if I use the same code in the admin.js file I get no response as stated above.
Hopefully I've provided enough information. If you need anything else please let me know.
Sorry for any formatting issues with the JavaScript code, I'm very new to it so I just threw it into dirty markup to clean it up a bit.
calling JavaScript code (admin.js)
const remote = require("electron").remote;
const main = remote.require("./main.js");
var importButton = document.getElementById("importButton");
if (importButton.addEventListener) {
importButton.addEventListener("click", importPackages, false);
} else if (importButton.attachEvent) {
importButton.attachEvent("onclick", importPackages);
}
function importPackages() {
var importDirectory = document.getElementById("importDirectory").files[0].path;
var rq = require('request-promise');
var mainAddr = 'http://localhost:5000';
rq(mainAddr + "/import_packages/" + importDirectory).then(function(htmlString) {
if (htmlString != "false") {
main.logPython(htmlString);
}
}).catch(function(err) {
main.logPython('request not sent');
})
}
python code (database_handler.py)
#!/usr/bin/env python
# -*- coding: utf-8 -*-
from flask import Flask, request
import sqlite3
app = Flask(__name__)
# app.debug = True
#app.route("/")
def hello():
return "true"
#app.route("/login/<username>/<password>")
def login(username, password):
conn = sqlite3.connect("hict.sqlite")
cursor = conn.cursor()
cursor.execute("SELECT firstname, lastname, userclass FROM credentials "
"WHERE username=? AND password=? LIMIT 1;",
(username, password))
data = cursor.fetchall()
if len(data) == 0:
return "false"
else:
return "|".join(map(str, data[0]))
#app.route("/import_packages/<path:import_directory>")
def import_packages(import_directory):
return import_directory
if __name__ == "__main__":
app.run(host='127.0.0.1', port=5000)
Main JavaScript code (main.js)
const electron = require("electron");
const {app, BrowserWindow} = electron;
app.on('window-all-closed', () => {
app.quit();
})
app.on('ready', () => {
var subpy = require('child_process').spawn('python', ['./database_handler.py']);
//var subpy = require('child_process').spawn('./dist/hello.exe');
var rq = require('request-promise');
var mainAddr = 'http://localhost:5000';
var openWindow = () => {
let win = new BrowserWindow({
width: 1920,
height: 1080
});
win.maximize();
win.setMenu(null);
win.loadURL(`file://${__dirname}/login.html`);
win.on('closed', () => {
win = null;
subpy.kill('SIGINT');
})
}
var startUp = () => {
rq(mainAddr).then(function(htmlString) {
console.log('server started!');
openWindow();
}).catch(function(err) {
console.log('waiting for the server start...');
startUp();
})
}
startUp();
})
exports.logPython = (returnData) => {
console.log(returnData);
}
It looks like you are using admin.js as code in your UI, so it is in a renderer process. Your main.js looks like it is running in your project's main process. I am unsure if it actually works to require main.js in admin.js for this case. Why not try using ipc to send the data from your UI to the main process (main.js)?
Also, to eliminate an obvious answer...
console.log() in the main process (main.js) will print out in your terminal as you expect. console.log() in the renderer process (admin.js) prints out in the dev console. On windows I think you open that with by clicking on your UI and then ctrl+shift+i. So if you didn't know this, it might be working but you don't see it.
Related
I am fairly new to JS/Winappdriver.
The application I am trying to test is a windows based "Click Once" application from .Net, so I have to go to a website from IE and click "Install". This will open the application.
Once the application is running, I have no way to connect the application to perform my UI interactions while using JavaScript.
Using C#, I was looping through the processes looking for a process name, get the window handle, convert it to hex, add that as a capability and create the driver - it worked. Sample code below,
public Setup_TearDown()
{
string TopLevelWindowHandleHex = null;
IntPtr TopLevelWindowHandle = new IntPtr();
foreach (Process clsProcess in Process.GetProcesses())
{
if (clsProcess.ProcessName.StartsWith($"SomeName-{exec_pob}-{exec_env}"))
{
TopLevelWindowHandle = clsProcess.Handle;
TopLevelWindowHandleHex = clsProcess.MainWindowHandle.ToString("x");
}
}
var appOptions = new AppiumOptions();
appOptions.AddAdditionalCapability("appTopLevelWindow", TopLevelWindowHandleHex);
appOptions.AddAdditionalCapability("ms:experimental-webdriver", true);
appOptions.AddAdditionalCapability("ms:waitForAppLaunch", "25");
AppDriver = new WindowsDriver<WindowsElement>(new Uri(WinAppDriverUrl), appOptions);
AppDriver.Manage().Timeouts().ImplicitWait = TimeSpan.FromSeconds(60);
}
How do I do this in Javascript ? I can't seem to find any code examples.
Based on an example from this repo, I tried the following in JS to find the process to latch on to but without luck.
import {By2} from "selenium-appium";
// this.appWindow = this.driver.element(By2.nativeAccessibilityId('xxx'));
// this.appWindow = this.driver.element(By2.nativeXpath("//Window[starts-with(#Name,\"xxxx\")]"));
// this.appWindow = this.driver.elementByName('WindowsForms10.Window.8.app.0.13965fa_r11_ad1');
// thisappWindow = this.driver.elementByName('xxxxxxx');
async connectAppDriver(){
await this.waitForAppWindow();
var appWindow = await this.appWindow.getAttribute("NativeWindowHandle");
let hex = (Number(ewarpWindow)).toString(16);
var currentAppCapabilities =
{
"appTopLevelWindow": hex,
"platformName": "Windows",
"deviceName": "WindowsPC",
"newCommandTimeout": "120000"
}
let driverBuilder = new DriverBuilder();
await driverBuilder.stopDriver();
this.driver = await driverBuilder.createDriver(currentEwarpCapabilities);
return this.driver;
}
I keep getting this error in Winappdriver
{"status":13,"value":{"error":"unknown error","message":"An unknown error occurred in the remote end while processing the command."}}
I've also opened this ticket here.
It seems like such an easy thing to do, but I couldn't figure this one out.
Any of nodes packages I could use to get the top level window handle easily?
I am open to suggestions on how to tackle this issue while using JavaScript for Winappdriver.
Hope this helps some one out there,
Got around this by creating an exe using C# that generated hex of the app to connect based on the process name, it looks like something like this.
public string GetTopLevelWindowHandleHex()
{
string TopLevelWindowHandleHex = null;
IntPtr TopLevelWindowHandle = new IntPtr();
foreach (Process clsProcess in Process.GetProcesses())
{
if (clsProcess.ProcessName.StartsWith(_processName))
{
TopLevelWindowHandle = clsProcess.Handle;
TopLevelWindowHandleHex = clsProcess.MainWindowHandle.ToString("x");
}
}
if (!String.IsNullOrEmpty(TopLevelWindowHandleHex))
return TopLevelWindowHandleHex;
else
throw new Exception($"Process: {_processName} cannot be found");
}
Called it from JS to get the hex of the top level window handle, like this,
async getHex () {
var pathToExe =await path.join(process.cwd(), "features\\support\\ProcessUtility\\GetWindowHandleHexByProcessName.exe");
var pathToDir =await path.join(process.cwd(), "features\\support\\ProcessUtility");
const result = await execFileSync(pathToExe, [this.processName]
, {cwd: pathToDir, encoding: 'utf-8'}
, async function (err, data) {
console.log("Error: "+ err);
console.log("Data(hex): "+ data);
return JSON.stringify(data.toString());
});
return result.toString().trim();
}
Used the hex to connect to the app like this,
async connectAppDriver(hex) {
console.log(`Hex received to connect to app using hex: ${hex}`);
const currentAppCapabilities=
{
"browserName": '',
"appTopLevelWindow": hex.trim(),
"platformName": "Windows",
"deviceName": "WindowsPC",
"newCommandTimeout": "120000"
};
const appDriver = await new Builder()
.usingServer("http://localhost:4723/wd/hub")
.withCapabilities(currentAppCapabilities)
.build();
await driver.startWithWebDriver(appDriver);
return driver;
}
Solution:
In WebDriverJS (used by selenium / appium), use getDomAttribute instead of getAttribute. Took several hours to find :(
element.getAttribute("NativeWindowHandle")
POST: /session/270698D2-D93B-4E05-9FC5-3E5FBDA60ECA/execute/sync
Command not implemented: POST: /session/270698D2-D93B-4E05-9FC5-3E5FBDA60ECA/execute/sync
HTTP/1.1 501 Not Implemented
let topLevelWindowHandle = await element.getDomAttribute('NativeWindowHandle')
topLevelWindowHandle = parseInt(topLevelWindowHandle).toString(16)
GET /session/DE4C46E1-CC84-4F5D-88D2-35F56317E34D/element/42.3476754/attribute/NativeWindowHandle HTTP/1.1
HTTP/1.1 200 OK
{"sessionId":"DE4C46E1-CC84-4F5D-88D2-35F56317E34D","status":0,"value":"3476754"}
and topLevelWindowHandle have hex value :)
So what is happening is that I already have a python script that is running selenium. But what I need to do is to call a python function with arguments from node.js to the already running python script without running a new instance of the python script.
For eg. I have a python script called HelloWorld.py
import sys
def HelloPal(pal):
print("Output from Python")
print("Hello " + pal)
How can I call HelloPal("World!") to the already open instance of HelloWorld.py?
What I thought of:
Referring to GeeksforGeeks
I can send a value of a boolean callFunc = true; using node.js.
And in the HelloWorld.py :
import sys
from selenium import webdriver
driver = webdriver.Chrome()
driver.get("https://www.google.com")
callFunc = False
try:
callFunc = sys.argv[2]
except:
print("Localhost URL is not called yet!")
while callFunc is True:
HelloPal(sys.argv[1]);
def HelloPal(pal):
callFunc = False
print("Output from Python")
print("Hello " + pal)
And in the node.js script :
var express = require('express');
var app = express();
app.listen(3000, function() {
console.log('server running on port 3000');
} )
app.get('/name', callName);
function callName(req, res) {
var spawn = require("child_process").spawn;
// E.g : http://localhost:3000/name?firstname=Mike
var process = spawn('python',["./HelloWorld.py",
req.query.firstname,
true] );
process.stdout.on('data', function(data) {
res.send(data.toString());
} )
}
But after testing I have recognized that node.js is opening a new instance of HelloWorld.py.
How can I call HelloPal("World!") to the already open instance of HelloWorld.py?
noobie here..
In the course of making an electron app,
I have used electron-process package to make electron run in the background(even when all windows are closed). I noticed that, when the electron app was relaunched, the background process of the previous launch of the app remained, and this made background processes pile up with increase in the number of relaunches of the app (If the app was launched 10 times, I would have 10 background processes of the app).. Then I decided to terminate the background process of the previous launch, when I made the next launch.. I have made an attempt to do the same, which is shown below. I noticed that, the app was launched and almost instantly, the app was terminated(which I figured it out to be, the process termination part of the code being exectued later than the part which created the window and started the app. Hence terminating the current process only, instead of the process of the previous launch of the app).. Please help me in making this code run sequentially.. All comments, suggestions, explainations, advices are warmly welcomed. Thanks in advance..
'use strict';
const electron = require('electron');
const main = require('electron-process').main;
const app = electron.app; // Module to control application life.
const BrowserWindow = electron.BrowserWindow; // Module to create native browser window.
const exec = require('child_process').exec;
const exec1 = require('child_process').exec;
const fs = require('fs');
let mainWindow = null;
let mainWindow1 = null;
app.on('ready', function() {
var pidrun;
var killtask;
var read;
if(process.platform == 'win32'){
exec('tasklist /v /fo csv | findstr /i "electron.exe"', (error, stdout, stderr) => {
if (error){
console.error(`exec error: ${error}`);
return;
}
pidrun = stdout[16]+stdout[17]+stdout[18]+stdout[19];
killtask = "Taskkill /FI \"PID eq "+pidrun+"\" /F";
exec(killtask, (error, stdout, stderr) => {
if (error) {
console.error('exec error: ${error}');
return;
}
if(stdout == "INFO: No tasks running with the specified criteria."){
return;
}
});
});
}
const backgroundhtml = 'file://' + __dirname + '/background.html';
const backgroundProcessHandler = main.createBackgroundProcess(backgroundhtml);
mainWindow = new BrowserWindow({width: 1280, height: 600});
backgroundProcessHandler.addWindow(mainWindow);
mainWindow.loadURL('file://'+__dirname+'/foreground2.html');
mainWindow.loadURL('file://' + __dirname + '/foreground.html');
mainWindow.webContents.openDevTools()
});
Sounds to me like you want only a single instance of your app running at any time, Electron provides app.makeSingleInstance specifically for that purpose.
for some reason my code is compiling with no error, however, my message "Helloworld" is not displaying properly in the console. however my test message is being displayed when i press the bound key combinations. below is my set of code, index.js and main.js
This is written for node/electron.
My main.js file:
//main.js
//requirements
const electron = require('electron');
const app = require('electron').app;
const BrowserWindow = require('electron').BrowserWindow;
const remote = require('electron').remote;
const ipc = require('electron').ipcMain;
const globalShortcut = require('electron').globalShortcut;
var mainWindow = null;
//create app, instantiate window
app.on('ready', function() {
mainWindow = new BrowserWindow({
frame: false,
height: 700,
resizable: false,
width: 368
});
mainWindow.loadURL(`file://${__dirname}/app/index.html`);
//this is the icp bind
globalShortcut.register('ctrl+shift+1', function(){
console.log("test")
mainWindow.webContents.send("testBindicp" ,"HelloWorld");
});
//this is the remote bind
globalShortcut.register('ctrl+shift+2', function(){
console.log("test")
mainWindow.webContents.send("testBindicp" ,"HelloWorld");
});
});
//close the app
ipc.on('close-main-window', function () {
app.quit();
});
below is my entire index.js:
//index.js
const globalShortcut = require('electron').globalShortcut;
const remote = require('electron').remote;
const ipc = require('electron').ipcRenderer;
//testing remote render from remote bind
remote.require('./main.js');
remote.on('testBindRemote', function(event){
console.log(event + " - test - from remote index.js");
});
//testing icpRenderer from icp bind
ipc.on('testBindicp', function (event) {
console.log(event + " - test - from icpRenderer on index.js")
});
//close the app
var closeEl = document.querySelector('.close');
closeEl.addEventListener('click', function() {
ipc.send('close-main-window');
});
the problem i'm having is when i press the keyboard binds, only the console logs from the main.js file are being send to the console. the close command is still working within the rendered window, but any other commands in the index.js window are not being bound to the proper main.js elements.
if i am doing something wrong, please let me know the proper way to implement these methodologies, as the remote and icp structures seem to be confusing to me.
Thank you.
You need to have another argument passed into the listening process ipc.on of index.js file, make it something like this:
ipc.on(<channel name>, function (event, arg) {
console.log(arg + " - test - from icpRenderer on index.js");
});
for more info, visit webContents API docs
What is the best way to manipulate DOM within an electron app?
I made some tutorials from docs using ipc and webcontents with no luck
My app is so simple, I just want to use the web like a console and showing messages (render proc) comming from the results of several sync functions (main proc)
I updated the question with real code.
I'm going to put another code, more simple to see and more simple to test (I think), is real code and works (but not like I want)
When I launch electron only writes the last message.
Ok, the response is really fast and I may not see the first messsage but to discard that I put a setTimeout and a large for() loop too, to make the uppercase function takes longer
index.js
const electron = require('electron');
const {app} = electron;
const {BrowserWindow} = electron;
const ipc = require('electron').ipcMain
app.on('ready', () => {
let win = new BrowserWindow({width: 800, height: 500});
win.loadURL('file://' + __dirname + '/index.html');
// Emitted when the window is closed.
win.on('closed', () => {
win = null;
});
// Event that return the arg in uppercase
ipc.on('uppercase', function (event, arg) {
event.returnValue = arg.toUpperCase()
})
});
index.html
<html>
<body>
<div id="konsole">...</div>
<script>
const ipc = require('electron').ipcRenderer
const konsole = document.getElementById('konsole')
// Functions
let reply, message
// First MSG
reply = ipc.sendSync('uppercase', 'Hi, I'm the first msg')
message = `Uppercase message reply: ${reply}`
document.getElementById('konsole').innerHTML = message
// Second MSG
reply = ipc.sendSync('uppercase', 'Hi, I'm the second msg')
message = `Uppercase message reply: ${reply}`
document.getElementById('konsole').innerHTML = message
</script>
</body>
</html>
You can comminicate between your frond-end and back-end with webContents and IPC. Then, you'll be able to manipulate your codes in front-end with backend's directive.
For Instance (Backend to Frontend);
Your main.js is sending a message to your front-end.
mainwindow.webContents.send('foo', 'do something for me');
Your frond-end will welcome that message here;
const {ipcRenderer} = require('electron');
ipcRenderer.on('foo', (event, data) => {
alert(data); // prints 'do something for me'
});
For Instance (Frontend to Backend);
Your frond-end;
const {ipcRenderer} = require('electron');
ipcRenderer.send('bar',"I did something for you");
Your back-end;
const {ipcMain} = require('electron');
ipcMain.on('bar', (event, arg) => {
console.log(arg) // prints "I did something for you"
event.sender.send('foo', 'done') // You can also send a message like that
})
UPDATE AFTER UPDATING QUESTION;
I tried your codes on my local, It seems like working.
Could you please try it with insertAdjacentHTML instead of 'innerHTML' to just make sure or just use console.log.
Like this;
document.getElementById('konsole').insertAdjacentHTML('beforeend',message);
"result" is a reference type value. "result" always chenge value when result = funcC() or another; Try this:
$('#msg').text(result.ToString());