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

최근에 받은 트랙백

글 보관함

Selenium WebDriver - PageFactory

2013. 10. 20. 02:14 | Posted by 솔웅


The PageFactory



PageObject 패턴을 지원하기 위해 Webdriver가 지원하는 라이브러리에는 factory 클래스가 포함돼 있습니다.



A Simple Example


PageFactory를 사용하기 위해 우선 WebElements나 List<WebElement>인 PageObject의 필드들을 정의해야 합니다.


예제를 보겠습니다.




package org.openqa.selenium.example;

import org.openqa.selenium.WebElement;

public class GoogleSearchPage {
    // Here's the element
    private WebElement q;

    public void searchFor(String text) {
        // And here we use it. Note that it looks like we've
        // not properly instantiated it yet....
        q.sendKeys(text);
        q.submit();
    }
}

이 코드가 제대로 작동되도록 하려면 q field를 제대로 초기화 시켜줘야 합니다.
여기서 우리는 PageObject를 초기화 해 줄 필요가 있습니다.

package org.openqa.selenium.example;

import org.openqa.selenium.WebDriver;
import org.openqa.selenium.WebElement;
import org.openqa.selenium.htmlunit.HtmlUnitDriver;
import org.openqa.selenium.support.PageFactory;

public class UsingGoogleSearchPage {
    public static void main(String[] args) {
        // Create a new instance of a driver
        WebDriver driver = new HtmlUnitDriver();

        // Navigate to the right place
        driver.get("http://www.google.com/");

        // Create a new instance of the search page class
        // and initialise any WebElement fields in it.
        GoogleSearchPage page = PageFactory.initElements(driver, GoogleSearchPage.class);

        // And now do the search.
        page.searchFor("Cheese");
    }
}


Explanation

The PageFactory relies on using sensible defaults: the name of the field in the Java class is assumed to be the "id" or "name" of the element on the HTML page. That is, in the example above, the line:


   q.sendKeys(text);



is equivalent to:


    driver.findElement(By.id("q")).sendKeys(text);



저기에서 사용된 driver 인스턴스는 PageFactory의 initElement 메소드에 전달된 그 인스턴스 입니다.


위 예제에서 우리는 PageFactory를 PageObject 인스턴스를 초기화 하는데 사용했습니다. 이렇게 함으로서 첫번째로 인수로 (public SomePage(WebDriver driver) {) WebDriver를 받는 생성자를 찾습니다. 만약에 그 생성자가 없으면 디폴트 생성자가 호출됩니다. 어떤 경우에는 PageObject는 WebDriver interface의 인스턴스말고도 다른것들을 필요로 합니다. 이미 생성된 객체의 element를 초기화하기 위해 PageFactory 가 사용 될 수도 있습니다.



ComplexPageObject page = new ComplexPageObject("expected title", driver);

// Note, we still need to pass in an instance of driver for the
// initialised elements to use
PageFactory.initElements(driver, page);



Making the Example Work Using Annotations



예제를 돌리면 PageFactory는 해당 페이지에서 이 클래스에 있는 WebElement의 필드 이름과 match 되는 element를 찾을 겁니다. 일단 ID 속성과 일치하는 element를 찾겠죠. 만약 찾지 못하면 PageFactory는 다시 뒤로 가서 그 name 속성의 value로 element를 찾을 겁니다.


이 코드가 작동 되더라도 Google 홈페이지의 소스코드를 잘 몰라서 field 의 이름이 q 라는 것을 모를겁니다. 다행히 우리는 annotation을 사용해서 이에 대해 알릴 수 있습니다..



package org.openqa.selenium.example;

import org.openqa.selenium.By;
import org.openqa.selenium.support.FindBy;
import org.openqa.selenium.support.How;
import org.openqa.selenium.WebElement;

public class GoogleSearchPage {
    // The element is now looked up using the name attribute
    @FindBy(how = How.NAME, using = "q")
    private WebElement searchBox;

    public void searchFor(String text) {
        // We continue using the element just as before
        searchBox.sendKeys(text);
        searchBox.submit();
    }
}



한가지 걸리는 부분은 WebElement에 대해 메소드를 호출할 때마다 driver 가 현재 페이지에서 다시 그것을 찾을 거라는 겁니다. AJAX-heavy 어플리케이션에서 이것은 당연히 일어나는 일로 생각 될 수도 있습니다. 하지만 Google search 페이지에서는 그 element가 항상 거기에 있고 변하지 않을 거라는 것을 알고 있습니다. 그리고 우리는 해당 페이지이외의 페이지에서 작업하지 않을거라는 것도 알고 있구요. (같은 이름의 다른 element가 없을 거라는 것을 알고 있다는 얘기죠. 우리가 한번 찾아본 것은 그 element를 캐쉬에 넣어두면 더 유용할 겁니다.



package org.openqa.selenium.example;

import org.openqa.selenium.By;
import org.openqa.selenium.support.CacheLookup;
import org.openqa.selenium.support.FindBy;
import org.openqa.selenium.support.How;
import org.openqa.selenium.WebElement;

public class GoogleSearchPage {
    // The element is now looked up using the name attribute,
    // and we never look it up once it has been used the first time
    @FindBy(how = How.NAME, using = "q")
    @CacheLookup
    private WebElement searchBox;

    public void searchFor(String text) {
        // We continue using the element just as before
        searchBox.sendKeys(text);
        searchBox.submit();
    }
}


Reducing Verbosity



위의 예제는 아직도 좀 너무 복잡한거 같습니다. annotating을 약간 바꿔서 더 간단하게 만들겠습니다.



public class GoogleSearchPage {
  @FindBy(name = "q")
  private WebElement searchBox;

  // The rest of the class is unchanged.
}



Notes

    PageFactory를 사용하면 필드들이 초기화 됐다라고 가정하실 수 있습니다. 만약 PageFactory를 사용하지 않는다면 field들이 초기화 됐다고 가정하게 되면 NullPointerExceptions 가 발생할 겁니다.
    List<WebElement> 필드는 @FindBy 나 @FindBys annotation 이 있을 경우에 사용합니다. 디폴트 검색 strategy는 by id 나 by name 입니다. 한 페이지 내에 같은 아이디나 name을 가지는 엘리먼트들이 여러개 존재하는 경우는 드물기 때문에 WebElement 필드는 list로 사용하기 적합합니다.
    WebElements are evaluated lazily. 만약 PageObject에서 WebElement를 전혀 사용하지 않는다면 findElement를 호출하는 일은 없을 겁니다.
    The functionality works using dynamic proxies. 이 의미는 특정 subclass로서 WebElement를 기대해서는 안된다는 것입니다. 설사 여러분이 driver의 type을 알고 있더라도요. 예를 들어 HtmlUnitDriver를 사용하신다면 HtmlUnitWebElement의 인스턴스와 함께 WebElement 필드가 초기화 되기를 기대하시면 안 됩니다.

반응형

Comment