ãGebããã¼ã¸ãªãã¸ã§ã¯ããã¿ã¼ã³ã¨ã¯? / What is Page Object pattern ?
ãGebããã¼ã¸ãªãã¸ã§ã¯ããã¿ã¼ã³ã¨ã¯? / What is Page Object pattern ?
èè
ï¼ãµãããããã
ãã®è¨äºã§ã¯ã以ä¸ãç®çã¨ãã¦ãã¾ãã
- Gebã¨ããGroovyè¨èªã§æ¸ããããã©ã¦ã¶èªååãã¬ã¼ã ã¯ã¼ã¯ãéãã¦Page Object patternãç解ãããã¨
- GebãPage Object patternãã©ã®ããã«ãµãã¼ããã¦ããã®ãç解ãããã¨
ï¼Gebã®éçºç°å¢æ§ç¯ã«ã¤ãã¦ã¯ä»¥ä¸ã®è¨äºãåç
§ãã¦ãã ãã
ç®æ¬¡
- å®ç¾©
- ã¡ãªãã
- WebDriverã«ãããµã³ãã«ããã°ã©ã
- Gebã«ããPage Object patternã®ãµãã¼ã
- Gebã«ããPage Object patternã®ãµã³ãã«ããã°ã©ã
1. å®ç¾©
以ä¸ãhttps://code.google.com/p/selenium/wiki/PageObjectsã翻訳ãã¦ã¿ãã¨ããã以ä¸ã®6ååãå®ã£ããã®ãå®ç¾©ã¨èããã¨ããããããã
- pulibcã¡ã½ããã¯ãã¼ã¸ã®æä¾ãããµã¼ãã¹ã表ããã¨
- ãã¼ã¸ã®å é¨ãé²åºããã¦ã¯ãªããªã
- ååã¨ãã¦assertionãã¦ã¯ãªããªã
- ã¡ã½ããã®returnã¯ãä»ã®PageObjectsã§ãããã¨
- ãã¼ã¸å ¨ä½ã表ãå¿ è¦ã¯ãªã
- åä¸ã¢ã¯ã·ã§ã³(ãµã¼ãã¹)ã§ãã£ãã¨ãã¦ããçµæãç°ãªãå ´åã¯ãç°ãªãã¡ã½ããã¨ãã¦ã¢ãã«åããã¦ãããã¨
- https://code.google.com/p/selenium/wiki/PageObjectsã®æç²
- åæ
Within your web app's UI there are areas that your tests interact with. A Page Object simply model these as objects within the test code.
PageObjects can be thought of as facing in two directions simultaneously.
Facing towards the developer of a test, they represent the services offered by a particular page.
Facing away from the developer, they should be the only thing that has a deep knowledge of the structure of the HTML of a page (or part of a page).
It's simplest to think of the methods on a Page Object as offering the "services" that a page offers rather than exposing the details and mechanics of the page.
As an example, think of the inbox of any web-based email system.
Amongst the services that it offers are typically the ability to compose a new email, to choose to read a single email, and to list the subject lines of the emails in the inbox.
How these are implemented shouldn't matter to the test.
â Summary
ã»The public methods represent the services that the page offers
ã»Try not to expose the internals of the page
ã»Generally don't make assertions
ã»Methods return other PageObjects
ã»Need not represent an entire page
ã»Different results for the same action are modelled as different methods
æ訳
ã¦ã§ãã¢ããªã®ãã¹ãã«ããã¦ããã¹ãã³ã¼ãã対話ããã¨ãªã¢ããããPageObjectsãå©ç¨ãããã¨ã§ãã®ãããªãã¹ãã³ã¼ããã·ã³ãã«ã«ã¢ãã«åãããã¨ãã§ããã
PageObjectsã¯åæã«2ã¤ã®å½ä»¤ã«å¯¾å¿ãã¦ããã¨èãããã¨ãã§ããããã¹ãéçºè ã«å¯¾å¿ãã¦ãPageObjectsã¯ç¹å®ã®ãã¼ã¸ã«ãã£ã¦æä¾ããããµã¼ãã¹ã表ãã
ãã¹ãéçºè ããé¢ãããã¨ã§ãPageObjectsã¯ããã¼ã¸ï¼ã¾ãã¯ãã¼ã¸ã®ä¸é¨ï¼ã®ãHTMLæ§é ã®æ·±ãç¥èãæã£ã¦ããå¯ä¸ã®ãã®ã§ãªããã°ãªããªãã
PageObjectsã®ã¡ã½ããã¯ããã®ãã¼ã¸ã®è©³ç´°ã¨æ§é ãé²åºããã®ã§ã¯ãªãããã®ãã¼ã¸ã®æä¾ããããµã¼ãã¹ããæä¾ããã¨ãèããã®ãæãã·ã³ãã«ã§ããã
ä¾ã¨ãã¦ãä»»æã®Webãã¼ã¹ã®é»åã¡ã¼ã«ã·ã¹ãã ã®åä¿¡ç®±ãèãããã¨ã¨ããã
ãã®ã·ã¹ãã ãæä¾ãããµã¼ãã¹ã¯ãé常ããä¸ã¤ã®é»åã¡ã¼ã«ãèªãããã«é¸æããããããã³ãåä¿¡ããã¯ã¹å ã®ã¡ã¼ã«ã®ä»¶åãä¸è¦§è¡¨ç¤ºãããããæ°ããé»åã¡ã¼ã«ããªã©ãæ§æããæ©è½ã§ããã
ãããæ©è½ã®ãã¹ããã©ã®ããã«å®è£ ããã¦ãã¦ãããã¹ããå¤ããã¹ãã§ã¯ãªãã
â è¦ç´
·pulibcã¡ã½ããã¯ãã¼ã¸ã®æä¾ãããµã¼ãã¹ã表ããã¨
·ãã¼ã¸ã®å é¨ãé²åºããã¦ã¯ãªããªã
·ååã¨ãã¦assertionãã¦ã¯ãªããªã
·ã¡ã½ããã®returnã¯ãPageObjectsã§ãããã¨
·ãã¼ã¸å ¨ä½ã表ãå¿ è¦ã¯ãªã
·åä¸ã¢ã¯ã·ã§ã³(ãµã¼ãã¹)ã§ãã£ãã¨ãã¦ããçµæãç°ãªãå ´åã¯ãç°ãªãã¡ã½ããã¨ãã¦ã¢ãã«åããã¦ãããã¨
2. ã¡ãªãã
- ãã¼ã¸ã®ãã¶ã¤ã³å¤æ´ã«å¯¾ãã¦ãã¹ãã·ããªãªãå¤æ´ããã«å¯¾å¿ãããã¨ãã§ãã
- ä¾ãã°ããã¶ã¤ã³å¤æ´ã«ãããã°ã¤ã³ãã¼ã¸ã®ãã°ã¤ã³ãã¿ã³ã®ä½ç½®ãå¤ãã£ãå ´åã§ãããã¹ãã·ããªãªãå¤æ´ããå¿ è¦ã¯ãªããããã°ã¤ã³ãã¼ã¸ãã¯ã©ã¹ã®ãã°ã¤ã³ãã¿ã³å®ç¾©(xpathãªã©)ã ãä¿®æ£ããã°å¯¾å¿ãããã¨ãã§ãã
- ãã¹ãã·ããªãªã®ä½æè
ã¨ãã¹ãã·ããªãªå®è£
è
ãåé¢ãããã¨ãã§ãã
- ä¾ãã°ããã¹ãã·ããªãªè ï¼è©ä¾¡ãã¼ã ãªã©ï¼ã¯ããã°ã¤ã³âååæ¤ç´¢âè³¼å ¥âè³¼å ¥å®äºãã¼ã¸ãã¨ããã·ããªãªé¨åã ãå®ç¾©ããããã°ã¤ã³å¦çãã©ããã£ã¦å®è£ ããã(WebDriver , Geb)ãªã©ã¯ãéçºè ãå®ç¾©ããã¨ããåæ¥ãå¯è½ã«ãªã
åæ
Page Object reduces the amount of duplicated code and means that if the UI changes, the fix need only be applied in one place.
æ訳
PageObjectã«ãã£ã¦éè¤ã³ã¼ããæ¸ãããã¨ãã§ãããå ãã¦ãUIãå¤æ´ãããå ´åã§ããä¸ã¶æä¿®æ£ããã°ããã ãã§è¯ããã¨ãæå³ããã
3. WebDriverã«ãããµã³ãã«ããã°ã©ã
public class LoginPage { private final WebDriver driver; public LoginPage(WebDriver driver) { this.driver = driver; // Check that we're on the right page. if (!"Login".equals(driver.getTitle())) { // Alternatively, we could navigate to the login page, perhaps logging out first throw new IllegalStateException("This is not the login page"); } } // The login page contains several HTML elements that will be represented as WebElements. // The locators for these elements should only be defined once. By usernameLocator = By.id("username"); By passwordLocator = By.id("passwd"); By loginButtonLocator = By.id("login"); // The login page allows the user to type their username into the username field public LoginPage typeUsername(String username) { // This is the only place that "knows" how to enter a username driver.findElement(usernameLocator).sendKeys(username); // Return the current page object as this action doesn't navigate to a page represented by another PageObject return this; } // The login page allows the user to type their password into the password field public LoginPage typePassword(String password) { // This is the only place that "knows" how to enter a password driver.findElement(passwordLocator).sendKeys(password); // Return the current page object as this action doesn't navigate to a page represented by another PageObject return this; } // The login page allows the user to submit the login form public HomePage submitLogin() { // This is the only place that submits the login form and expects the destination to be the home page. // A seperate method should be created for the instance of clicking login whilst expecting a login failure. driver.findElement(loginButtonLocator).submit(); // Return a new page object representing the destination. Should the login page ever // go somewhere else (for example, a legal disclaimer) then changing the method signature // for this method will mean that all tests that rely on this behaviour won't compile. return new HomePage(driver); } // The login page allows the user to submit the login form knowing that an invalid username and / or password were entered public LoginPage submitLoginExpectingFailure() { // This is the only place that submits the login form and expects the destination to be the login page due to login failure. driver.findElement(loginButtonLocator).submit(); // Return a new page object representing the destination. Should the user ever be navigated to the home page after submiting a login with credentials // expected to fail login, the script will fail when it attempts to instantiate the LoginPage PageObject. return new LoginPage(driver); } // Conceptually, the login page offers the user the service of being able to "log into" // the application using a user name and password. public HomePage loginAs(String username, String password) { // The PageObject methods that enter username, password & submit login have already defined and should not be repeated here. typeUsername(username); typePassword(password); return submitLogin(); } }
4. Gebã«ããPage Object patternã®ãµãã¼ã
Gebã«ã¯Page Object patternããµãã¼ãããfirst class objectããããã¾ããGroovyã®DSLæ©è½ãæ´»ç¨ããããã®first class objectãããã
â first class objectã¨ã¯?
ãfirst class objectãã¨ã¯ãå®è¡æã«çæãã¦å©ç¨ã§ãããªãã¸ã§ã¯ããæããã¡ã½ããã®å¼æ°ã¨ãã¦æ¸¡ããããå¤æ°ã«ä»£å ¥ããããã»ãã®ãªãã¸ã§ã¯ãã«æ»ãå¤ã¨ãã¦è¿ãããã§ãããã¡ãªã¿ã«ãintåãbooleanåãªã©Javaè¨èªã®ããªããã£ãåã¯ããã¡ã¼ã¹ãã¯ã©ã¹ãªãã¸ã§ã¯ãã¨ã¯è¦ãªãããªããç´ç²ãªãªãã¸ã§ã¯ãæåã好ãæè¡è ã¯ãããæ¬ ç¹ã¨ãã¦ã¨ãããJavaè¨èªãçã®ãªãã¸ã§ã¯ãæåè¨èªã§ã¯ãªãçç±ã¨ãã¦å¼ãåãåºããããã¨ããã£ããã ãGroovyã§ã¯ãæ°å¤ãå«ããã¹ã¦ããã¡ã¼ã¹ãã¯ã©ã¹ãªãã¸ã§ã¯ãã¨ãã¦å®è£ ãã¦ããããã®æ¬ ç¹ã解æ¶ãã¦ããã
Pageã¯ã©ã¹ã¯ã"location(URL)", "at checker" and "content"ãå®ç¾©ãã¦ããã
éçºè
ã¯ãããããå©ç¨ãããã¨ã§å®è£
ã¨ãµã¼ãã¹ãåé¢ãããã¨ãã§ããã
5. Gebã«ããPage Object patternã®ãµã³ãã«ããã°ã©ã
5.1 PageObjectsé©ç¨å
5.1.1 ãã¹ãã·ããªãªå®è¡ã®ã¡ã¤ã³ã¯ã©ã¹(Browser.drive{...}ã§è¨è¿°ãã)
import geb.Browser Browser.drive { go "http://google.com/ncr" $("input[name=q]").value "Chuck Norris" $("input[value='Google Search']").click() waitFor { $("li.g", 0).find("a").text().contains("Chuck") } }
5.2 PageObjectsé©ç¨å¾
- PageObjectsã¯ä»¥ä¸ã®ï¼ã¤
- GoogleHomePage
- GoogleResultsPage
5.2.1 ãã¹ãã·ããªãªå®è¡ã®ã¡ã¤ã³ã¯ã©ã¹
Browser.drive { //"location(URL)"ã®å¼ã³åºã //GoogleHomePageãªãã¸ã§ã¯ãã®å¼ã³åºã㨠//GoogleHomePage.urlãã¼ã¸ã¸ã®é·ç§» to GoogleHomePage //GoogleHomePage.searchãå¼ã³åºã search "Chuck Norris" //"at checker"ã®å¼ã³åºã //GoogleResultsPageã表示ããããã¨ããã§ã㯠at GoogleResultsPage //"content"ã®å¼ã³åºã //containsã¯groovyã®æä¾ããã¡ã½ããã§ãJavaã®String.containsã¨åæ§ resultLink(0).text().contains("Chuck") }
5.2.2 GoogleHomePage(Googleã®æ¤ç´¢ãã¼ã¸ç¨PageObject)
- "static url"ã"location(URL)"ã表ãã¦ãã
- "static at"ã"at checker"ã表ãã¦ãã
- "static content"ã"content"ã表ãã¦ãã
import geb.* class GoogleHomePage extends Page { static url = "http://google.com/?complete=0" static at = { title == "Google" } static content = { //"content"ã®DSLã¯ä»¥ä¸ã®ããã«å®ç¾©ãã //«name» { «definition» } searchField { $("input[name=q]") } searchButton(to: GoogleResultsPage) { $("input[value='Google Search']") } } void search(String searchTerm) { searchField.value searchTerm searchButton.click() } }
5.2.3 GoogleResultsPage(Googleã®æ¤ç´¢çµæãã¼ã¸ç¨PageObject)
- "static url"ã"location(URL)"ã表ãã¦ãã
- "static at"ã"at checker"ã表ãã¦ãã
- "static content"ã"content"ã表ãã¦ãã
class GoogleResultsPage extends Page { static at = { waitFor { title.endsWith("Google Search") } } static content = { //"wait"ãªãã·ã§ã³ã«ãã£ã¦ãGoogleã®æ¤ç´¢çµæãã¼ã¸ã« //$("li.g")ã表示ãããã¾ã§ä¸å®æéå¾ ã¤ãã¨ãã§ããã //ããã表示ãããªãã£ãå ´åã¯"RequiredPageContentNotPresent" ä¾å¤ã //çºçãã results(wait: true) { $("li.g") } result { index -> results[index] } resultLink { index -> result(index).find("a") } } }