多个浏览器和页面对象模式 [英] Multiple browsers and the Page Object pattern
问题描述
我们使用页面对象模式来组织我们的内部AngularJS应用程序测试。
以下是我们的示例页面对象:
var LoginPage = function(){
this.username = element(by.id(username));
this.password = element(by.id(password));
this.loginButton = element(by.id(submit));
}
module.exports = LoginPage;
在单浏览器测试中,很清楚如何使用它:
var LoginPage = require(./../ po / login.po.js);
describe(登录功能,function(){
var scope = {};
beforeEach(function(){
browser.get (/#login);
scope.page = new LoginPage();
});
it(应该成功登录用户) ,function(){
scope.page.username.clear();
scope.page.username.sendKeys(login);
scope.page.password.sendKeys(password);
scope.page.loginButton.click();
//断言我们登录
});
});
但是,当涉及多个浏览器实例化并且需要在两者之间切换的测试时它们在单个测试中,不清楚如何在多个浏览器中使用相同的页面对象:
describe(登录功能 ,function(){
var scope = {};
beforeEach(function(){
browser.get(/#login);
scope.page = new LoginPage();
});
it(应该警告有打开的会话,function(){
scope.page .username.clear();
scope.page.username.sendKeys(login);
scope.page.password.sendKeys(password);
scope.page.loginButton.click() ;
//断言我们登录
//启动另一个浏览器并登录
var browser2 = browser.forkNewDriverInstance();
//问题在这里 - scope.page.username.clear()将是appli ed到主浏览器
});
});
问题:
在我们分叉了一个新的浏览器之后,我们如何使用相同的Page Object字段和函数,但是应用于新实例化的浏览器(在这种情况下 browser2
)? / p>
换句话说,这里的所有元素()
调用都将应用于浏览器
,但需要应用于 browser2
。我们如何切换上下文?
思考:
-
这里可能的一种方法是重新定义全局
元素= browser2。元素
处于browser2
的上下文中。这种方法的问题是我们在页面对象函数中也有browser.wait()
调用。这意味着还应该设置browser = browser2
。在这种情况下,我们需要记住临时变量中的浏览器
全局对象,并在切换回主浏览器后恢复它
context .. -
另一种可能的方法是将浏览器实例传递给页面对象,例如:
var LoginPage = function(browserInstance){
browser = browserInstance? browserInstance:浏览器;
var element = browser.element;
// ...
}
但这会可能需要更改我们拥有的每个页面对象..
希望问题很清楚 - 让我知道如果需要澄清的话。
看看我的解决方案。我简化了示例,但我们在当前项目中使用此方法。我的应用程序有两个用户权限类型的页面,我需要在两个浏览器中同时执行一些复杂的操作。我希望这可能会给你一些新的,更好的方法!
use strict;
//在配置中,您应该声明全局浏览器角色。我只有2个角色 - 所以我创建2个全局实例
// onPrepare()函数中的某个地方
global.admin = browser;
admin.admin = true;
global.guest = browser.forkNewDriverInstance();
guest.guest = true;
//请注意,默认浏览器为'admin'示例:
// let someElement = $('someElement'); //这将尝试在管理员浏览器中找到。
class BasePage {
//其他共享逻辑也可以在这里添加。
构造函数(browser = admin){
//简化示例
this._browser = browser
}
}
class HomePage extends BasePage {
//您不会直接创建此对象。相反,你应该使用.getPageFor(浏览器)
构造函数(浏览器){
super(浏览器);
this.rightToolbar = ToolbarFragment.getFragmentFor(this._browser);
this.chat = ChatFragment.getFragmentFor(this._browser);
this.someOtherNiceButton = this._browser。$('button.menu');
}
//这个函数依赖于我们为onPrepare()中的浏览器实例修补的参数;
static getPageFor(浏览器){
if(browser.guest)返回新的GuestHomePage(浏览器);
else if(browser.admin)返回新的AdminHomePage(浏览器);
}
openProfileMenu(){
let menu = ProfileMenuFragment.getFragmentFor(this._browser);
this.someOtherNiceButton.click();
返回菜单;
}
}
class GuestHomePage extends RoomPage {
constructor(browser){
super(browser);
}
//某些功能仅适用于访客
login(){
//在这种情况下将是'访客'浏览器。
this._browser。$('input.login')。sendKeys('sdkfj'); // blabla
this._browser。$('input.pass')。sendKeys('2345'); // blabla
this._browser。$('button.login')。click();
}
}
类AdminHomePage扩展RoomPage {
构造函数(浏览器){
super(浏览器);
}
acceptGuest(){
let acceptGuestButton = this._browser。$('。request-admission .control-btn.admit-user');
this._browser.wait(EC.elementToBeClickable(acceptGuestButton),10000,
'管理员应该能够看到并点击接受来宾按钮。'+
'确保来宾正在尝试连接到页面');
acceptGuestButton.click();
//直接调用浏览器,因为我们需要执行复杂的操作。举个例子。
guest.wait(EC.visibilityOf(guest。$('。central-content')),10000,'访客应该被删除到页面');
}
}
//然后在你的测试中
让guestHomePage = HomePage.getPageFor(guest);
guestHomePage.login();
let adminHomePage = HomePage.getPageFor(admin);
adminHomePage.acceptGuest();
adminHomePage.openProfileMenu();
guestHomePage.openProfileMenu();
We are using the Page Object pattern to organize our internal AngularJS application tests.
Here is an example page object we have:
var LoginPage = function () {
this.username = element(by.id("username"));
this.password = element(by.id("password"));
this.loginButton = element(by.id("submit"));
}
module.exports = LoginPage;
In a single-browser test, it is quite clear how to use it:
var LoginPage = require("./../po/login.po.js");
describe("Login functionality", function () {
var scope = {};
beforeEach(function () {
browser.get("/#login");
scope.page = new LoginPage();
});
it("should successfully log in a user", function () {
scope.page.username.clear();
scope.page.username.sendKeys(login);
scope.page.password.sendKeys(password);
scope.page.loginButton.click();
// assert we are logged in
});
});
But, when it comes to a test when multiple browsers are instantiated and there is the need to switch between them in a single test, it is becoming unclear how to use the same page object with multiple browsers:
describe("Login functionality", function () {
var scope = {};
beforeEach(function () {
browser.get("/#login");
scope.page = new LoginPage();
});
it("should warn there is an opened session", function () {
scope.page.username.clear();
scope.page.username.sendKeys(login);
scope.page.password.sendKeys(password);
scope.page.loginButton.click();
// assert we are logged in
// fire up a different browser and log in
var browser2 = browser.forkNewDriverInstance();
// the problem is here - scope.page.username.clear() would be applied to the main "browser"
});
});
Problem:
After we forked a new browser, how can we use the same Page Object fields and functions, but applied to a newly instantiated browser (browser2
in this case)?
In other words, all element()
calls here would be applied to browser
, but needed to be applied to browser2
. How can we switch the context?
Thoughts:
one possible approach here would be to redefine the global
element = browser2.element
temporarily while being in the context ofbrowser2
. The problem with this approach is that we also havebrowser.wait()
calls inside the page object functions. This means thatbrowser = browser2
should be also set. In this case, we would need to remember thebrowser
global object in a temp variable and restore it once we switch back to the mainbrowser
context..another possible approach would be to pass the browser instance into the page object, something like:
var LoginPage = function (browserInstance) { browser = browserInstance ? browserInstance : browser; var element = browser.element; // ... }
but this would probably require to change every page object we have..
Hope the question is clear - let me know if it needs clarification.
Look at my solution. I simplified example, but we are using this approach in current project. My app has pages for both user permissions types, and i need to do some complex actions same time in both browsers. I hope this might show you some new, better way!
"use strict";
//In config, you should declare global browser roles. I only have 2 roles - so i make 2 global instances
//Somewhere in onPrepare() function
global.admin = browser;
admin.admin = true;
global.guest = browser.forkNewDriverInstance();
guest.guest = true;
//Notice that default browser will be 'admin' example:
// let someElement = $('someElement'); // this will be tried to be found in admin browser.
class BasePage {
//Other shared logic also can be added here.
constructor (browser = admin) {
//Simplified example
this._browser = browser
}
}
class HomePage extends BasePage {
//You will not directly create this object. Instead you should use .getPageFor(browser)
constructor(browser) {
super(browser);
this.rightToolbar = ToolbarFragment.getFragmentFor(this._browser);
this.chat = ChatFragment.getFragmentFor(this._browser);
this.someOtherNiceButton = this._browser.$('button.menu');
}
//This function relies on params that we have patched for browser instances in onPrepare();
static getPageFor(browser) {
if (browser.guest) return new GuestHomePage(browser);
else if (browser.admin) return new AdminHomePage(browser);
}
openProfileMenu() {
let menu = ProfileMenuFragment.getFragmentFor(this._browser);
this.someOtherNiceButton.click();
return menu;
}
}
class GuestHomePage extends RoomPage {
constructor(browser) {
super(browser);
}
//Some feature that is only available for guest
login() {
// will be 'guest' browser in this case.
this._browser.$('input.login').sendKeys('sdkfj'); //blabla
this._browser.$('input.pass').sendKeys('2345'); //blabla
this._browser.$('button.login').click();
}
}
class AdminHomePage extends RoomPage {
constructor(browser) {
super(browser);
}
acceptGuest() {
let acceptGuestButton = this._browser.$('.request-admission .control-btn.admit-user');
this._browser.wait(EC.elementToBeClickable(acceptGuestButton), 10000,
'Admin should be able to see and click accept guest button. ' +
'Make sure that guest is currently trying to connect to the page');
acceptGuestButton.click();
//Calling browser directly since we need to do complex action. Just example.
guest.wait(EC.visibilityOf(guest.$('.central-content')), 10000, 'Guest should be dropped to the page');
}
}
//Then in your tests
let guestHomePage = HomePage.getPageFor(guest);
guestHomePage.login();
let adminHomePage = HomePage.getPageFor(admin);
adminHomePage.acceptGuest();
adminHomePage.openProfileMenu();
guestHomePage.openProfileMenu();
这篇关于多个浏览器和页面对象模式的文章就介绍到这了,希望我们推荐的答案对大家有所帮助,也希望大家多多支持IT屋!