扩展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.WebDriver
inheritance的自定义构造函数。 在构造函数中,你可以访问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保存在某处的参数,并调用他们点菜。