前言
我正在尝试为 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;
然后我只包含我的扩展网络驱动程序,而不是官方的:
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_
是如何工作的;我只是猜测我可以直接注入一个函数。
设置一个继承自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;
});
});
};
CustomDriver.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
另一种选择:
使用 function.prototype.bind -
创建一堆函数,这些函数的编写就好像它们的 this 上下文是驱动程序实例一样:
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()
的部分应用性质,您可以定义更通用的实用程序方法,并在包装它们时指定它们的行为。
因此,您不是创建一个类来为每个测试扩展 Driver,而是创建一些包装器来抽象您尝试完成的实际行为 - 选择一个元素,保存屏幕截图等 - 然后在每页或每个功能的基础上,将 css 选择器或文件路径等参数保存在某处,并点菜调用它们。