扩展selenium webdriver js

时间:2013-09-11 17:10:32

标签: node.js selenium

前言

我正在尝试为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的简单脚本(请注意我使用的是全局变量)。

有没有办法访问我的函数中的“当前驱动程序”?与顶部的方法不同,这不是原型方法,因此我无法访问this

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

2 个答案:

答案 0 :(得分:3)

设置继承自webdriver.WebDriver的自定义构造函数。在构造函数中,您可以访问可用于添加自定义定位器的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

答案 1 :(得分:2)

另一种选择:

使用function.prototype.bind -
创建一堆函数,这些函数就像它们的上下文是驱动程序实例一样:

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

现在你有了一个能够推荐的功能。带有一堆便捷方法的驱动程序实例,用于与特定页面上的元素进行交互 你必须记得在驱动程序实例上调用你的包装器而不是免费获得&#39;重载的构造函数上的行为。

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

因此,不是为每个测试创建一个扩展Driver的类,而是制作一些包装器来抽象您尝试完成的实际行为 - 选择元素,保存屏幕截图等 - 然后在在每页或每个功能的基础上,将css选择器或文件路径等参数保存在某处,并调用它们ala-carte。