Webdriver js selenium extension

Introduction

I'm trying to write some extensions to the selenium-webdriver , for example:

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 turn on my advanced web editor instead of the official one:

 var webdriver = require('./webdriver.ext'); 

I think the correct way to extend objects in Node JS.

Problem

The problem I am facing is adding a custom locator strategy. Strategies are as follows:

 /** * 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 am trying to add a new one by injecting it into this 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 is really great for simple scripts where driver defined (note that I am using a global variable).

Is there a way to access the "current driver" inside my function? In contrast to the methods above, this is not the prototype method, so I do not have access to this .

I do not know how those factory_ work; I just guessed that I could directly enter the function.

+6
source share
2 answers

Configure a custom constructor that inherits from webdriver.WebDriver . Inside the constructor, you have access to the this object, which you can use to add a 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 
+3
source

Another option:

Use .prototype.bind function -
Create a bunch of functions that are written as if this context was an instance of the driver:

 function myCustomMethod(){ this.seleniumDriverMethodOfSomeSort() //etc. } 

And then export one wrapping function to associate them with the instance and assign them method names:

 function WrapDriverInstance(driver){ driver.myCustomMethod = myCustomMethod.bind(driver) } 

You can even use all your methods in an array like [{method : methodfunction, name : 'methodName'}] , and then do the following:

 function bindAllMyMethodsAtOnce(driver){ methodArray.forEach(item=>{ driver[item.name] = item.method.bind(driver) }) } 

Or be really crazy and take advantage of the fact that .bind() allows you to run the application with a partial function:

 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 β€œfuel” the driver instance using many convenient methods for interacting with elements on a specific page.
You should remember that you invoke your shell on the driver instance, and do not get the β€œfree” behavior on your overloaded constructor.

But due to the partial nature of the .bind() application, you can define more general methods for the general-purpose utility and indicate their behavior when porting them.

Therefore, instead of creating a class for extending the driver for each test, you make several wrappers that abstract the actual behavior that you are trying to perform - select an element, save a screenshot, etc. - and then on the page or on each functional basis, parameters, such as the css selector or file paths, are stored somewhere, and call them ala-carte.

+1
source

Source: https://habr.com/ru/post/953645/


All Articles