使用Test Automation的页面对象模型,我们将页面链接在一起:
WebDriver driver = new WebDriver()
HomePage homePage = new HomePage(driver);
LoginPage loginPage = homePage.GoToLoginPage();
WelcomePage welcomePage = loginPage.Login();
etc
etc
这样做的最大好处是,如果开发人员更改主页,使其不再链接到登录页面,我可以更新我的主页类,并在运行测试之前查看我需要更新的所有测试(有错误)。 / p>
然而,对于Gherkin,上面的每一行都会形成一个单独的步骤'因此是一种单独的方法。那么,如何进行这种联系呢?
是将页面对象类的实例(例如homePage,loginPage等)放置到交叉小黄瓜语句持久性存储中的唯一方法(例如,像specflow POCO或' World')?
答案 0 :(得分:4)
好的,所以在询问了众多开发和测试自动化专家之后,似乎解决方案是继续链接[e.g. WelcomePage welcomePage = loginPage.loginWithValidUser(validUser)]
是要走的路。
要跨步骤保持页面对象的实例(例如上面示例中的welcomePage),您可以使用依赖注入工具(在Ruby的黄瓜实现中创建类似于World扩展的功能)。
以下是更多信息: https://cukes.info/docs/reference/java-di
但是,大多数项目都将受益于依赖注入模块 更好地组织代码并在Step之间共享状态 定义
来自SpecFlow的更多信息(.net官方黄瓜实施):
http://specflow.org/getting-started/beyond-the-basics/
最后,我在这个区域创建了一个可以帮助人们的整个博客,因为小黄瓜/页面对象交互是我非常感兴趣的主题:
http://www.seligmanventures.com/dev-blog/test-automation-page-object-model-with-gherkin
答案 1 :(得分:0)
对于大多数网站(可以使用网址),我认为最好只使用网址而非动作来获取相同的网址。
例如:
# Suggested by OP:
driver = Selenium::Webdriver.for :chrome, prefs: prefs
homepage = Homepage.new(driver)
login = homepage.go_to_login
welcome = login.log_in_as('dave4429')
# My Suggestion:
homepage = Url.new('/')
login = Url.new('/login')
welcome = Url.new('/welcome')
这意味着您从网址开始,而不是每次测试都必须从主页开始。您仍然可以使用您建议的方法,但它们将在其他区域中使用,以确保用户可以通过除URL之外的其他方式访问该页面。
但是,这不是一站式解决方案。对于移动和桌面应用程序,您唯一的选择可能是通过主屏幕,在这种情况下,您建议的方法绝对是您的选择。
"页面对象本身不应该进行验证或断言。这是测试的一部分,应该始终在测试代码中,而不是在页面对象中。" - Selenium HQ
我给出的一个非常基本的例子,我很可能将它们包装成模块和类,以实现这样的编码:
google = Project::Pages::Google.new
google.search_for('Hello, World!')
expect(google.found_result?).to_equal(true)
修改强>
除此之外,您似乎对黄瓜如何与小黄瓜合作存在误解。
每步可以有多行代码,因为步骤本身就是对步骤中操作的描述。
例如:
Given I am logged in as "dave4429"
When I have submitted the "Contact Us" form with the following data:
| dave4429@example.com | David McBlaine | I want to find out more about your Data Protection services, can I talk to a staff member or get a PDF? |
Then an email should be sent to "support@example.com" with the details specified
"当"的定义可能看起来像这样:
When(/^I have submitted the "Contact Us" form with the following data:$/) do |table|
rows = table.raw
row = rows[0]
contact_us.fill_form({email: row[0], username: row[1], message: row[2]})
contact_us.submit_message
expect(browser.title).to_equal("Message Sent!")
end
这完全取决于你在定义中分解了多少步骤。
编辑#2
我也很清楚你想要进行方法链接,这是contact_us.fill_form({email: row[0], username: row[1], message: row[2]}).submit_message
的方式,在使用我和#的技术时,这也不是问题所在。 39; m建议,但是这个链接是否应该针对每个单独的页面,或者是否所有内容都应该包含在一个类或模块中的问题,只能通过您的需求来回答。
我只是认为这会对单个类产生过多影响,并且分解该类将允许对测试人员进行更多控制,并且将编写更少的冗余代码。
答案 2 :(得分:0)
我最近看到的另一个选择是将页面对象实例存储为可以从任何类访问的静态变量?
答案 3 :(得分:-1)
使用黄瓜和硒可能有点棘手。我已经开发了一种模式,该模式涉及Selenium的IWebDriver
接口的扩展方法,使我可以使用页面对象导航到特定页面。我在SpecFlow依赖项注入框架中注册了IWebDriver
对象,然后我的步骤定义类可以随意初始化所需的任何页面对象。
您只需要插入前/后场景挂钩即可管理Web驱动程序对象:
[Binding]
public class WebDriverFactory
{
private readonly IObjectContainer container;
public WebDriverFactory(IObjectContainer container)
{
this.container = container;
}
[BeforeScenario]
public void CreateWebDriver()
{
var driver = new ChromeDriver(...);
// Configure Chrome
container.RegisterInstanceAs<IWebDriver>(driver);
}
[AfterScenario]
public void DestroyWebDriver()
{
var driver = container.Resolve<IWebDriver>();
if (driver == null)
return;
// Capture screenshot if you want
// var photographer = (ITakeScreenshot)driver;
driver.Quit();
driver.Dispose();
}
}
然后,使用IWebDriver
界面上的一些扩展将步骤定义和页面对象粘合在一起。
让您的页面对象彼此导航。例如,主页允许您导航到“创建博客文章”页面,并返回该页面的页面对象:
public class HomePage
{
private readonly IWebDriver driver;
private readonly WebDriverWait wait;
private IWebElement CreatePostLink => driver.FindElement(By.LinkText("Create New Blog Post"));
public HomePage(IWebDriver driver)
{
this.driver = driver;
wait = new WebDriverWait(driver, 30);
}
public AddEditBlogPostPage ClickCreatePostLink()
{
CreatePostLink.Click();
wait.Until(d => d.Title.Contains("Create new blog post"));
return new AddEditBlogPostPage(driver);
}
}
随后,当您创建新博客帖子时,AddEditBlogPostPage返回BlogPostListingPage:
public class AddEditBlogPostPage
{
private readonly IWebDriver driver;
private IWebElement Title => driver.FindElement(By.Id("Title"));
private IWebElement PostDate => driver.FindElement(By.Id("Date"));
private IWebElement Body => driver.FindElement(By.Id("BodyText"));
private IWebElement SaveButton => driver.FindElement(By.XPath("//button[contains(., 'Save Blog Post')]"));
public AddEditBlogPostPage(IWebDriver driver)
{
this.driver = driver;
}
public BlogPostListingPage CreateBlogPost(BlogPostDataRow data)
{
Title.SendKeys(data.Title);
PostDate.SendKeys(data.Date.ToShortDateString());
Body.SendKeys(data.Body);
SaveButton.Click();
return new BlogPostListingPage(driver);
}
}
步骤:
When I create a new blog post:
| Field | Value |
| Title | Selenium Page Objects and Cucumber |
| Date | 11/1/2019 |
| Body | ... |
将具有以下定义:
[Binding]
public class BlogPostSteps
{
private readonly IWebDriver driver;
public BlogPostSteps(IWebDriver driver)
{
this.driver = driver;
}
[When(@"I add a new blog post:")]
public GivenIAmAddingANewBlogPost(Table table)
{
var addBlogPostPage = driver.GoToCreateBlogPostPage();
var blogPostData = table.CreateInstance<BlogPostDataRow>();
addBlogPostPage.CreateBlogPost(blogPostData);
}
}
driver.GoToCreateBlogPostPage();
是IWebDriver
上的扩展方法,从一个页面对象到另一个页面对象的导航开始。
public static class SeleniumPageNavigationExtensions
{
public static AddEditBlogPostPage GoToCreateBlogPostPage(this IWebDriver driver)
{
var homePage = new HomePage(driver);
return homePage.ClickCreatePostLink();
}
}
这使您可以灵活地使页面对象保持“纯净”并且没有SpecFlow,Cucumber和Gherkin。您可以在不使用Gherkin或行为驱动开发的其他测试中使用这些相同的扩展方法和页面对象。这样可以轻松地重用您的测试类。您的测试项目应与其测试的实际应用程序一样有目的地进行构建。
答案 4 :(得分:-2)
经过对此主题的大量讨论后,同样合理的替代方案是在使用带有小黄瓜的页面对象模式时不返回新页面的实例。您将失去通常使用POM链接的好处,但代码可以更好地阅读并且不那么复杂。发布这个替代答案,作为测试社区,我们可以投票选择哪种方法是人们的偏好。