扩展seleniumwebdriver js

前言

我试图写一些selenium-webdriver的扩展,如下所示:

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; 

然后我只包括我的扩展webdriver,而不是官方的:

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

我认为这是在Node JS中扩展事物的正确方法。

问题

我遇到的问题是添加自定义定位器策略。 这些策略在源代码中是这样的:

 /** * 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); 

我试图添加一个新的注入到该对象:

 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]"); }; 

对于定义了driver简单脚本,这实际上可以正常工作(请注意,我正在使用全局variables)。

有没有办法访问我的function内的“当前的驱动程序”? 不像顶部的方法,这不是一个典型的方法,所以我没有访问到this

我不知道这些factory_是如何工作的。 我只是猜测,我可以直接注入一个函数。

设置从webdriver.WebDriverinheritance的自定义构造函数。 在构造函数中,你可以访问this对象,你可以使用它来添加自定义的定位器

 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 

另外一个select:

使用function.prototype.bind –
创build一堆函数,就好像它们的上下文是一个驱动程序实例一样:

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

然后导出一个单独的包装函数,将它们绑定到实例上,并将它们分配给方法名称:

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

你甚至可以把所有的方法都放在像[{method : methodfunction, name : 'methodName'}]这样的数组中,然后执行下面的操作:

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

或者变得非常疯狂,利用.bind()让你做部分函数应用的事实:

 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) }) } 

现在你有一个函数可以用一系列便捷的方法来“驱动”一个驱动程序实例,以便与特定页面上的元素进行交互。
你必须记得在驱动实例上调用你的包装,而不是在你的重载构造函数中获得“自由”的行为。

但是,由于.bind()的部分应用性质,您可以定义更多的通用实用程序方法,并在包装​​它们时指定它们的行为。

因此,不是每个testing都要为一个类来扩展驱动程序,而是使用一些包装来抽象出您要实现的实际行为 – select一个元素,保存屏幕截图等等,然后在每个页面或每个function上基础上,像cssselect器或文件path保存在某处的参数,并调用他们点菜。