I'm trying to write a few extensions for selenium-webdriver, like so:
var webdriver = require('selenium-webdriver');
var fs = require('fs');
var resumer = require('resumer');
webdriver.WebDriver.prototype.saveScreenshot = function(filename) {
return this.takeScreenshot().then(function(data) {
fs.writeFile(filename, data.replace(/^data:image\/png;base64,/,''), 'base64', function(err) {
if(err) throw err;
});
});
};
webdriver.WebDriver.prototype.streamScreenshot = function() {
var stream = resumer();
this.takeScreenshot().then(function(data) {
stream.queue(new Buffer(data.replace(/^data:image\/png;base64,/,''), 'base64')).end();
});
return stream;
};
module.exports = webdriver;
And then I just include my extended webdriver, instead of the official one:
var webdriver = require('./webdriver.ext');
I think that's the proper way to extend things in Node JS.
The issue I'm having is with adding a custom Locator Strategy. The strategies look like this in the source:
/**
* Factory methods for the supported locator strategies.
* @type {Object.<function(string):!webdriver.Locator>}
*/
webdriver.Locator.Strategy = {
'className': webdriver.Locator.factory_('class name'),
'class name': webdriver.Locator.factory_('class name'),
'css': webdriver.Locator.factory_('css selector'),
'id': webdriver.Locator.factory_('id'),
'js': webdriver.Locator.factory_('js'),
'linkText': webdriver.Locator.factory_('link text'),
'link text': webdriver.Locator.factory_('link text'),
'name': webdriver.Locator.factory_('name'),
'partialLinkText': webdriver.Locator.factory_('partial link text'),
'partial link text': webdriver.Locator.factory_('partial link text'),
'tagName': webdriver.Locator.factory_('tag name'),
'tag name': webdriver.Locator.factory_('tag name'),
'xpath': webdriver.Locator.factory_('xpath')
};
goog.exportSymbol('By', webdriver.Locator.Strategy);
I'm trying to add a new one by injecting it into that object:
webdriver.By.sizzle = function(selector) {
driver.executeScript("return typeof Sizzle==='undefined'").then(function(noSizzle) {
if(noSizzle) driver.executeScript(fs.readFileSync('sizzle.min.js', {encoding: 'utf8'}));
});
return new webdriver.By.js("return Sizzle("+JSON.stringify(selector)+")[0]");
};
This actually works fine for simple scripts where driver
is defined (notice that I'm using a global variable).
Is there a way to access the "current driver" inside my function? Unlike the methods at the top, this isn't a prototypical method, so I don't have access to this
.
I don't know how those factory_
s work; I was just guessing that I could inject a function directly.
public class T3EmployeeMaintenance extends Setup{ //public static WebDriver driver = new FirefoxDriver(); //no need of this now @BeforeClass public void invokeBrowser() { //... Show activity on this post. You have to finish class at the end. in your html simply add at the end of each class you declared.
Selenium WebDriver with JavaScript is a favorable combination to perform automated UI testing of applications. JavaScript offers efficiency with its well-built and structured patterns and functions, making the script more compact. It offers security and is well supported by a large community of developers.
User extension is created, when Selenium IDE is not fulfilling the current requirement. To create user extension it is required to add javascript to selenium's object prototype. After creation of extension, it is required to add it in Selenium IDE and restart IDE.
Setup a Custom constructor that inherits from webdriver.WebDriver
. Inside the constructor you have access to the this
object which you can use to add your custom locator
var util = require('util');
var webdriver = require('selenium-webdriver');
var WebDriver = webdriver.WebDriver
var fs = require('fs');
var resumer = require('resumer');
function CustomDriver() {
WebDriver.call(this);
// append your strategy here using the "this" object
this...
}
util.inherits(WebDriver, CustomDriver);
CustomDriver.prototype.saveScreenshot = function(filename) {
return this.takeScreenshot().then(function(data) {
fs.writeFile(filename, data.replace(/^data:image\/png;base64,/, ''), 'base64', function(err) {
if (err) throw err;
});
});
};
CustomerDriver.prototype.streamScreenshot = function() {
var stream = resumer();
this.takeScreenshot().then(function(data) {
stream.queue(new Buffer(data.replace(/^data:image\/png;base64,/, ''), 'base64')).end();
});
return stream;
};
module.exports = CustomDriver
Another option:
Use function.prototype.bind -
Create a bunch of functions that are written as if their this context were a driver instance:
function myCustomMethod(){
this.seleniumDriverMethodOfSomeSort()
//etc.
}
And then export a single wrapping function to bind them onto the instance and assign them to method names:
function WrapDriverInstance(driver){
driver.myCustomMethod = myCustomMethod.bind(driver)
}
You could even stick all your methods in an array like [{method : methodfunction, name : 'methodName'}]
and then do this:
function bindAllMyMethodsAtOnce(driver){
methodArray.forEach(item=>{
driver[item.name] = item.method.bind(driver)
})
}
Or get really crazy and take advantage of the fact that .bind()
lets you do partial function application:
function customClicker(selector){
return this.findElement(By.css(selector)).click()
}
function customSendKeys(selector,keys){
return this.findElement(By.css(selector)).sendKeys(keys)
}
var arrayOfElementSelections = [{elementCSS : 'div.myclass', name : 'boxOStuff'}] //etc
function wrapCustomActions(driver){
arrayOfElementSelections.forEach(element=>{
driver[element.name+'Clicker'] = customClicker.bind(driver,element.elementCSS)
driver[element.name+'Keyer'] = customSendKeys.bind(driver,element.elementCSS)
})
}
And now you have a function that can 'prime' a driver instance with a bunch of convenience methods for interacting with elements on a specific page.
You have to remember to call your wrapper on the driver instance instead of getting 'free' behavior on your overloaded constructor.
But, because of the partial application nature of .bind()
, you can define more general purpose utility methods and specify their behaviors when you wrap them.
So instead of making a class to extend Driver for each test, you make a few wrappers that abstract the actual behavior you're trying to accomplish - select an element, save a screenshot, etc. - and then on a per page or per feature basis, have the parameters like css selectors or filepaths saved somewhere, and invoke them ala-carte.
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