几周前,我已经开始使用Selenium 2 / Webdriver Web自动化框架,我对此很满意,但是我不确定如何最好地设计我的Page Objects。以下问题困扰着我:


页面URL是Page Object的属性吗?如果许多不同的URL指向同一页面怎么办?另外,如果使用URL传递一些参数怎么办?
除WebElements和WebDriver实例外,您的页面对象还有哪些字段?
如何初始化页面对象?在构造函数中?使用工厂方法?您是否使用WebDriver提供的PageFactory类?
您的页面对象是否曾经负责浏览到相应的网页,还是依靠“外力”来进行浏览?用WebDriver术语来说,您的对象是否可以使用driver.get(“ URL”)?

我当然对这些问题有自己的答案,但为了避免混乱,我将其排除在主要帖子之外。

评论

要澄清一下,您是UI自动化工具的新手,还是试图将通过其他工具学到的知识应用于Selenium 2 / Webdriver?

我是一般测试自动化的新手,哎呀,我是一般测试自动化的新手。不知道为什么这很重要,除了可能作为我问傻问题的借口... :)

我只是想弄清楚您是将Selenium API中的“页面对象”表示为对象,还是将您的“页面对象”表示为组织代码的一种方式。听起来像是您的意思。

是的,我指的是Page Object设计模式,而不是Selenium特有的任何方式。

您是否尝试过ISFW?它使用selenium1和selenium2 / webdriver提供了强大的页面概念。 ISFW中定义的页面不是基于URL的。例如,考虑用户信息页面。页面是相同的,但是内容会有所不同,即不同。 fname,lname,adderss。该网址在此类页面中也是动态的。在ISFW中,所有这些思想都得到了照顾,谢谢

#1 楼

我发现Page Object模式非常有用,并使用了经过修改的PageFactory(为自定义超时进行了参数化)。 。


我认为并非所有页面上的URL关联都是必需的,因此我将其排除在基类之外。 URL参数?我以我的PageObjects用户为中心;如果用户不需要知道,PageObject也不需要。对象状态仅是顺序执行和构造的产物。


format(:Date):字符串
select(:WebElement, :String)
selectMany(:WebElement, :int)
selectRadio(:WebElement, ...)
... 。

我还在singleton实用程序类中定义了许多谓词,函数和ExpectedConditions,它们与FluentWait一起使用以实现:


waitToBeAt(path:String, timeout:int)
varEquals(var:String, value:String, timeout:int)

switchToWindow(path:String, value:String, timeout:int):WebDriver
windowIsClosed(winTitle:String, timeout:int)

elementLocated(:By, timeout:int):WebElement
.....


I还将JavaScript代码段保存在离散的文件中,并在启动时将其读取到Map中,并按文件名索引。与在源代码中编写转义的源代码相比,这具有无限的可维护性。 />我的PageObjects总是知道如何导航到下一页,并具有返回这些PageObjects的方法。我尽可能使用链接(return this;),因为它使测试代码非常流畅,这是使用此模式的最喜欢的部分:



评论


我认为返回其他页面对象的页面对象始终不是一个好的解决方案。想象一下一个LoginPage对象,如果登录方法成功,它可以将您带到HomePage,如果失败,则可以带您回到LoginPage。

– dzieciou
2014年5月29日20:25

作为测试作者,您知道给定的登录信息何时成功。因此,有必要为每个目标页面创建不同的方法,在这种情况下:public HomePage loginFail(String username,String password){...}。我通常建议创建带有通用返回类型的参数化方法,以保持代码DRY。如果您的代码不正确,例如导航到意外页面,您应该抛出一个异常以供测试运行程序捕获。

–乔编码器
2014年6月1日在3:49



#2 楼

下面的答案基于我们构建的框架,该框架使用状态机遍历我们正在测试的应用程序。代表网址)确实是我的测试套件中各个页面对象的属性。它们都源自与terryp非常相似的基本页面对象(尽管我们已经转换为PyQuery而不是BeautifulSoup了-大多)。
__init__中,将页面中的表单元素抓取,并将它们包装到.form属性中。 (例如lib.pages)找到最合适的页面。如果将一个url用于许多不同的页面(ajax等),则使用基于内容的其他识别信息(我知道,嗯)。
在我们使用的状态机模型中,状态机是迭代器。因此,在每个页面对象上通过next()方法(通过状态机上的__iter__)进行从一页到下一页的浏览


#3 楼

我用Clojure(一种在JVM上运行的Lisp)编写Selenium测试。尽管可以在Clojure中定义对象,但使用函数而不是对象来组织Lisp代码更有意义,因此我的测试都是在偶尔关闭的情况下编写为函数的。 (我提到Clojure是为了说明为什么我不使用页面对象,而不是作为对其他任何人的认可或推荐。)尽管我刚才说了什么,但我认为我的某些回答适用于您的问题。 >
我没有测试页面的结果URL。对于每个页面,我都有一个知道如何导航到该页面的函数。该函数知道URI。其参数包括基本URL和最终URL中需要包含的所有参数。如果页面有多个路径,我将在原始页面的上下文中测试每个路径。

我的测试通常没有特定于页面的状态。可能存在全局状态(例如,用于登录具有不同特权的帐户的用户名和密码),但是由于不是特定于页面的,因此我将它们存储在可以传递的全局“上下文”中。 >
我的“转到X页面”函数执行driver.get(URL),或者调用了一个调用driver.get(URL)的函数。它假定浏览器会话已经处于可以访问该页面的状态。例如,如果您必须在进入X页之前登录,则外力(一种想知道您在进入X页时会发生什么的测试)将首先通过登录页面登录。

#4 楼

我使用Python和Selenium 1并发现页面对象非常有用。


大多数情况下都不关心URL。我检查页面内容以确保位于期望的位置。
页面对象中的大多数字段与页面上的表单字段相关。我也有一些对象字段,可以根据DOM读取静态文本的一部分。 。基类还保存Selenium会话对象。大多数子类是从基本实例p=HistoryPage(base)初始化的,但我也可以执行p=base.clone_to(HistoryPage)。这主要是通过Selenium会话,这可能是一种更好的方法。
基类处理大多数导航,最常见的是base.visit("link=Settings")。某些页面对象具有进行导航并为新页面返回新页面对象的方法:transaction_page = history_page.find_transaction('canceled')


评论


我知道,所以基本上您的页面对象是负责导航和初始化的对象。我不清楚页面如何知道要浏览的URL。 “ link = Settings”是什么意思?

– Sassy
2011年5月20日下午6:29

在Selenium 1中,这意味着带有innerText()=='Settings'的链接。硒2将使用By.linkText('Settings')。

–兰德尔·博恩(Randall Bohn)
2011年5月20日13:11

我懂了。但是起点是什么?基本上,是要与之交互的第一个页面对象,它如何知道从哪里开始?没有要按的链接/按钮。我可以看到两种方式,一种是将测试放在第一个URL中,在这种情况下,它将从何处获取该URL?或者,您的所有测试都从同一点开始,说出主页并通过交互从那里开始浏览。

– Sassy
11年5月20日在16:58



它们都从“ /”开始。

–兰德尔·博恩(Randall Bohn)
2011年5月20日18:16

#5 楼


页面URL是Page对象的属性吗?如果许多不同的URL指向同一页面怎么办?另外,如果使用URL传递一些参数,该怎么办呢?


我的大多数页面都不知道(或不在乎)那些导致他们。当我有要直接跳转的页面时(而不是通过跟随其他页面上的链接来导航),我经常将这些URL的知识捆绑到“站点”对象或“站点导航”对象中。


您的页面对象除了WebElements和WebDriver实例之外还具有哪些字段?页面。

我通常不存储WebElement对象供跨页面操作使用。相反,我为每个操作都创建了新的。通常。

我的页面对象经常使用轮询DSL来轮询各种条件。因此,页面通常还包含可在轮询表达式中使用的轮询器或默认计时器。


如何初始化页面对象?在构造函数中?使用工厂方法?您使用WebDriver提供的PageFactory类吗?


我的页面对象构造函数只是存储WebDriver实例以及以后需要的其他任何参数。我非常努力地确保页面对象在其构造函数中不做任何实际工作。

我几乎总是创建工厂方法来隐藏调用构造函数的附带细节。因此,与其在我的测试中编写此代码,不如:

@Test public void disasterRecovery() {
    ...
    new HalPage(webDriver, site, defaultTimer).openThePodBayDoor();
    ...
}


...我要编写: />如果发现自己到处都在创建hal()工厂方法,则经常将其移入“夹具”基类,该基类提供了许多常见的工厂方法。我对此做法感到不安,因为随着时间的流逝,灯具的基础类因工厂方法而变得肿。

我目前广泛使用的另一种做法是将那些常用的页面参数捆绑到某种“上下文”对象中,然后将其代替单个参数来处理。最近,我对这种做法感到不安,因为上下文对象倾向于收集许多不同的对象,这增加了耦合并降低了内聚性。合适的网页还是您依靠“外部力量”进行浏览?用WebDriver的术语来说,您的对象是否曾经做过driver.get(“ URL”)?


我从来没有这样做的页面。我将所有站点导航职责分配给一个“站点”或“站点导航”对象。

带有页面对象的常见模式是:当页面操作导航到另一个页面时,该操作将返回一个新页面代表目标页面的对象。最近,我不想这样做。练习将每个页面耦合到其每个目的地。相反,当某些方法调用导致新页面的页面操作时,我使调用方法为目标页面创建页面对象。

#6 楼

这是我几年前编写的一些较旧的代码。

    def __init__(self):
        self.b = t.get_browser()
        self.url = self._get_url()
        self.resource = self._get_resource()
        self.title = self._get_title()
        self.html = self._get_html()
        self.soup = BeautifulSoup(self.html)
        self.meta_keywords = self._get_meta_keywords()
        self.meta_description = self._get_meta_description()
        self.links = self._get_links()
        self.images = self._get_images()


这是我们页面类的基本实现。因此,对于登录页面等,您可以继承子类并将其扩展为所需的其他任何类。

对于此基类,页面本身可以验证其是否正确加载。同样,当您进入页面的更多自定义实现时,可以根据需要扩展验证和操作。正如我记得Simon Stewart在GTAC 2007上谈论的那样,这个想法是,您需要使页面尽可能地原子化,而无需过度定制。

#7 楼

我会根据我如何使用页面对象来回答它。

免责声明:我很少使用Selenium 2,但是页面对象的概念与任何工具无关。

答案-


我的页面对象不知道页面URL,实际上页面URL是我所知的唯一一种基类。这个基类(我通常称其为-SelTestCase)被其他类扩展。
我从来不需要处理传递参数的URL。
我的页面对象也知道页面的字段-例如Textbox等。
我不使用webdriver,我使用构造函数来初始化页面对象。
我不对页面对象执行driver.get或selenium.open,但我的页面对象使用应用程序元素来到达页面。实际上,当控件到达页面对象时,页面已经处于已知状态,因此我从那里开始。

一旦收到令人满意的响应数量,就发布您的答案...

#8 楼

您是否尝试过ISFW?它使用selenium1和selenium2 / webdriver提供了强大的页面概念。 ISFW中定义的页面不是基于URL的。例如,考虑用户信息页面。页面是相同的,但是内容会有所不同,即不同。 fname,lname,adderss。该网址在此类页面中也是动态的。
在ISFW中,所有这些考虑都应得到照顾
感谢

#9 楼

看一下提供了增强的Page Object Model的Test Automation Framework。浏览器浏览到指定页面并验证该页面是否已加载。
一种“ at”验证方法,它将验证浏览器在所述页面上。当您在页面上执行操作时会被加载。

http://menonvarun.github.io/taf/index.html

http://menonvarun.github.io/taf/pages/page_object_model_in_taf.html