Detect user key/mouse in Python Selenium - javascript

I'm using Selenium Browser for day to day browsing, and I'd like to fire some code when I press some keys on any page. At first I thought I can just load javascript on every page that registers keys/mouse input, but I'd actually really prefer to have some python list available with past keys/mouse clicks, e.g. my key example in javascript:
var myhistory = []
document.addEventListener("keydown", keyDownTextField, false);
function keyDownTextField(e) {
var keyCode = e.keyCode;
myhistory.push(keyCode)
}
Is there any way to do this in pure Python/Selenium?

What I would try:
Execute a javascript that registers at the document body
<body onkeyup="my_javasctipt_keyup()" and onkeydown="my_javasctipt_keydown()">
using browser.execute_script. (partially solved, see question)
Save the key up and keydown events in a variable in javascript. (solved, see question)
use browser.execute_script to return the variables.
What I am uncertain about:
The return value of browser.execute_script may return json serializable objects or strings only
keyup and keydown in body may not work if they are used in child elements that define their own event listeners
Hopefully this is of help. If any code results form this I would be interested in knowing.
This code is what I feel should work:
from selenium import webdriver
browser = webdriver.Firefox()
browser.execute_script("""var myhistory = []
document.addEventListener("keydown", keyDownTextField, false);
function keyDownTextField(e) {
var keyCode = e.keyCode;
myhistory.push(keyCode)
}""")
def get_history():
return browser.execute_script("myhistory")
# now wait for a while and type on the browser
import time; time.sleep(5000)
print("keys:", get_history())
The point is that the code of selenium can never run at the same time as the browser handles keyboard input. As such, events need to be handled in javascript, the result saved, e.g. in an array and then, when selenium is asked, the array is returned to Python.

boppreh/keyboard would let you do that.
You install it. pip install keyboard
You import it. import keyboard
You use it. keyboard.add_hotkey('left', print, args=['You pressed the left arrow key'])
Then you disable it. keyboard.remove_all_hotkeys()

Well, in that case you had to choose the right tool for the job, i advice puppeteer a web-automation family instrument a pure-made JS, which can easily interact with the browser ( from js to js ) and catch events directly from the other side without any mediation.
Yet with selenium you can still achieve this transitively without messing too much with the pages's code or overcharging it with unnecessary tasks, also reloading the page content resets all its variables, which means it's lossy approach. The best closest way is to set an eventhandler internally and directly catch it from outside using Runtime.evaluate instead because it doesn't affect the page content and specifically it sticks to the function until it yields something using promise calls, it's better away than probing around some global variable over and over which is seen a bad practise see here.
myhistory = []
evt_handler = """
new Promise((rs,rj) => window.onkeydown= e => rs(e.keyCode) )
"""
def waitforclick():
try:
myhistory.append(browser.execute_cdp_cmd('Runtime.evaluate', {'expression': evt_handler, 'awaitPromise': True,'returnByValue': True})['result']['value'])
except:
waitforclick()
To avoid locking out the cpu you need to fork a thread in parallel.
from threading import Timer
t = Timer(0.0, waitforclick)
then t.start() instead of waitforclick().
Also you can use timeout if you want to reject the promise with a zero value after some time.

Related

How to store/stash JavaScript event and reuse it later?

From https://developers.google.com/web/fundamentals/app-install-banners/#trigger-m68
let deferredPrompt;
window.addEventListener('beforeinstallprompt', (e) => {
e.preventDefault();
// Stash the event so it can be triggered later.
deferredPrompt = e;
});
This code is fine, but I want to trigger the stashed event later, in a different place. To perform that, I need to store an event not just in a variable, but somewhere else.
The question: how can an event be stored with its methods?
I tried Local Storage with serialization/deserialization of an object:
> localStorage.setItem('stashed-event', JSON.stringify(e))
>
> JSON.parse(localStorage.getItem('stashed-event'))
But this approach doesn't work as expected, because it's storing only key-values and losing all event methods.
You cannot store an event in this manner. You want to store an object. Only serializable properties are storable for such an object. Functions are not serializable in JavaScript. Functions are not serializable in many languages.
Fundamentally this is basically because when you deserialize an object, its signature can change. If you have ever programmed in java, this is similar to a deserialization error when reading in a serialized object and attempting to reconstruct an object. Because the body of a method function of an object can change in between the time the object is written to some storage and then later read, methods are not serializable. This is because when you serialize an object, it does not serialize its interface definition where methods are defined. It just stores data.
Same reason when you serialize to a json string, it drops the functions.
Instead of storing an event, store the useful information from the event in an object (or let things be implicitly dropped by stringify and use the event directly).
Which method of storage you use just depends on things not mentioned in your question. Such as how long it should be stored, whether it should be available outside of your site's origin, how much data will typically be stored, whether there is more than one object to store, etc. Based on the limited information provided in your question, you are probably fine just using either localStorage or an in memory array.
If you find the need to store hundreds of objects then indexedDB would begin to be more appropriate. But just choosing a different storage medium will have no effect whatsoever on whether you can store functions. You cannot store functions.
There have been loads of talk around this as soon as I/O 2018 mentioned about handling of A2HS event being developer driven from now onwards. This is also captured in the official doc and inspired from it, there is a beautiful article explaining thoroughly how to achieve exactly this scenario. While I'd suggest to go through the complete article for proper understanding of the updated dynamics around the A2HS flow, feel free to jump onto the "The New Add To Homescreen Flow" section for your requirement.
In a nutshell, follow the following steps:
Create a variable outside the scope of the beforeinstallprompt event handler.
Save a reference to the beforeinstallprompt event object in the above handler.
Use this later to trigger the add to homescreen prompt on demand.
The article have the complete code snippets which you can refer/reuse.
Edit: I read your question once again and realized one important aspect you might be specifically looking for, viz., using it "somewhere else". If this means you are referring to using it on a different page, then my suggestion would be to go for storing the event object in:
IndexedDB which is a collection of "object stores" which you can just drop objects into. Disadvantage - Can have browser compatibility restrictions. Also, can result in large amount of nested callbacks.
Or you can choose to use the "in process cache" (heap memory of your application) which doesn't require serializing either. Disadvantage - This cannot be shared across multiple servers though.
Other than this, I cannot foresee a con free solution at the moment. But will try to figure it out and possibly update the thread.
After reading your question a few times, and the answers another few,
The question: how can any javascript Object be stored with its methods?
The answer: there is no how.
However,
Josh properly explained you can extract and store all the serializable properties, say data, from your event.
I will just add you can create an event with somehow that same data later anywhere, this new event will have all the methods any Event has, but by now probably none of use.
Obviously, even serialized in your data, properties like timeStamp, isTrusted, etc... will be overriden at creating the new event.
What you just miss / need is an EventTarget, the value of the event.target property,
the reference which is lost forever when document.body unloads forever, or when serializing the event Object.
But if it is still alive, or if you know what event.target should be, like a DOM Element or any Object you can reference, from wherever you recreate the event (where?), just dispatch your event to that object, if it listens to that event.type,
your brand new event should be at least heard.
Simple example from MDN EventTarget, or see EventTarget.dispatchEvent
As a comment over the extensive answer by cegfault: eval, and text source code... could be <script> text source code </script>... should your script produces a (String) script. If not you ´d probably better go further backwards to where did your script creates the unserializable things that appear in your event, and think about recreating those things, not the event.
TL;DR to accomplish what you are doing, you have three options:
Store a reference to the event in a global value (which is what most tutorials - like your referenced youtube video - will recommend you do). This requires the event to run in the same context (ie web page) as when you store the reference
When you store the reference to the event in localStorage (such as by name or a key/value look up), on the page/context where you want to execute the event, make sure the appropriate functions and libraries are loaded before executing the event
[strongly NOT recommended] Store the javascript source code in your storate and eval() it later [again, please don't do this]
As mentioned by #Josh and #SaurabhRajpal, what you are asking for, strictly speaking, is not possible in JavaScript. What you are doing with JSON.stringify(e) will probably return undefined or null, as the MDN documentation for JSON.stringify says:
If undefined, a Function, or a Symbol is encountered during conversion it is either omitted (when it is found in an object) or censored to null (when it is found in an array). JSON.stringify can also just return undefined when passing in "pure" values like JSON.stringify(function(){}) or JSON.stringify(undefined).
In short, there is no way to store a single function into localStorage (or any other offline storage). To explain why this is not possible, see this example:
function foo() {
console.log("a")
}
function bar() {
return foo()
}
How can you store bar() for later usage? In order to store bar, you would also have to store foo(). This becomes much more complicated when you consider referencing a function which is in, or uses, a large library (like jQuery, underscore, D3, charting libraries, etc). Keep in mind your computer has already parsed the source code down into binary, and as such won't easily know how to read the function for every possible if, for, and switch statements to ensure all possible correlated functions and libraries are saved.
If you really wanted to do this, you would have to write your own javascript parser, and you really don't want to do that!
So what are your options? First, do everything on the same page, and store the reference to the event in a global value (the youtube video you link to in a comment is using this method).
Your second option is to use a reference to the event (not the event itself), and make sure the source code for that reference is use later. For (html) example:
// on page #1
<script src="path/to/my/js/library.js"></script>
...
<script>
window.addEventListener('beforeinstallprompt', (e) => {
e.preventDefault()
localStorage.setItem('stashed-event', 'before-install')
})
</script>
// later, on page #2:
<script src="path/to/my/js/library.js"></script>
...
<script>
var evt = localStorage.setItem('stashed-event', 'before-install')
if(evt == 'before-install') {
dosomething() // which would be a function in path/to/my/js/library.js
}
// another option here would be to define window listeners for all possible events
// in your library.js file, and then simply build and trigger the event here. for
// more about this, see: this link:
// https://developer.mozilla.org/en-US/docs/Web/Guide/Events/Creating_and_triggering_events
</script>
Finally, you can store javascript source code and then eval() it later. Please, please, please do NOT do this. It's bad practice and can lead to very evil things. But, if you insist:
// on page #1
window.addEventListener('beforeinstallprompt', (e) => {
e.preventDefault()
SomeAjaxFunction("path/to/my/js/library.js", function(responseText) {
localStorage.setItem('stashed-event', {
name: 'before-install',
call: 'beforeInstallFunction()',
src: responseText
})
})
})
// later, on page #2:
var evt = localStorage.setItem('stashed-event', 'before-install')
if(evt) {
console.log("triggering event " + evt.name)
eval(evt.src)
eval(evt.call)
}
Like I said, this is a really bad idea, but it's an option.
IMHO, I think you're trying to avoid including a library or source code in a later page/app/whatever, and javascript just does not work this way. It's best to pass around references in-memory, and only use key/value storage for names. Everything else is a type of coding gymnastics to avoid simply including your source code in the places it needs ot be included.
You can create a global constant and update it when ever event changes rather than serializing it and de-serializing which is a costly processes. SO this is how you can do it - You can create a window instance and clone the event in the window object so that it wont mutate.(Note this wont won't work across tabs)
window.addEventListener('click', (e) => {
e.preventDefault();
window.deferredPrompt = Object.assign(e);//Don't mutate
});
let someOtherMethod = ()=>{
console.log(window.deferredPrompt)
}
window.setInterval(someOtherMethod, 5000);
Try clicking after 5 seconds in the last window and check after 5 seconds
Here is a simple but successful solution.
The idea is to capture the event in a variable and only fire it when signaled by another window of the same origin (domain etc).
The solution uses localStorage methods as the signaling semaphore.
Here is the code I used. I have tested it successfully in Chrome, both mobile & desktop.
//In event handling window
//Register the ServiceWorker
if('serviceWorker' in navigator) {
navigator.serviceWorker.register('sw.js');
};
//Capture beforeInstall event
window.addEventListener('beforeinstallprompt', function(event){
event.preventDefault();
window.deferredPrompt = event;
return false;
})
//Wait for signal
window.onstorage = event => {
if (event.key === 'installprompt') {
//Fire the event when signaled.
window.deferredPrompt.prompt();
// Discard event
window.deferredPrompt = null;
//Discard storage item
localStorage.removeItem('installprompt');
}
}
//In a different window or tab from the same origin fire the event when ready.
localStorage.setItem('installprompt', 'whatever');
Please see if this helps.
Defining an event listener for 'beforeinstallprompt' event
window.addEventListener('beforeinstallprompt', (e) => {
e.preventDefault();
//do all the stufff
console.log('Event triggered');
});
When you want to dispatch the event manually.
Create a new event variable.
var myevent = new Event('beforeinstallprompt');
window.dispatchEvent(myevent);
Outputs 'Event triggered' in the console.

Is there a node way to detect if a network interface comes online?

I have looked at the os module and the ip module but those are really good at telling me the current ip address of the system not if a new one comes online or goes offline. I know I can accomplish this problem using udev rules (I'm on Ubuntu) but I was hoping for a way to do this using only node. How would I go about discovering if a network interface is started?
You could always setup a listener using process.nextTick and see if the set of interfaces has changed since last time. If so, send out the updates to any listeners.
'use strict';
let os = require('os');
// Track the listeners and provide a way of adding a new one
// You would probably want to put this into some kind of module
// so it can be used in various places
let listeners = [];
function onChange(f) {
if (listeners.indexOf(f) === -1) {
listeners.push(f);
}
}
let oldInterfaces = [];
process.nextTick(function checkInterfaces() {
let interfaces = os.networkInterfaces();
// Quick and dirty way of checking for differences
// Not very efficient
if (JSON.stringify(interfaces) !== JSON.stringify(oldInterfaces)) {
listeners.forEach((f) => f(interfaces));
oldInterfaces = interfaces;
}
// Continue to check again on the next tick
process.nextTick(checkInterfaces);
});
// Print out the current interfaces whenever there's a change
onChange(console.log.bind(console));
Unfortunately, there is no event bases way to do this in node. The solution we came up with was to use UDEV to generate events when a device goes offline and comes online.
In short, you can use npm package network-interfaces-listener. It will send data every time an(or all) interface becomes online, or offline. Not a perfect solution, but it will do.
I tried the answer of Mike Cluck. The problem I faced with nextTick() approach is that it gets called in the end.
The package creates a separate thread(or worker in nodejs). The worker has setInterval() which calls the passed callback function after every second. The callback compares previous second data, if it does not match, then change has happened, and it calls listener.
Note(edit): The package mentioned is created by me in order to solve the problem.

JavaScript: How to clear my custom keyboard mappings and use the default ones

In my web application I define my own keyboard event handler and override some default key mappings:
document.onkeydown = function(eventParam)
{
var keycode;
keycode = eventParam.which;
// detect ESC key
if (keycode == 27)
{
//close the window
LightboxFileInfo.close();
}
return true;
};
However, at another point in my web application, i want to clear my custom key mappings and to use again the default ones. This is my problem. Could you please advise how to do just that? How to clear all my keyboard mappings and again to use the default ones without restarting the web-application?
Basically, my web application has numerous windows. The windows are lightboxes. For each window/lightbox I use different functions for the same keys. Remember, that I have a web application, not a website. It means, that everything occurs in one webpage/JavaScript Document, where I display different windows through the already mentioned lightboxes and not as different .html/.php files.
If I need to save the default keyboard mappings before changing them, then fine. Would you please advise how to do that? My JavaScript knowledge simply ends here. Of course, I look for the simplest solution.
I am looking for a solution using:
JavaScript
I do not use jQuery, just plain JavaScript.
Besides that, I use:
HTML 5
CSS 3
PHP 5.5.8
MySQL 5.6.15
Apache 2.4.7
Thank you in advance
Since you aren't disabling the default behavior with eventParam.preventDefault();, the normal defaults should work too in the first place. But to disable your own functionality from being called, you can just override the handler function with empty one
document.onkeydown = function() { };

Overwrite a javascript variable from webdriver

I'm not sure whether is there any solution for my issue but, unfortunately I haven't found any article or information about it.
The situation is the following. We have a site which uses jQuery heavily and there is a service which refreshes a part of the site in every 5th or 10th second. Due to this half of the time I got this error from WebDriver:
"Element not found in the cache - perhaps the page has changed since it was looked up"
According to the Internet I got this error when the DOM tree has changed between the moment when the WebElement has been initialized and when I want to use it to perform, for example, a click event.
According to our developers our jquery solution has a variable which controls whether the page will be refreshed or not. So, to solve my issue I have to overwrite this variable. I have to overwrite this variable in that way the jQuery will be able to understand it - I mean in the same instance if this definition is proper in this context.
So, I would like to ask whether is possible or not? If so, than I would like to ask a small example.
Thanks in advance!
András
I can only agree with Aleh.
Using JavaScriptExecutor is the only way to handle such issues.
I had a problem with jQuery jNice library and couldn't find any other solution.
Here is an example in Java for filling a text field:
JavascriptExecutor js = (JavascriptExecutor) webDriver;
js.executeScript("document.getElementsByName('<field_name_gets_here>')[0].value='" + your_value + "'");
If the JavaScript variable you mentioned is global, then yes - you can overwrite it by executing JavaScript from your Selenium. For example, if that variable is called doRefresh, you can overwrite it by executing JS like this: doRefresh = false; from Selenium.
If that variable is not global, the above won't work. However, it sounds like the elements in question might be dynamically loaded via ajax - in which case the xhr object is global and you can access it instead.
So, first you can make a local copy of the xhr object and then overwrite the original (effectively disabling it) by executing JavaScript from Selenium:
// create a copy of the xhr object for later use
var xhrHolder = window.XMLHttpRequest;
// overwrite the original object to disable it
window.XMLHttpRequest = {};
Then find your element via Selenium as you would normally. And proceed with your test.
When finished, you can put the xhr object back in place (so the page can continue refreshing and doing ajax) by executing JavaScript from Selenium:
// put the xhr object back
window.XMLHttpRequest = xhrHolder;
You can try my approach - I created my own wrapper for situations where page might be loading. The below part of code tries to search element in the loop, for three seconds (configurable). BTW the driver variable below is instance of WebDriver
private WebElement foundElement;
public WebElement find(By by){
for (int milis=0; milis<3000; milis = milis+200){
try{
foundElement = driver.findElement(by);
}catch (Exception e){
try {
Thread.sleep(200);
} catch (InterruptedException e1) {
e1.printStackTrace(); //To change body of catch statement use File | Settings | File Templates.
}
}
}
return foundElement;
}
And later in the code:
WebElement submitButton = find(By.id("submitNewBid"));
submitButton.click();
I believe it is possible. Example for c#:
((IJavaScriptExecutor)driver).ExecuteScript("window.$('.class').data('var') = 0;")

ResetIsDirty on FCKeditor immediately after SetHTML -- Concurrency/timing issue with Javascript

I am using IsDirty to check for changes in my FCKeditor. Unfortunately, it seems that its functions are asynchronous.
Here is the failing code:
var txtObj = $('activities').EstActText1.id;
var oEditor = FCKeditorAPI.GetInstance(txtObj);
oEditor.SetHTML(jsonObj.DATA.ESTACTTEXT1.toString());
oEditor.ResetIsDirty();
The problem is, SetHTML does not take effect immediately (if you put a check right afterward using GetHTML, it will return what was previously in the textarea). Thus, ResetIsDirty will run, THEN the HTML will actually be changed, and the dirty flag will be set again.
Is there any way I can force the SetHTML call to complete before continuing? If not, is there any way (besides a ghetto setTimeout call that will add latency and not necessarily always work) to make sure that the ResetIsDirty will actually take effect after the HTML is changed?
I still would be interested in a direct answer, but I'm leaning on the side of that not being very feasible. It would require something of a sleep function, but JavaScript doesn't go toward that realm.
However, what you're supposed to do is handle the FCKeditor_OnComplete event:
function FCKeditor_OnComplete( editorInstance )
{
editorInstance.Events.AttachEvent( 'OnAfterSetHTML', function(){
editorInstance.ResetIsDirty(); // clean flag to avoid having to save
} ) ;
editorInstance.ResetIsDirty(); //clean flag upon initial load as well
}
This was placed in a script tag with defer set.
I know, the question is not actual, but it may be useful to someone. Try to use:
ckeditor = CKEDITOR.instances['Editor_ID'];
ckeditor.setData(lyr_data.lyrics,function(){
ckeditor.updateElement();
ckeditor.resetDirty();
});

Categories