13

Seleniumによって提案されたページオブジェクトパターンに従っていますが、ページ用のより特殊なWebElementを作成するにはどうすればよいですか。具体的には、ページにテーブルがあり、テーブルの特定の行を取得したり、テーブルの内容を返したりするためのヘルパー関数をいくつか作成しました。

現在、テーブルを持つ私が作成したページオブジェクトのスニペットは次のとおりです。

public class PermissionsPage  {

    @FindBy(id = "studyPermissionsTable")
    private WebElement permissionTable;

    @FindBy(id = "studyPermissionAddPermission")
    private WebElement addPermissionButton;

    ... 

}

ですから、私がやりたいのは、permissionsTableを、前述のメソッドのいくつかを備えた、よりカスタマイズされたWebElementにすることです。

例えば:

public class TableWebElement extends WebElement {
    WebElement table;
    // a WebDriver needs to come into play here too I think

    public List<Map<String, String>> getTableData() {
        // code to do this
    }

    public int getTableSize() {
        // code to do this
    }

    public WebElement getElementFromTable(String id) {
        // code to do this
    }
}

これが私が説明しようとしていることを理解できることを願っています。私が探しているのは、このカスタムWebElementを使用して、テーブル固有の追加処理を実行する方法だと思います。このカスタム要素をページに追加し、Seleniumが注釈に基づいてWeb要素をページにワイヤリングする方法を利用します。

出来ますか?もしそうなら、誰かがこれを行う方法を知っていますか?

4

10 に答える 10

30

すべてのWebDriverインターフェイスを組み合わせたインターフェイスを作成しました。

public interface Element extends WebElement, WrapsElement, Locatable {}

要素をラップするときにWebElementsが実行できるすべてのことをラップするためだけにあります。

次に、実装:

public class ElementImpl implements Element {

    private final WebElement element;

    public ElementImpl(final WebElement element) {
        this.element = element;
    }

    @Override
    public void click() {
        element.click();
    }

    @Override
    public void sendKeys(CharSequence... keysToSend) {
        element.sendKeys(keysToSend);
    }

    // And so on, delegates all the way down...

}

次に、たとえばチェックボックス:

public class CheckBox extends ElementImpl {

    public CheckBox(WebElement element) {
        super(element);
    }

    public void toggle() {
        getWrappedElement().click();
    }

    public void check() {
        if (!isChecked()) {
            toggle();
        }
    }

    public void uncheck() {
        if (isChecked()) {
            toggle();
        }
    }

    public boolean isChecked() {
        return getWrappedElement().isSelected();
    }
}

スクリプトで使用する場合:

CheckBox cb = new CheckBox(element);
cb.uncheck();

Elementクラスをラップする方法も考え出しました。ビルトインを置き換えるためにいくつかのファクトリを作成する必要がありますPageFactoryが、それは実行可能であり、多くの柔軟性を提供します。

私は自分のサイトでこのプロセスを文書化しました:

私はまた、これと他の質問に触発されたselophaneと呼ばれるプロジェクトを持っています:selophane

于 2012-12-07T19:38:12.193 に答える
4

WebElementインターフェイスを実装するWebComponentクラスを提供するWebDriverExtensionsフレームワークを使用して、カスタムWebElementsを作成できます。

カスタムWebElementを作成します

public class Table extends WebComponent {
    @FindBy(tagName = "tr")
    List<Row> rows;

    public Row getRow(int row) {
        return rows.get(row - 1);
    }

    public int getTableSize() {
        return rows.size();
    }

    public static class Row extends WebComponent {
        @FindBy(tagName = "td")
        List<WebElement> columns;

        public WebElement getCell(int column) {
            return columns.get(column - 1);
        }
    }
}

...次に、@ FindByアノテーションを使用してPageObjectに追加し、PageFactory.initElementsメソッドを呼び出すときにWebDriverExtensionFieldDecoratorを使用します

public class PermissionPage {
    public PermissionPage(WebDriver driver) {
        PageFactory.initElements(new WebDriverExtensionFieldDecorator(driver), this);
    }

    @FindBy(id = "studyPermissionsTable")
    public Table permissionTable;

    @FindBy(id = "studyPermissionAddPermission")
    public WebElement addPermissionButton;
}

...そしてそれをテストで使用します

public class PermissionPageTest {
    @Test
    public void exampleTest() {
        WebDriver driver = new FirefoxDriver();
        PermissionPage permissionPage = new PermissionPage(driver);

        driver.get("http://www.url-to-permission-page.com");
        assertEquals(25, permissionPage.permissionTable.getTableSize());
        assertEquals("READ", permissionPage.permissionTable.getRow(2).getCell(1).getText());
        assertEquals("WRITE", permissionPage.permissionTable.getRow(2).getCell(2).getText());
        assertEquals("EXECUTE", permissionPage.permissionTable.getRow(2).getCell(3).getText());
    }
}




または、 WebDriverExtensionsPageObject実装 をさらに適切に使用します

public class PermissionPage extends WebPage {
    @FindBy(id = "studyPermissionsTable")
    public Table permissionTable;

    @FindBy(id = "studyPermissionAddPermission")
    public WebElement addPermissionButton;

    @Override
    public void open(Object... arguments) {
        open("http://www.url-to-permission-page.com");
        assertIsOpen();
    }

    @Override
    public void assertIsOpen(Object... arguments) throws AssertionError {
        assertIsDisabled(permissionTable);
        assertIsDisabled(addPermissionButton);
    }
}

およびWebElementsの静的assertsメソッドを使用するJUnitRunner

import static com.github.webdriverextensions.Bot.*;

@RunWith(WebDriverRunner.class)
public class PermissionPageTest {

    PermissionPage permissionPage;

    @Test
    @Firefox
    public void exampleTest() {
        open(permissionPage);
        assertSizeEquals(25, permissionPage.permissionTable.rows);
        assertTextEquals("READ", permissionPage.permissionTable.getRow(2).getCell(1));
        assertTextEquals("WRITE", permissionPage.permissionTable.getRow(2).getCell(2));
        assertTextEquals("EXECUTE", permissionPage.permissionTable.getRow(2).getCell(3));
    }
}
于 2015-03-04T18:35:48.140 に答える
1

補足:WebElementクラスではありません。そのインターフェースは、クラスが次のようになることを意味します。

public class TableWebElement implements WebElement {

ただし、その場合は、WebDriverにあるすべてのメソッドを実装する必要があります。そして、そのちょっとやり過ぎ。

これが私がこれを行う方法です-私はセレンによって提案された提案されたPageObjectsを完全に取り除き、私自身のクラスを持つことによって「車輪の再発明」をしました。アプリケーション全体に対して1つのクラスがあります。

public class WebUI{
  private WebDriver driver;    
  private WebElement permissionTable;   

   public WebUI(){
      driver = new firefoxDriver();
   }

  public WebDriver getDriver(){
     return driver;
  }

  public WebElement getPermissionTable(){
     return permissionTable;
  }

  public TableWebElement getTable(){
     permissionTable = driver.findElement(By.id("studyPermissionsTable"));
     return new TableWebElement(this);
  }
}

そして、ヘルパークラスがあります

public class TableWebElement{
  private WebUI webUI;

 public TableWebElement(WebUI wUI){
    this.webUI = wUI;
 }

 public int getTableSize() {
    // because I dont know exactly what are you trying to achieve just few hints
    // this is how you get to the WebDriver:
    WebElement element = webUI.getDriver().findElement(By.id("foo"));

    //this is how you get to already found table:
    WebElement myTable = webUI.getPermissionTable();

 }

}

テストのサンプル:

 @Test
 public void testTableSize(){
    WebUI web = new WebUI();
    TableWebElement myTable = web.getTable();
    Assert.assertEquals(myTable.getSize(), 25);
 }
于 2012-07-25T09:26:58.373 に答える
1

これのフォローアップとして、これは私がやったことです(他の誰かがこれと同じ問題を抱えている場合に備えて)。以下は、WebElementのラッパーとして作成したクラスのスニペットです。

public class CustomTable {

private WebDriver driver;
private WebElement tableWebElement;

public CustomTable(WebElement table, WebDriver driver) {
    this.driver = driver;
    tableWebElement = table;
}

public WebElement getTableWebElement() {
    return tableWebElement;
}

public List<WebElement> getTableRows() {
    String id = tableWebElement.getAttribute("id");
    return driver.findElements(By.xpath("//*[@id='" + id + "']/tbody/tr"));
}

public List<WebElement> getTableHeader() {
    String id = tableWebElement.getAttribute("id");
    return tableWebElement.findElements(By.xpath("//*[@id='" + id + "']/thead/tr/th"));
}   
.... more utility functions here
}

そして、次のように参照して作成するすべてのページでこれを使用します。

public class TestPage {

@FindBy(id = "testTable")
private WebElement myTestTable;

/**
 * @return the myTestTable
 */
public CustomTable getBrowserTable() {
    return new CustomTable(myTestTable, getDriver());
}

私が気に入らないのは、ページがテーブルを取得したいときに「新しい」テーブルを作成することだけです。これは、テーブル内のデータが更新されたとき(つまり、セルが更新されたとき、行が追加されたとき、行が削除されたとき)に発生するStaleReferenceExeptionsを回避するために行いました。要求されるたびに新しいインスタンスを作成することを回避する方法について誰かが提案を持っている場合は、更新されたWebElementを返します。

于 2012-08-13T14:03:47.480 に答える
1

また、DefaultFieldDecoratorを拡張してカスタムWebElement実装を作成しました。これは、PageFactoryパターンでうまく機能します。ただし、PageFactory自体の使用について懸念があります。これは、「静的」アプリケーション(ユーザーの操作によってアプリのコンポーネントレイアウトが変更されない場合)でのみうまく機能するようです。

ただし、ユーザーのリストと各ユーザーの横にある削除ボタンを含むテーブル付きのページがある場合など、もう少し複雑なものを操作する必要がある場合は、PageFactoryの使用が問題になります。

これが私が話していることを説明するための例です:

public class UserList
 {
private UserListPage page;

UserList(WebDriver driver)
{
    page = new UserListPage(driver);
}

public void deleteFirstTwoUsers()
{
    if (page.userList.size() <2) throw new RuntimeException("Terrible bug!");  

    page.deleteUserButtons.get(0).click();
    page.deleteUserButtons.get(0).click();
}

class UserListPage {

@FindBy(xpath = "//span[@class='All_Users']")
List<BaseElement> userList;

@FindBy(xpath = "//span[@class='All_Users_Delete_Buttons']")
List<BaseElement> deleteUserButtons;

UserListPage(WebDriver driver)
 {
    PageFactory.initElements(new ExtendedFieldDecorator(driver), this);
 }
}

したがって、上記のシナリオでは、deleteFirstTwoUsers()メソッドを呼び出すと、「StaleElementReferenceException:stale element reference:element is not attachtothepagedocument」で2番目のユーザーを削除しようとすると失敗します。これは、「page」がコンストラクターで1回だけインスタンス化され、PageFactoryに「ユーザーの数が減少したことを知る方法がない」ために発生します:)。

私がこれまでに見つけた回避策は、エーテルです:

1)このようなメソッドにはPage Factoryをまったく使用せず、ある種のwhileループでWebDriverを直接使用しますdriver.findElementsBy()...

2)または次のようなことをします:

public void deleteFirstTwoUsers()
{
    if (page.userList.size() <2) throw new RuntimeException("Terrible bug!");  

    new UserListPage(driver).deleteUserButtons.get(0).click();
    new UserListPage(driver).deleteUserButtons.get(1).click();
}

3)またはこれ:

public class UserList {
  private WebDriver driver;

UserList(WebDriver driver)
{
    this.driver = driver;
}

UserListPage getPage { return new UserListPage(driver);})
public void deleteFirstTwoUsers()
{
    if (getPage.userList.size() <2) throw new RuntimeException("Terrible bug!");  

    getPage.deleteUserButtons.get(0).click();
    getPage.deleteUserButtons.get(0).click();
}

ただし、最初の例では、ラップされたBaseElementを使用してもメリットはありません。そして、2番目と3番目では、ページで実行するすべてのアクションに対して、PageFactoryの新しいインスタンスを作成することになります。だから私はそれがすべて2つの質問に帰着すると思います:

1)本当に、PageFactoryを何かに使う価値があると思いますか?次のように、各要素をその場で作成するのは非常に「安価」です。

class UserListPage
{
private WebDriver driver;

UserListPage(WebDriver driver)
{
    this.driver = driver;
}

List<BaseElement> getUserList() {
    return driver.findElements(By.xpath("//span[@class='All_Users']"));
}

2)@Override driver.findElementsメソッドを使用して、WebElementではなくラップされたBaseElementオブジェクトのインスタンスを返すことは可能ですか?そうでない場合、代替手段はありますか?

于 2018-10-12T21:28:03.960 に答える
0

なぜWebElementを拡張するのが面倒なのですか?実際にSeleniumパッケージを開発しようとしていますか?そうであれば、テーブルに固有の要素だけでなく、使用するすべてのWeb要素に追加を適用できない限り、そのメソッドを拡張することは意味がありません。

次のようなことをしてみませんか。

public class PermissionsPage extends TableWebElement{

...permissions stuff...

}

import WebElement

public class TableWebElement{

...custom web elements pertaining to my page...

}

それがあなたの質問に答えるかどうかはわかりませんが、それは何よりもクラスアーキテクチャの質問であるように私には思えました。

于 2012-07-25T04:15:00.293 に答える
0

PageObject上のPageObjectを表す「SubPageObject」と呼ばれるものを作成します。

利点は、カスタムメイドのメソッドを作成でき、このSubPageObject内で本当に必要なWebElementのみを初期化できることです。

于 2012-07-25T07:33:16.893 に答える
0

Web要素ラッパーを作成してみませんか?このような?

class WEBElment
{

public IWebElement Element;

public WEBElement(/*send whatever you decide, a webelement, a by element, a locator whatever*/)

{

Element = /*make the element from what you sent your self*/

}


public bool IsDisplayed()

{

return Element.Displayed;

}

} // end of class here

ただし、クラスをこれよりも複雑にすることができます。ただのコンセプト

于 2012-08-03T19:38:23.810 に答える
0

htmlelementsフレームワークを見てください、それはまさにあなたが必要としているもののように見えます。チェックボックス、ラジオボックス、テーブル、フォームなどの共通要素が事前に実装されているため、独自の要素を簡単に作成し、1つの要素を他の要素に挿入して明確なツリー構造を作成できます。

于 2015-09-09T06:14:31.117 に答える
0

手順1)インターフェイスElementを拡張するという基本クラスを作成しますIWrapsElement

 public class Element : IWrapsElement
{
    public IWebElement WrappedElement { get; set; }
    public Element(IWebElement element)
    {
        this.WrappedElement = element;
    }
}

ステップ2)カスタム要素ごとにクラスを作成し、クラスから継承しElementます

 public class Checkbox : Element
{
    public Checkbox(IWebElement element) : base(element) { }

    public void Check(bool expected)
    {
        if (this.IsChecked()!= expected)
        {
            this.WrappedElement.Click();
        }
    }

    public bool IsChecked()
    {
        return this.WrappedElement.GetAttribute("checked") == "true";
    }
}

使用法:

a)

Checkbox x = new Checkbox(driver.FindElement(By.Id("xyz")));
x.Check(true)

b)

 private IWebElement _chkMale{ get; set; }
 [FindsBy(How = How.Name, Using = "chkMale")]

 private Checkbox ChkMale
    {
        get { return new Checkbox (_chkMale); }
    }
ChkMale.Check(true);
于 2017-07-20T09:42:31.577 に答える