반응형
블로그 이미지
개발자로서 현장에서 일하면서 새로 접하는 기술들이나 알게된 정보 등을 정리하기 위한 블로그입니다. 운 좋게 미국에서 큰 회사들의 프로젝트에서 컬설턴트로 일하고 있어서 새로운 기술들을 접할 기회가 많이 있습니다. 미국의 IT 프로젝트에서 사용되는 툴들에 대해 많은 분들과 정보를 공유하고 싶습니다.
솔웅

최근에 올라온 글

최근에 달린 댓글

최근에 받은 트랙백

글 보관함

카테고리


반응형

Handling iFrames using WebDriver


inline frame은 현재의 HTML 문서 내에 다른 문서를 넣을 때 사용됩니다. 즉 iframe은 웹페이지 안에 있는 웹페이지를 말하는거죠. iframe은 별도로 DOM을 가지고 있습니다.

iframe을 사용하는 HTML 소스코드는 아래와 같이 사용하시면 됩니다





위의 코드를 보면 iframe안에 또 다른 iframe이 있는 걸 보실 수 있을 겁니다. 안쪽의 iframe으로 가려면 바깥쪽의 iframe 을 지나가야 되고 또 그 안쪽의 iframe 안에도 body가 있습니다.

그리고 나서 다시 안쪽의 iframe은 을 나와서 바깥쪽 iframe 까지 나오면 이제 OK 버튼을 보게 됩니다. 테스트를 작성하려면 이 과정을 거쳐야 합니다. iframe 안에 있는 element에 직접 접근하는 것은 가능하지 않습니다. iframe은 자신만의 DOM element를 가지고 있어서 그 안의 element들을 다루려면 switch to 를 해서 접근해야 합니다.


Selects the frame 1

driver.switchTo().frame("frame1");

Selects the frame2

driver.switchTo().frame("frame2");

Switching back to the parent window

driver.switchTo().defaultContent(); // 이제 iframe 바깥으로 완전히 빠져 나온 겁니다.

iframe의 value를 알지 못할 때도 있는데요. 그럴 때는 아래와 같이 tagName 메소드를 사요ㅇ해서 그 name을 얻을 수 있습니다.

driver.switchTo().frame(driver.findElements(By.tagName("iframe").get(0));



아래 메소드를 사용해서 iframe을 선택하실 수 있습니다.

    frame(index)
    frame(Name of Frame [or] Id of the frame
    frame(WebElement frameElement)
    defaultContent()


반응형


반응형

오늘은 회사에서 작업하다가 체험한 유용한 로직을 정리해 두어야 겠다.



<input onclick="display('0000','AAAA','y')" value="test" class="test" type="button">

<input onclick="display('0001','BBBB','y')" value="test" class="test" type="button">

<input onclick="display('0002,'CCCC','y')" value="test" class="test" type="button">

<input onclick="display('0003','DDDD','y')" value="test" class="test" type="button">

<input onclick="display('0004','EEEE','y')" value="test" class="test" type="button">

<input onClick="display('0005','FFFF','y')" value="test" class="test" type="button">



Selenium WebDriver로 Automation Testing 작업을 하고 있는데 위와 같은 링크가 나타났다.

예를 들어 이중에서 세번째 것을 클릭해야 하는데 ... 이걸 어떻게 클릭해야 할지....


여러번 헤매다가 찾아낸 방법이다.


List<WebElement> solution = WebDriverAction.getElemtnts(By.cssSelector("input[type='button'][value='test'"));

for(WebElement aaa : solution)

{

if(aaa.getAttribute("onClick").contains("CCCC")) {

aaa.click();

}

}




일단 cssSelector를 사용해서 type과 value 조합으로 WebElement들을 모드면 위의 input 들만 모일것 같다.

WebDriverAction.getElement를 사용해서 이 조합으로 된 WebElement들을 List로 담는다.


그리고 for 루프를 돌리면서 onClick이라는 attribute의 값들을 받아서 그 중에 CCCC를 포함하고 있는 놈이 걸리면 그 놈을 클릭한다.


해결하고 나니까 간단한데 이걸 처음 봤을 때는 사실 좀 헤맸다.



그리고 아래와 같은 코드도 있다.


driver.findElement(By.id("testId")).click();

List<WebElement> solution = WebDriverAction.getElemtnts(By.cssSelector("div[class="testclass"));

for(WebElement aaa : solution)

{

if(aaa.getText().equalsIgnoreCase("Year") {

aaa.click();

}

}



이것은 select option 코드에서 특정 option을 선택할 때 사용한 거다.

이걸 사용한 select option은 일반적이지는 않았다.



하여간 이렇게 WebElement들을 List에 담아서 for 문을 돌리면서 특정 조건에 맞으면 해당 아이템을 클릭하는 로직이 실무에서는 많이 사용되는 것 같다.




반응형


반응형

Why using JavascriptExecutor in WebDriver can be dangerous


나는 자주 JavascriptExecutor를 사용해서 자바스크립트를 running 하라는 권고를 듣는다. 예를 들어 개발자 컴퓨터에서는 pass를 하는데 functional test machine에서는 fail을 하는 테스트를 어느 프로젝트에서 본 적이 있다. 그 코드는 아래와 같았다.

driver.findElement(By.cssSelector(".save")).click();

그래서 개발자는 아래와 같이 이 코드를 고쳤다.

((JavascriptExecutor)driver).executeScript("$('.save').click();");


  이 코드는 제대로 작동 했다.




추측컨대 웹 드라이버는 style="display: none;" 과 함께 DOM안에 항상 존재하는 버튼을 찾게 된다. 하지만 버튼이 visible 될때까지 기다리지 않기 때문에 이 버튼 click은 fail이 되게 된다.


실제로 이 테스트는 아래와 같다.


- open the dialog (dialog 를 연다)
- wait for the button to become visible (버튼이 visible 할 때까지 기다린다)
- click the button (버튼을 클릭한다)

만약에 우리가 자바스크립트를 사용했다면 버튼이 visible 할 때까지 기다릴 필요가 없다. 그러니까 자바스크립트가 더 빠르고 제대로 동작하게 된다.

뭐 제대로 작동하니까 문제가 없을까? 그렇지 않다. 이 save 버튼이 scroll 하지 않는 javascript dialog 에 있게 된다. 만약 페이지가 scroll up이나 down을 하게 되도 이 dialog 는 그대로 남게 된다. 이 dialog는 항상 위에서부터 120px 거리에 있다. 이 테스트가 돌아갈 때 dialog 830px hige에 데이터를 생성하게 된다. 이것은 아래에서 950px에 놓이게 된다. 개발자의 machine은 1600X1200 해상도 이다. 이 해상도에서는 이 dialog가 완전히 다 보이게 된다. functional test machine은 해상도가 800X600 이다. 이렇게 되면 이 dialog의 밑부분이 보이지 않게 된다. 브라우저가 full screen이어도 이 save button을 볼 수 없게 되는 것이다.


그래서 개발자 machine에서 WebDriver는 보여서 이 save 버튼을 click 하게 된다. functional test machine 에서는 visible 하지 않고 이것을 scroll 할 수도 없기 때문에 save button이 보이지 않게 된다. 그래서 click이 안 되게 된다.

자바스크립트를 보이든 보이지 않든 상관없이 버튼이 click될 수 있도록 바꾸면 functional test machine에서는 pass가 되더라도 800X600 을 사용하는 사람들은 이 버튼을 클릭할 수 없게 된다.

이런 경우 자바스크립트를 사용하면 실제 requirement의 상황에 정확하게 일치하는 테스트가 되지 않을 수도 있고 이런 경우는 여러분의 tests report를 false가 되도록 해야 한다.


반응형


반응형

PageBase.java

GoogleSearchPage.java

GoogleSearchResultPage.java

GoogleSearchSuggestPage.java

GoogleSearch_withPageObject.java


지난 글에서 GoogleSearch_withPageObject 에 있는 testGoogleSearch() 를 분석했습니다.

이 메소드에서 사용하는 GoogleSearchPage와 GoogleSearchResultPage 클래스도 알아봤고 그 이전에 PageBase 클래스도 공부해 봤습니다.


오늘은 GoogleSearch_withPageObject 클래스에 있는 testGoogleSuggest() 메소드를 분석해 보겠습니다.



우선 이 메소드 안에서 사용되는 GoogleSearchSuggestPage 클래스를 보겠습니다.


public class GoogleSearchSuggestPage extends PageBase{
        private final static String pageTitle = "Google";

        private final static String SEARCH_FIELD_NAME = "q";
       
        /** Constructor */
        public GoogleSearchSuggestPage(WebDriver driver){
                super(driver, pageTitle);

                // set the default URL
                URL = "http://www.google.com/webhp?complete=1&hl=en";
        }

        /** Enter the Search Text in the Search form field */
        public void enterSearchForm(String searchText){
                driver.findElement(By.name(SEARCH_FIELD_NAME)).sendKeys(searchText);                
        }
       
        /** Submit the form and return the next page object.
         * Seperate function should be in a seperate method. */
        public GoogleSearchResultPage submitForm(){
                driver.findElement(By.name(SEARCH_FIELD_NAME)).submit();
                return new GoogleSearchResultPage(driver);
        }

        /**
         * Enter the query string, and get the list of the suggestions
         * It use WaitTool to wait for the div (class=gsq_a).
         *
         * @param searchText the query string to search for
         * @return the list of the google search suggestions
         */
        public List<WebElement> getSearchSuggestions(String searchText){
        // Enter the query string "Cheese"
                driver.findElement(By.name(SEARCH_FIELD_NAME)).sendKeys(searchText);

        // Wait and get the list of suggestions
        List<WebElement> allSuggestions = WaitTool.waitForListElementsPresent(driver, By.className("gsq_a"), 5);
                return allSuggestions;
        }
}


이 클래스도 PageBase 클래스를 extends 합니다.

PageBase는 이전 글에서 공부해 봤구요.

이 클래스에서도 GoogleSearchPage 클래스와 마찬가지로 pageTitle과 SEARCH_FIELD_NAME 이라는 String 객체들을 final 로 선언했습니다.


다음에 나오는 생성자에서도 super() 메소드를 사용해서 PageBase의 생성자를 실행하고 여기에 파라미터로 driver와 pageTitle을 전달합니다.


driver는 GoogleSearch_withPageObject 클래스의 testGoogleSuggest() 메소드에서 전달된 정보를 PageBase의 생성자에 전달할 겁니다.


이전글에서 다뤘던 testGoogleSuggest() 메소드에서는 HTMLUnitDriver를 전달했는데 이 testGoogleSuggest()에서는 어떤 정보를 전달하는지 조금 있다가 보겠습니다.


이 클래스의 생성자에서도 URL 을 정의했는데요. 이전에 다뤘던 GoogleSearchPage 클래스에서 정의했던것과 조금 다릅니다. 


이렇게 사용하는 driver가 다르고 URL 이 다를 경우 이렇게 Page Objects 디자인을 사용하면 메소드들을 재사용할 수 있어서 편리하고 유지관리도 편해 집니다.


다음 enterSearchForm() 메소드를 보면 By.name 으로 Element를 찾고 그것을 submit() 합니다.


그리고 submitForm() 메소드를 보면 GoogleSearchResultPage 를 return 하네요.

By.name으로 Element를 찾고 이것을 submit() 하고 이 GoogleSearchResultPage를 return 합니다.

이것은 GoogleSearchPage에서 사용했던 메소드를 재사용하는 겁니다. 그 다음에 getSearchSuggestion() 메소드를 보면 By.name으로 Element를 찾고 그 Element에 searchTest를 input 합니다.

그리고 나서 WaitTool 클래스에 있는 메소드 를 사용해서 Google Textbox에 표시되는 Autocomplete 리스트를 받아서 List 객체에 담습니다.

(이 WaitTool 클래스 분석은 생략하겠습니다.)


이제 GoogleSearch_withPageObject 클래스에서 아직 살펴보지 않았던 testGoogleSuggest() 메소드를 분석해 보겠습니다.


public static void testGoogleSuggest() throws Exception {
               
        GoogleSearchSuggestPage googleSuggestPage = new GoogleSearchSuggestPage(new FirefoxDriver());
       
        // Go to the Google Suggest home page: "http://www.google.com/webhp?complete=1&hl=en"
        googleSuggestPage.open();
       
        // Enter the query string "Cheese", and get the list the suggestions
        List<WebElement> allSuggestions = googleSuggestPage.getSearchSuggestions("Cheese");
       
        for (WebElement suggestion : allSuggestions) {
            System.out.println(suggestion.getText());
        }
     }


Page Objects를 사용하지 않은 클래스와 비교하면 무척 간단합니다.

그 이유는 여러 능을 GoogleSearchSuggestPage, GoogleSearchResultPage 그리고 PageBase 클래스 등에 분산해서 코딩을 했기 때문입니다.

driver를 생성하는 부분은 PageBase 클래스에 코딩해 뒀고 URL 세팅하는 기능 등은 GoogleSearchSuggestPage 클래스 코딩해 두는 등 기능별로 코드를 분산해서 여러 클래스에서 가져다 쓸 수 있도록 만들었습니다.


이게 Page Objects 디자인의 주요 특징입니다.


이 메소드를 보시면 GoogleSearchSuggestPage 에 FirefoxDriver를 전달하네요.

그러면 GoogleSearchSuggestPage 클래스는 이것을 받아서 자신이 만든 pageTitle과 함께 PageBase로 전달하고 PageBase에서는 FirefoxDriver 객체를 생성하게 됩니다.

그리고 open() 메소드로 해당 URL을 Firefox브라우저에 띄우고요.

그리고 allSuggestions 라는 리스트 객체에 구글 텍스트박스에 나오는 Autocomplete 리스트를 담습니다.


맨 마지막에 나오는 for 문에서는 이 리스트를 출력하는 거구요.


이렇게 해서 Page Objects를 사용하는 샘플을 모두 분석해 봤네요.


이제 대충 Selenium 에서 Page Objects를 어떻게 사용하는지 알것 같습니다.








반응형


반응형

PageBase.java

GoogleSearchPage.java

GoogleSearchResultPage.java

GoogleSearchSuggestPage.java

GoogleSearch_withPageObject.java



지난 글에서는 위 클래스 중 PageBase.java 코드를 분석했습니다.

이번에는 GoogleSearch_withPageObject 안에 있는 testGoogleSearch() 메소드에서 사용하는 클래스들(GoogleSearchPage,GoogleSearchResultPage)을 공부해 보겠습니다.



public class GoogleSearchPage extends PageBase{
       
        private final static String pageTitle = "Google";
       
        private final static String SEARCH_FIELD_NAME = "q";

        /** Constructor */
        public GoogleSearchPage(WebDriver driver){
                super(driver, pageTitle);

                // set the default URL
                URL = "http://www.google.com";
        }
       
        /** Returns the default URL */
        public String getURL(){
                return URL;
        }
       
        /** Enter the Search Text in the Search form field */
        public void enterSearchForm(String searchText){
                driver.findElement(By.name(SEARCH_FIELD_NAME)).sendKeys(searchText);                
        }
       
        /** Submit the form and return the next page object.
         * Seperate function should be in a seperate method. */
        public GoogleSearchResultPage submitForm(){
                driver.findElement(By.name(SEARCH_FIELD_NAME)).submit();
                return new GoogleSearchResultPage(driver);
        }
}


이 클래스를 보면 PageBase 를 extends 한것을 보실 수 있습니다.

이 PageBase 클래스를 상속함으로서 PageBase 클래스 안에 정의된 여러 기능들을 사용할 수 있습니다.

PageBase 클래스는 지난 글에서 분석했습니다.


GoogleSearchPage 클래스 안을 보면 우선 pageTitle이라는 String 객체를 초기화 하고 여기에 Google 글자를 할당했습니다.

그리고 SEARCH_FIELD_NAME 객체를 초기화 해서 q 라는 텍스트를 초기화 하구요.

이 두 String은 private로 선언해서 이 클래스 내부에서만 사용할 수 있고 static을 선언해서 클래스 내의 여러 인스턴스들이 공유할 수 있는 변수로 사용하겠다는 겁니다. 그리고 final 은 상수처럼 이 값을 나중에 바꾸지 못하게 하는 겁니다.


다음에 보니까 GoogleSearchPage() 메소드가 있네요. 이 메소드는 WebDriver를 파라미터로 전달 받습니다.

그리고 super()를 사용해서 PageBase 의 생성자를 실행시킵니다. 파라미터로는 WebDriver와 pageTitle을 전달하구요.

그리고 URL이라는 변수에 http://www.google.com 를 할당합니다.

이 URL은 PageBase에서 proteted로 선언된 String 입니다. GoogleSearchPage는 PageBase를 상속했기 때문에 이 변수를 사용할 수 있는 겁니다.


그 다음은 getURL() 메소드가 있고 이 메소드는 URL을 return 합니다.

그리고 enterSearchForm() 메소드가 String을 파라미터로 받습니다.

그리고 이 메소드에서 하는 일은 By.name으로 해당 Element를 찾고 즉 name이 q 인 Element를 찾고 여기에 전달받은 파라미터를 input 합니다.


다음 submitForm()은 전달받는 파라미터는 없지만 GoogleSearchResultPage 클래스를 return 하는 메소드 입니다.

여기서 하는일은 By.name으로 Element를 찾아서 그것을 submit 하도록 합니다.

그리고 GoogleSearchResultPage()를 실행해서 그 결과를 return 합니다.


자연스럽게 여기서 GoogleSearchResultPage 클래스를 보게 됐네요.


public class GoogleSearchResultPage extends PageBase{
       
        private static final String resultStats_id = "resultStats";
        private final static String pageTitle = "Google Search";
       
       
        public GoogleSearchResultPage(WebDriver driver){
                super(driver, pageTitle);        
        }
        
        /** Returns the search ResultStat. */
        public String getSearchResultStat(){
                return driver.findElement(By.id(resultStats_id)).getText();
        }  
}


이 클래스도 PageBase를 상속 받았습니다.

그리고 resultStats_id라는 String 변수에 resultStats를 할당하고 pageTitle 이라는 String 변수에는 Google Search를 할당합니다. 모두 final로 선언해서 이 값을 바꾸지 못하도록 합니다.


이어서 나오는 생성자는 super()를 사용해서 PageBase의 생성자를 실행시킵니다.


그리고 getSearchResultStat() 메소드에서는 By.id로 resultStats_id 값을 가지고 있는 id를 찾아서 거기에 있는 텍스트를 get 하고 그 결과를 return 합니다.


이제 PageBase 와 GoogleSearchPage 그리고 GoogleSearchResultPage를 모두 분석했습니다.


그러면 이제 GoogleSearch_withPageObject 의 testGoogleSearch() 메소드를 살펴 볼 수 있습니다.


public static void testGoogleSearch(){
            //Create a new instance of GoogleSearch page
            GoogleSearchPage googleSearchPage = new GoogleSearchPage(new HtmlUnitDriver());

        // go to Google ("http://www.google.com")
            googleSearchPage.open();

        // Enter something to search for
            googleSearchPage.enterSearchForm("Cheese!");

        // Now submit the form, and get the next page object
            GoogleSearchResultPage googleSearchResultPage = googleSearchPage.submitForm();
              

        // Verify: Check the title of the page        
        String pageTitle = googleSearchResultPage.getTitle();
        System.out.println("Page title is: " + pageTitle);
        assertTrue("Got title: " + pageTitle, pageTitle.contains("Cheese!"));
    }


처음에 new GoogleSearchPage()를 생성하는데 여기에 파라미터로 HtmlUnitDriver() 를 보내죠?

그러면 GoogleSearchPage 클래스의 생성자를 봐야 되는데요. 

저 위에서 봤듯이 이 생성자에서는 super() 를 사용해서 PageBase의 생성자를 호출합니다.

파라미터로는 WebDriver를 보내는데 이것은 testGoogleSearch() 메소드에서 HtmlUnitDriver()를 보냈습니다. 그리고 다른 파라미터는 GoogleSearchPage 클래스에서 만든 pageTitle 이라는 String 값을 보내게 됩니다.

그러면 PageBase 의 생성자를 볼까요?


여기서는 WebDriver 객체를 전달받은 파라미터 즉 여기서는 HtmlUnitDriver()를 사용해서 생성합니다. 그리고 pageTitle은 전달받은 pageTitle로 세팅을 하구요.


즉 GoogleSearchPage googleSearchPage = new GoogleSearchPage(new HtmlUnitDriver()); 이 한줄은 WebDriver를 생성하기 위해 만든건데요. 이 WebDriver를 만드는 기능이 GoogleSearchPage클래스와 PageBase 클래스에 나눠져 있고 그 기능들을 사용하는 겁니다.


그 다음에는 googleSerchPage.open()을 사용했는데요. 이 메소드는 PageBase에 있는 open() 메소드를 사용해서 drever.get()을 하도록 합니다.

여기서는 GoogleSearchPage 에서 정해진 URL 인 http://www.google.com으로 가는 겁니다.


그리고 enterSearchForm() 메소드를 호출하고 파라미터로 Cheese! 를 전달합니다.


이 enterSearchForm() 메소드는 GoogleSearchPage 클래스에 있죠?

이 전달된 Cheese! 값을 해당 페이지의 name 이 q 인 Element에 input 하는 겁니다.


그 다음에는 GoogleSearchResultPage 객체를 만들어서 거기에 GoogleSearchPage의 submitForm()메소드에서 return 된 값을 할당하구요. 


이 submitForm() 메소드는 해당 form 을 submit 하고 GoogleSearchResultPage() 생성자를 실행시켜서 그 결과값을 return 하는 거였죠?

GoogleSearchResultPage() 생성자는 해당 WebDriver와 pageTitle (여기서는 Google Search)을 파라미터로 PageBase 클래스에 전달해서 새롭게 WebDriver와 pageTitile을 만듭니다.


그 다음에는 새로 이동된 페이지의 Title을 을 get() 해서 콘솔에 뿌려주고 이 Title에 Cheese!라는 글자가 들어 있는지 테스트 합니다.


여기까지가 testGoogleSearch() 메소드가 동작하는 것을 분석한 겁니다.


이 작업은 page objects를 사용하지 않은 GoogleSearch 클래스의 testGoogleSearch()와 정확히 같은 동작을 합니다.


다만 GoogleSearch_withPageObject 클래스의 testGoogleSearch()는 page objects를 사용해서 그 기능을 여러 클래스에 분담해서 그 기능들을 가져다 쓰는 식으로 설게 된 겁니다.


다음에는 GoogleSearch_withPageObject 클래스의 testGoogleSearch()를 분석해 보겠습니다.

반응형


반응형

지난번 글에서 GoogleSearch 를 Page Object 를 사용해서 기능을 구현하기 위해 어떤 클래스들이 사용 됐는지 분석해 봤습니다.


PageBase.java

GoogleSearchPage.java

GoogleSearchResultPage.java

GoogleSearchSuggestPage.java

GoogleSearch_withPageObject.java


이렇게 5개의 클래스가 사용이 됐는데요.



가장 기본이 되는 PageBase.java 부터 분석해 보겠습니다.



/**
* Page Object base class. It provides the base structure
* and properties for a page object to extend.
*
* @author Chon Chung
*/
public class PageBase {
         /** Default URL */
         protected String URL;       
       
         /** This page's WebDriver */
         protected WebDriver driver;
       
         /** Expected Page Title. This will be used in isPageLoad()
         * to check if page is loaded. */
         protected String pageTitle;
       
       
         /** Constructor */
         public PageBase(WebDriver driver, String pageTitle) {
                 this.driver = driver;
                 this.pageTitle = pageTitle;
         }
       
         /**
         * Check if page is loaded by comparing
         * the expected page-title with an actual page-title.
         **/
         public boolean isPageLoad(){
                 return (driver.getTitle().contains(pageTitle));
         }
       
       
         /** Open the default page */
         public void open(){
                 driver.get(URL);
         }
       
       
         /** Returns the page title */
         public String getTitle() {
                 return pageTitle;
         }
       
         /** Returns the default URL */
         public String getURL() {
                return URL;
         }
       
         /**
         * Send text keys to the element that finds by cssSelector.
         * It shortens "driver.findElement(By.cssSelector()).sendKeys()".
         * @param cssSelector
         * @param text
         */
         protected void sendText(String cssSelector, String text) {
                        driver.findElement(By.cssSelector(cssSelector)).sendKeys(text);
         }
       
         /** Is the text present in page. */
         public boolean isTextPresent(String text){
                 return driver.getPageSource().contains(text);
         }
       
         /** Is the Element in page. */
         public boolean isElementPresent(By by) {
                        try {
                                driver.findElement(by);//if it does not find the element throw NoSuchElementException, thus returns false.
                                return true;
                        } catch (NoSuchElementException e) {
                                return false;
                        }
         }

         /**
         * Is the Element present in the DOM.
         *
         * @param _cssSelector                 element locater
         * @return                                        WebElement
         */
         public boolean isElementPresent(String _cssSelector){
                        try {
                                driver.findElement(By.cssSelector(_cssSelector));
                                return true;
                        } catch (NoSuchElementException e) {
                                return false;
                        }
         }
       

         /**
                * Checks if the elment is in the DOM and displayed.
                *
                * @param by - selector to find the element
                * @return true or false
                */
         public boolean isElementPresentAndDisplay(By by) {
                        try {                       
                                return driver.findElement(by).isDisplayed();
                        } catch (NoSuchElementException e) {
                                return false;
                        }
         }
       
         /**
         * Returns the first WebElement using the given method.        
         * It shortens "driver.findElement(By)".
         * @param by                 element locater.
         * @return                 the first WebElement
         */
         public WebElement getWebElement(By by){
                         return driver.findElement(by);                        
         }
}
/**
* Further reading:
* 1. Selenium webdriver page object:
*                 http://stackoverflow.com/questions/10315894/selenium-webdriver-page-object
* 2. Using Page Objects with Selenium and Web Driver 2.0
*                 http://www.summa-tech.com/blog/2011/10/10/using-page-objects-with-selenium-and-web-driver-20/
* 3. PageFactory
*                 http://code.google.com/p/selenium/wiki/PageFactory
*/



이 클래스에는 String 객체인 URL 과 pageTitle 그리고 WebDriver 객체인 driver 를 제일 먼저 초기화 합니다.

그리고 PageBase() 메소드를 보면 WebDriver와 pageTitle을 파라미터로 전달받아 각각 위에서 만들었던 WebDriver와 pageTitle 객체에 할당합니다.


다음에 나오는 isPageLoad() 메소드는 driver로 열은 페이지의 title에 해당 pageTitle이 있는지 확인해서 그 결과를 True or False로 반환합니다.

원하는 페이지가 open 된 것인지 확인할 때 사용하는 것 같습니다.


그리고 open() 메소드는 아까 setting 됐던 URL을 get() 메소드를 사용해서 브라우저에 open 시키는 메소드 입니다.


getTitle() 메소드는 아까 할당됐든 pageTitle을 return 해주고 getURL()은 URL을 return 합니다.


sendText()는 cssSelector와 text라는 스트링 파라미터를 전달 받습니다.

그리고 cssSelector 인 곳을 찾아 거기에 sendKey()메소드를 사용해서 text를 입력해 줍니다.


다음에 있는 isTextPresent() 메소드는 페이지내에 전달받은 text가 있는제 체크해서 그 결과를 boolean 타입으로 return 합니다.


그리고 isElementPresent() 는 text가 아니라 By 타입을 받아서 해당 Element 가 있는지 체크하고 그 결과를 boolean 타입으로 return 하구요.

그 아래는 isElementPresent() 를 OverLoading 한 것이네요. By 타입이 아니라 String 타입의 파라미터가 전달됐을 경우 실행됩니다.

하는일은 똑 같이 해당 Element 가 있는지 체크하고 그 결과를 boolean 타입으로 return 합니다.


이어서 isElementPresentAndDisplay()를 볼까요.

By 타입의 파라미터를 받아서 해당 Element가 display 돼 있는지 여부를 체크해서 boolean type을 return 합니다.


마지막으로 getWebElement() 메소드는 By 타입을 파라미터로 받아서 해당 element가 있는지 여부를 체크해서 boolean 타입을 반환하구요.


PageBase 클래스에는 이런 기능들이 있네요.

가장 기본적인 기능은 WebDriver 객체를 세팅하고 해당 페이지를 open (get()) 하는 기능이 되겠죠.






반응형


반응형

지난 글에서 다룬 GoogleSearch 클래스를 Page Object 를 사용해서 만드는 방법을 알아보겠습니다.


이렇게 Page Object를 사용하는 방법으로 사용하려면 그 기능별로 여러 클래스를 만들어야 합니다.


지난 GoogleSearch 클래스는 한개의 클래스에서 모든 작업이 이루어 졌기 때문에 한개의 JAVA 파일만 보면 됐습니다.

그런데 Page Object를 사용하는 버전에서는 여러개의 클래스 즉 여러개의 JAVA 파일을 봐야합니다.


우선 Page Object 를 사용하는 GoogleSearch 에는 어떤 클래스들이 있는지 알아보겠습니다.



우선 처음에 볼 부분은 GoogleSearch_withPageObject.java 입니다.


/**
* Google search demo class. This class shows how to use page objects.
* Compare with "GoogleSearch.java" class, and see how to use page objects.
*
* @author Chon Chung
*
*/
public class GoogleSearch_withPageObject {

    public static void main(String[] args) {
            GoogleSearch.testGoogleSearch();
            try{
                    GoogleSearch.testGoogleSuggest();
            }catch(Exception e){
                    System.out.println("Unable to perform testGoogleSuggest() - " +
                                                            e.getMessage());
            }
    }
   
    public static void testGoogleSearch(){
            //Create a new instance of GoogleSearch page
            GoogleSearchPage googleSearchPage = new GoogleSearchPage(new HtmlUnitDriver());

        // go to Google ("http://www.google.com")
            googleSearchPage.open();

        // Enter something to search for
            googleSearchPage.enterSearchForm("Cheese!");

        // Now submit the form, and get the next page object
            GoogleSearchResultPage googleSearchResultPage = googleSearchPage.submitForm();
              

        // Verify: Check the title of the page        
        String pageTitle = googleSearchResultPage.getTitle();
        System.out.println("Page title is: " + pageTitle);
        assertTrue("Got title: " + pageTitle, pageTitle.contains("Cheese!"));
    }
   
    public static void testGoogleSuggest() throws Exception {
               
        GoogleSearchSuggestPage googleSuggestPage = new GoogleSearchSuggestPage(new FirefoxDriver());
       
        // Go to the Google Suggest home page: "http://www.google.com/webhp?complete=1&hl=en"
        googleSuggestPage.open();
       
        // Enter the query string "Cheese", and get the list the suggestions
        List<WebElement> allSuggestions = googleSuggestPage.getSearchSuggestions("Cheese");
       
        for (WebElement suggestion : allSuggestions) {
            System.out.println(suggestion.getText());
        }
     }
}
/**
* Further reading:
* 1. Selenium webdriver page object:
*                 http://stackoverflow.com/questions/10315894/selenium-webdriver-page-object
* 2. Using Page Objects with Selenium and Web Driver 2.0
*                 http://www.summa-tech.com/blog/2011/10/10/using-page-objects-with-selenium-and-web-driver-20/
* 3. PageFactory
*                 http://code.google.com/p/selenium/wiki/PageFactory
* 4. Ben Burton's WebDriver Best Practices
*                 Video -- http://vimeo.com/44133409
* http://benburton.github.com/presentations/webdriver-best-practices/
*                
*/


main() 메소드는 이전의 GoogleSearch 클래스와 똑 같습니다.


testGoogleSearch() 메소드를 보면 GoogleSearch 에서는 먼저 WebDriver 객체를 만들고 그 객체를 사용해서 구글 페이지를 열었는데요.


이번에는 GoogleSearchPage 의 객체를 생성해서 사용합니다.


이 얘기는 GoogleSearchPage라는 클래스가 따로 있고 이 곳에서 WebDriver를 초기화 하는 작업을 한다는 얘기 입니다.



그러면 GoogleSearchPage.java를 볼까요?


/**
* Google Search Page Object.
* @author Chon Chung
*/
public class GoogleSearchPage extends PageBase{
       
        private final static String pageTitle = "Google";
       
        private final static String SEARCH_FIELD_NAME = "q";

        /** Constructor */
        public GoogleSearchPage(WebDriver driver){
                super(driver, pageTitle);

                // set the default URL
                URL = "http://www.google.com";
        }
       
        /** Returns the default URL */
        public String getURL(){
                return URL;
        }
       
        /** Enter the Search Text in the Search form field */
        public void enterSearchForm(String searchText){
                driver.findElement(By.name(SEARCH_FIELD_NAME)).sendKeys(searchText);                
        }
       
        /** Submit the form and return the next page object.
         * Seperate function should be in a seperate method. */
        public GoogleSearchResultPage submitForm(){
                driver.findElement(By.name(SEARCH_FIELD_NAME)).submit();
                return new GoogleSearchResultPage(driver);
        }
}


GoogleSearchPage()라는 생성자가 있죠? 여기에서 super 메소드를 사용합니다.

이 얘기는 이 클래스는 어떤 다른 클래스를 상속 받았다는 얘기입니다.

좀 위에 보니까 PageBase 클래스를 상속 받았네요.


소스 분석은 나중에 하고 오늘은 어떤 클래스들이 Page Object를 위해 사용됐는지 보기로 했으니까 다른 메소드들을 분석하는 것은 다음 글에서 하겠습니다.


그럼 PageBase 클래스를 보죠.


/**
* Page Object base class. It provides the base structure
* and properties for a page object to extend.
*
* @author Chon Chung
*/
public class PageBase {
         /** Default URL */
         protected String URL;       
       
         /** This page's WebDriver */
         protected WebDriver driver;
       
         /** Expected Page Title. This will be used in isPageLoad()
         * to check if page is loaded. */
         protected String pageTitle;
       
       
         /** Constructor */
         public PageBase(WebDriver driver, String pageTitle) {
                 this.driver = driver;
                 this.pageTitle = pageTitle;
         }
       
         /**
         * Check if page is loaded by comparing
         * the expected page-title with an actual page-title.
         **/
         public boolean isPageLoad(){
                 return (driver.getTitle().contains(pageTitle));
         }
       
       
         /** Open the default page */
         public void open(){
                 driver.get(URL);
         }
       
       
         /** Returns the page title */
         public String getTitle() {
                 return pageTitle;
         }
       
         /** Returns the default URL */
         public String getURL() {
                return URL;
         }
       
         /**
         * Send text keys to the element that finds by cssSelector.
         * It shortens "driver.findElement(By.cssSelector()).sendKeys()".
         * @param cssSelector
         * @param text
         */
         protected void sendText(String cssSelector, String text) {
                        driver.findElement(By.cssSelector(cssSelector)).sendKeys(text);
         }
       
         /** Is the text present in page. */
         public boolean isTextPresent(String text){
                 return driver.getPageSource().contains(text);
         }
       
         /** Is the Element in page. */
         public boolean isElementPresent(By by) {
                        try {
                                driver.findElement(by);//if it does not find the element throw NoSuchElementException, thus returns false.
                                return true;
                        } catch (NoSuchElementException e) {
                                return false;
                        }
         }

         /**
         * Is the Element present in the DOM.
         *
         * @param _cssSelector                 element locater
         * @return                                        WebElement
         */
         public boolean isElementPresent(String _cssSelector){
                        try {
                                driver.findElement(By.cssSelector(_cssSelector));
                                return true;
                        } catch (NoSuchElementException e) {
                                return false;
                        }
         }
       

         /**
                * Checks if the elment is in the DOM and displayed.
                *
                * @param by - selector to find the element
                * @return true or false
                */
         public boolean isElementPresentAndDisplay(By by) {
                        try {                       
                                return driver.findElement(by).isDisplayed();
                        } catch (NoSuchElementException e) {
                                return false;
                        }
         }
       
         /**
         * Returns the first WebElement using the given method.        
         * It shortens "driver.findElement(By)".
         * @param by                 element locater.
         * @return                 the first WebElement
         */
         public WebElement getWebElement(By by){
                         return driver.findElement(by);                        
         }
}
/**
* Further reading:
* 1. Selenium webdriver page object:
*                 http://stackoverflow.com/questions/10315894/selenium-webdriver-page-object
* 2. Using Page Objects with Selenium and Web Driver 2.0
*                 http://www.summa-tech.com/blog/2011/10/10/using-page-objects-with-selenium-and-web-driver-20/
* 3. PageFactory
*                 http://code.google.com/p/selenium/wiki/PageFactory
*/


PageBase() 생성자를 보시면 WebDriver와 pageTitle을 세팅합니다.

이렇게 3개의 클래스만 사용되나요?

아까 봤던 GoogleSearch_withPageObject 클래스의 testGoogleSearch() 메소드를 다시 보겠습니다.


좀 아래로 내려가니까 GoogleSearchResultPage 객체를 만드는 부분이 있습니다.

이 클래스도 따로 있는 것 같습니다.


/**
* Google Search Result Page Object.
* @author Chon Chung
*
*/
public class GoogleSearchResultPage extends PageBase{
       
        private static final String resultStats_id = "resultStats";
        private final static String pageTitle = "Google Search";
       
       
        public GoogleSearchResultPage(WebDriver driver){
                super(driver, pageTitle);        
        }
        
        /** Returns the search ResultStat. */
        public String getSearchResultStat(){
                return driver.findElement(By.id(resultStats_id)).getText();
        }  
}


여기서도 PageBase 클래스를 상속하네요.

이 PageBase 클래스는 저 위에 있으니까 다시 볼 필요는 없습니다.


그러면 testGoogleSearch() 메소드는 다 봤고 이어서 testGoogleSuggest() 메소드를 보겠습니다.

여기에서는 GoogleSearchSuggestPage 클래스에 대한 객체를 생성하네요.

이 GoogleSearchSuggestPage.java 파일도 사용됐습니다.


public class GoogleSearchSuggestPage extends PageBase{
        private final static String pageTitle = "Google";

        private final static String SEARCH_FIELD_NAME = "q";
       
        /** Constructor */
        public GoogleSearchSuggestPage(WebDriver driver){
                super(driver, pageTitle);

                // set the default URL
                URL = "http://www.google.com/webhp?complete=1&hl=en";
        }

        /** Enter the Search Text in the Search form field */
        public void enterSearchForm(String searchText){
                driver.findElement(By.name(SEARCH_FIELD_NAME)).sendKeys(searchText);                
        }
       
        /** Submit the form and return the next page object.
         * Seperate function should be in a seperate method. */
        public GoogleSearchResultPage submitForm(){
                driver.findElement(By.name(SEARCH_FIELD_NAME)).submit();
                return new GoogleSearchResultPage(driver);
        }

        /**
         * Enter the query string, and get the list of the suggestions
         * It use WaitTool to wait for the div (class=gsq_a).
         *
         * @param searchText the query string to search for
         * @return the list of the google search suggestions
         */
        public List<WebElement> getSearchSuggestions(String searchText){
        // Enter the query string "Cheese"
                driver.findElement(By.name(SEARCH_FIELD_NAME)).sendKeys(searchText);

        // Wait and get the list of suggestions
        List<WebElement> allSuggestions = WaitTool.waitForListElementsPresent(driver, By.className("gsq_a"), 5);
                return allSuggestions;
        }
}



여기서도 PageBase 클래스가 상속됐습니다. 다시 볼 필요는 없겠죠.

그 다음에 이 파일 안에서 다른 custom 클래스에 대한 객체를 만드는 부분은 없습니다.


다시 GoogleSearch_withPageObject 클래스로 돌아가죠.

testGoogleSuggest() 메소드를 보니까 다른 custom 클래스에 대한 객체를 생성하는 부분은 없습니다.


그러면 지금까지 살펴본 클래스들이 GoogleSearch.java 를 Page Object를 사용하는 버전으로 바꿨을 때 만든 클래스들 입니다.


PageBase.java

GoogleSearchPage.java

GoogleSearchResultPage.java

GoogleSearchSuggestPage.java

GoogleSearch_withPageObject.java


총 이렇게 5개의 파일이 사용됐습니다.


지난번 GoogleSearch.java에서는 단 한개의 파일만 사용했는데 똑 같은 일을 하는 Page Object를 만들기 위해 이렇게 파일을 5개로 쪼개서 기능을 부여 했네요.


다음 글에서는 각 클래스별로 어떤 일을 하고 전체적으로 어떻게 연관을 맺고 실행이 되는지를 알아보겠습니다.



반응형


반응형

지난 글 'Page Objects in Selenium 2 (Web Driver)' 맨 마지막에 아래와 같이 샘플 소스 코드들이 있었습니다.


이 샘플 코드들을 한번 분석해 보겠습니다.


* Here are the source codes


Project Home: https://github.com/ChonC/wtbox
wiki: https://github.com/ChonC/wtbox/wiki
PageBase source code
Page Objects test example


샘플을 보면 구글 페이지에서 검색하는 기능을 Selenium으로 테스트하는 소스들이 있습니다.


Page Objects를 사용하지 않은 버전과 Page Objects를 사용한 버전이 있습니다.


우선 Page Objects를 사용하지 않은 버전을 보겠습니다.



/**
* Testing Google search. This class does not use Page Object pattern.
* Compare it with “GoogleSearch_withPageObject.java” class, which uses Page Object pattern.
*
* This example is from
* "The 5 Minute Getting Started Guide
* (http://code.google.com/p/selenium/wiki/GettingStarted)."
*
*/
public class GoogleSearch {

         public static void main(String[] args) {
                 GoogleSearch.testGoogleSearch();
                 try{
                         GoogleSearch.testGoogleSuggest();
                 }catch(Exception e){
                         System.out.println("Unable to perform testGoogleSuggest() - " +
                                                                 e.getMessage());
                 }
         }
       
         public static void testGoogleSearch(){
         // Create a new instance of the html unit driver
         WebDriver driver = new HtmlUnitDriver();

         // And now use this to visit Google
         driver.get("http://www.google.com");

         // Find the text input element by its name
         WebElement element = driver.findElement(By.name("q"));

         // Enter something to search for
         element.sendKeys("Cheese!");

         // Now submit the form. WebDriver will find the form for us from the element
         element.submit();
       
         //*** return the next page object

         // Check the title of the page
         String pageTitle = driver.getTitle();
         System.out.println("Page title is: " + pageTitle);
         assertTrue("Got title: " + pageTitle, pageTitle.contains("Cheese!"));
         }
       
         public static void testGoogleSuggest() throws Exception {
                       
         // The Firefox driver supports javascript
         WebDriver driver = new FirefoxDriver();
       
         // Go to the Google Suggest home page
         driver.get("http://www.google.com/webhp?complete=1&hl=en");
       
         // Enter the query string "Cheese"
         WebElement query = driver.findElement(By.name("q"));
         query.sendKeys("Cheese");

         // Sleep until the div we want is visible or 5 seconds is over
         long end = System.currentTimeMillis() + 5000;
         while (System.currentTimeMillis() < end) {
         WebElement resultsDiv = driver.findElement(By.className("gsq_a"));

         // If results have been returned, the results are displayed in a drop down.
         if (resultsDiv.isDisplayed()) {
         break;
         }
         }

         // And now list the suggestions
         List<WebElement> allSuggestions = driver.findElements(By.className("gsq_a"));
       
         for (WebElement suggestion : allSuggestions) {
         System.out.println(suggestion.getText());
         }
         }
}


/**
* References:
* 1. http://stackoverflow.com/questions/10315894/selenium-webdriver-page-object
*/

맨 먼저 main() 메소드를 봐야 합니다.


가장 먼저 하는 일은 GoogleSearch에 있는 testGoogleSearch() 메소드를 호출하는 겁니다.

GoogleSearch는 이 클래스 이름이니까 이 클래스 내에 있는 testGoogleSearch() 메소드를 가장 먼저 시작하는 겁니다.


testGoogleSearch() 메소드를 보죠.


먼저 WebDriver로 HtmlUnitDreiver() 를 초기화를 하고 get() 메소드를 사용해서 http://www.google.com 으로 갑니다.

그리고 name 이 q 인 element를 찾아서 element 라는 WebElement 객체를 만들구요. 

이 element 객체에 Cheese!라고 입력을 합니다.

그리고 이 form 에 대한 submit() 을 실행합니다.


그러면 Cheese!로 검색한 페이지로 가게 되겠죠.

이 페이지의 title을 pageTitle이라는 String 객체에 담습니다.


그리고 이 pageTitle을 콘솔에 출력합니다.


그러면 콘솔에 아래와 같이 출력 됩니다.




출력하고 난 다음에는 assertTrue라는 jUnit 메소드를 사용해서 해당 페이지에 Cheese!라는 글자가 포함돼 있는지 테스트 합니다.


여기까지 하면 한가지 테스트가 완료 됐네요.


다시 main()메소드를 보면 그 다음에 testGoogleSuggest() 라는 메소드를 또 호출하는 것을 볼 수 있습니다.


이제 이 메소드가 실행될 텐데요. 한번 보겠습니다.


여기서는 WebDriver 에 FirefoxDriver() 를 세팅합니다.

여기까지 실행하면 Firefox가 오픈됩니다. (화면은 비어있구요.)

get() 메소드를 사용해서 http://www.google.com/webhp?complete=1&hl=en 로 이동합니다.



그리고 name이 q 인 Element를 찾아서 query라는 객체에 이 WebElement를 담습니다.

그리고 이 query 에 Cheese 를 입력합니다.


그러면 여기까지 진행이 됩니다.


그 다음에는 현재 시간 + 5초를 해서 end 라는 long 객체에 담네요.

그리고 while 문을 사용해서 이 5초동안 기다리게 하고 나서 className이 gsq_a 인 WebElement를 resultsDiv라는 객체에 담습니다.


이건 아마 원격으로 처리되다 보면 Ajax call 로 Auto Complete 기능을 실행하는데 시간이 걸리니까 이렇게 했나 봅니다.

이 while 문 안쪽을 보니까 resultsDiv 가 display 되면은 더이상 while 문을 돌지 않고 빠져 나가도록 break를 했네요.


그 다음을 보면 다시 className이 gsq_a 인 Element들을 List 객체인 allSuggestions 에 담습니다.

위 페이지를 Firebugs로 보면은 Auto complete으로 나온 cheesecake factory, cheesecake factory san diego, cheesecake recipe, cheese 이런 것들이 gsq_a 라는 클래스에 속해 있는걸 보실 수 있을 겁니다.




다음에는 이 allSuggestions List 객체에 대해서 for 문을 돌려서 콘솔에 출력하네요.



그러면 아래와 같이 Auto Complete 에 있었던 Suggestion들이 콘솔에 출력 됩니다.


여기까지가 GoogleSearch 클래스가 하는 일 입니다.


샘플 소스를 보면은 이 작업을 Page Objects를 사용해서 만든 샘플도 있습니다.

이름이 GoogleSearch_withPageObject 라는 클래스인데요.

위 GoogleSearch 클래스를 Page Object를 사용해서 어떻게 만드는지 잘 보여주고 있습니다.


이 클래스는 다음 글에서 정리 하겠습니다.


반응형


반응형

Selenium 2 Web Driver 의 Page Objects 에 대해 잘 정리하고 좋은 소스코드도 있는 곳을 찾아서 그 내용을 정리해 둡니다.


Page Objects in Selenium 2 (WebDriver)





참조



This project is inspired by:
Ben Burton's WebDriver Best Practices
Dave Justin’s page object post
James Cox's Using Page Objects



* Why Page Objects pattern?


반복되는 코드를 줄이기 위해.
single page object로 페이지의 internal state를 캡슐화 하기 위해
UI 의 변화는 실제 테스트 코드가 아닌 오직 single page object에만 영향을 미친다.
Code re-use : 여러 테스트 케이스에서 같은 page object를 사용할 수 있도록 한다.



* Step 1 : Page Object base class


Page Base 클래스는 extend 하기 위해 base structure와 page object의 property들을 제공한다.




* Step 2 : Extends PageBase






* Step 3 : Navigation from Page to Page


1. 사용자가 새로운 URL을 브라우저의 URL 바에 입력하는 것을 시뮬레이팅 할 때, 필요한 page object를 생성하는 것은 test case가 해야할 일 입니다.





2. 페이지에서 어떤 일은 브라우저에서 다른 페이지로 넘어가도록 만듭니다. 예를 들어 링크를 클릭하거나 form 을 submit 하는 경우가 있겠죠. 이럴 경우 다음 page object를 return 하는 것은 page object가 해야할 일입니다.


* Step 4 : Tests with Page Objects




* Here are the source codes


Project Home: https://github.com/ChonC/wtbox
wiki: https://github.com/ChonC/wtbox/wiki
PageBase source code
Page Objects test example

Thank you!

Chon Chung - @chon_chung


반응형


반응형

Selenium 2/WebDriver Quick Tips: Page Object Navigation Strategies
   
   


automated web testing (테스트 자동화) 에서 Page Object는 애플리케이션의 웹 페이지를 나타내는 클래스 혹은 객체를 말합니다. Page Object는 개발자가 웹페이지와 상호작용하는 기술적이고 상세한 내용들을 따로 떼어내서 다루기 때문에 좀 더 가독성 있고 실제 구현해야 할 비지니스에 집중할 수 있는 개발환경을 제공합니다.
  

두가지 장점으로 정리하자면 아래와 같습니다.

    * 가독성 높은 테스트 코드
    * 한 장소에 page access logic을 따로 만들어서 사용해 유지보수에도 훨씬 유리하다.



이 Page Object를 사용하지 않을 경우의 테스트는 아래와 같습니다.


    @Test
    public void shouldBeAbleToSearchForCats() {

        driver.get("http://www.trademe.co.nz");
        driver.findElement(By.id("searchString"))
                 .sendKeys("cats");
        driver.findElement(
                  By.cssSelector("button[value='Search']"))
                  .click();

        assertThat(driver.getTitle())
                         .contains("cats for sale");
        List titleElements
           = driver.findElements(
                        By.cssSelector(".listingTitle"));
        List adTitles = Lists.newArrayList();
        for(WebElement titleElement : titleElements) {
            adTitles.add(titleElement.getText());
        }

        assertThat(adTitles).isNotEmpty();
        for(String title : adTitles) {
            assertThat(title)
                 .containsIgnoringCase("cats");
        }
    }


이것을 Page Object를 사용해서 만들면 아래처럼 만들 수 있습니다.
   


    SearchPage searchPage;
    ResultsPage resultsPage;
           
    @Test
    public void shouldBeAbleToSearchForCats() {
        searchPage.open();
        searchPage.searchFor("cats");
        resultsPage.shouldDisplayTitlesWith("cats");
    }



이 경우 SearchPage 클래스는 아래와 같이 Thucydides 를 사용할 겁니다.



@DefaultUrl("http://www.trademe.co.nz")
public class SearchPage extends PageObject {

    @FindBy(id = "searchString")
    WebElement searchField;

    @FindBy(css = "button[value='Search']")
    WebElement searchButton;

    @FindBy(id = "main-box-categories")
    WebElement categoriesList;

    public SearchPage(WebDriver driver) {
        super(driver);
    }

    public void searchFor(String searchTerms) {
        searchField.sendKeys(searchTerms);
        searchButton.click();
    }

    public void selectsCategory(String category) {
        categoriesList.findElement(
            By.linkText(category)).click();
    }
}


Page Object를 가지고 navigation을 해야 될 때 사용되는 일반적인 방법은 새로운 페이지로 navigate 하고 이 page에 대한 Page Object를 return 하는 메소드를 만드는 것입니다. 예를 들어 serchFor 메소드 리스트는 아래와 같이 작성되어야 할 겁니다.


   

public void searchFor(String searchTerms) {
        searchField.sendKeys(searchTerms);
        searchButton.click();
        return switchTo(ResultsPage.class);
    }



만약 operation이 새로운 페이지로 navigate 하지 않는다면 이 Page Object 는 간단히 자기 자신을 return 하면 됩니다.


   

public SearchPage selectsCategory(String category) {
        categoriesList.findElement(
            By.linkText(category)).click();
        return this;
    }



이 접근법은 두가지 장점이 있습니다. 첫번째로 테스트의 가독성이 좀 더 편하게 됩니다.


   

SearchPage searchPage;
    ResultsPage resultsPage;
           
    @Test
    public void shouldBeAbleToSearchForCats() {
        searchPage.open();
        searchPage.searchFor("cats")
                  .selectsCategory("Pets & Animals")
                  .shouldDisplayTitlesWith("cats");
    }



또한 이 Page Objects 의 내용을 잘 몰라도 그냥 이것을 extend 해서 사용하면 됩니다. IDE 가 각 action 이후에 어떤 operation이 가능한지 그 리스트를 보여줄 겁니다.



이 접근법은 page navigation이 분명하게 정의되고 변화없이 안정적인 어플리케이션의 경우 아주 유용하게 사용될 수 있습니다. 어떤 어플리케이션들에서는 navigation이 분명하지 않은 경우가 있습니다. non-trivial navigation logic 에 따라 여러 다른 screen으로 이동하거나 (GWT 같은 AJAX-heavy 프레임워크를 사용하는 어플리케이션의 경우)가 그렇습니다.
위의 경우 return 된 Page Object를 사용해서 navigating 하는 방법은 효율적입니다. 테스트 코드에 page navigation 에 대한 내장된 로직을 넣게 되니까요. 만약에 이 로직이 non-trivial 이라면 이렇게 코딩을 하면 나중에 유지보수상의 문제가 생길겁니다. 그러면 Page Object 로부터 얻는 잇점 대신 그로인해 생기는 단점이 더 클 수도 있습니다.

반응형
이전 1 2 3 다음