2016-10-07 1 views
3

Je ne peux utiliser que des valeurs codées en dur dans les annotations @FindBy de l'objet page Mais je voudrais résoudre dynamiquement les localisateurs.Selenium Page Objet. Comment lire le localisateur @FindBy depuis une source externe?

public class LoginPage extends BasePage { 

    // hardocded value works ok 
    @FindBy(name = "login field") 
    WebElement usernameFld; 

    // does not compile 
    // this is a kind of what I would like to have 
    @FindBy(getLocatorFromExternalSource()) 
    WebElement passwordFld; 

} 

Je l'ai vu quelques messages mentionnant que ces choses peuvent être résolus en mettant en place des annotations personnalisées/décorateurs/usines mais n'a pas trouvé des exemples encore.

QUESTION: Quelqu'un peut-il s'il vous plaît donner un exemple de la façon de mettre en œuvre ElementLocatorFactory sur mesure afin localisateurs pourraient être résolus dynamiquement?

Je sais que je peux utiliser des appels simples de style ancien comme:

driver.findElement(getLocatorFromExternalSource("passwordFld")).click() 

mais je voudrais utiliser

passwordFld.click() 

à la place.

+0

Vos localisateurs changer beaucoup? Pourquoi voulez-vous les lire à partir d'un fichier externe au lieu de simplement les placer dans vos objets de page où ils sont facilement trouvés et organisés? – JeffC

+0

1. J'essaye d'écrire le test agnostique de plate-forme pour des dispositifs d'IOS et android et ne veux pas dupliquer des objets de page 2. La localisation d'UI pourrait être le point suivant où les localisateurs externalisés sont utiles 3. Certains locators peuvent changer dans l'exécution (éléments triés dans un autre ordre, etc) – ludenus

Répondre

0

Les annotations sont des méta-données. Elles doivent donc être disponibles au démarrage lors du chargement de la classe. ce que vous cherchez n'a pas de réponse directe, mais les hacks sont là en utilisant Reflections, IMHO je l'éviterais et si vous avez besoin d'externaliser les locators alors je vous suggère d'implémenter Object repository où vous liriez vos locators à l'exécution à la volée une source externe.

0

Personnellement, je ne suis pas un grand fan de PageFactory choses mais si vous l'utilisez déjà, je ferais quelque chose comme ci-dessous.

public class LoginPage extends BasePage 
{ 
    WebDriver driver; 
    WebElement usernameFld; 
    @FindBy(name = "login field") 
    WebElement usernameIOs; 

    @FindBy(name = "something else") 
    WebElement usernameAndroid; 

    public LoginPage(WebDriver webDriver, Sites site) 
    { 
     this.driver = webDriver; 
     switch (site) 
     { 
      case IOS: 
       usernameFld = usernameIOs; 
       break; 
      case ANDROID: 
       usernameFld = usernameAndroid; 
       break; 
     } 
    } 

    public void setUsername(String username) 
    { 
     usernameFld.sendKeys(username); 
    } 
} 

ailleurs vous définiriez

public enum Sites 
{ 
    IOS, ANDROID 
} 

Je préfère déclarer localisateurs puis gratter les éléments que je les ai besoin. Je trouve que cela est beaucoup plus performant et vous avez des problèmes d'éléments moins obsolètes, etc.

public class LoginPage extends BasePage 
{ 
    WebDriver driver; 
    By usernameLocator; 
    By usernameIOsLocator = By.name("login field"); 
    By usernameAndroidLocator = By.name("something else"); 

    public LoginPage(WebDriver webDriver, Sites site) 
    { 
     this.driver = webDriver; 
     switch (site) 
     { 
      case IOS: 
       usernameLocator = usernameIOsLocator; 
       break; 
      case ANDROID: 
       usernameLocator = usernameAndroidLocator; 
       break; 
     } 
    } 

    public void setUsername(String username) 
    { 
     driver.findElement(usernameLocator).sendKeys(username); 
    } 
} 
+0

Merci pour la réponse. Je pense que j'ai eu ton idée. L'avantage est que tout est clair en choisissant explicitement le localisateur. Inconvénient est que vous auriez à modifier tous vos cas de commutateur pour tous les objets de la page une fois que vous ajoutez une nouvelle plate-forme, disons IOS9, IOS10, iPad. Je préférerais ajouter/changer la config de page et garder le code intact. – ludenus

+0

Ce n'est vraiment pas si différent de ce que vous faites. Lorsque vous ajoutez une nouvelle configuration, vous devez ajouter un nouveau localisateur pour chaque élément de chaque page de votre fichier de configuration. Je devrais faire la même chose ici. La différence est, mon chemin garde la méthodologie de l'objet de la page propre en ce que tout ce qui a trait à la page est dans l'objet page. Votre fichier de configuration va devenir trop lourd car il contient tous les localisateurs pour tous les éléments multipliés par le nombre de configs que vous supportez. – JeffC

+0

Je suis d'accord - le nombre de localisateurs est le même. Mais je préfère garder les données de test à part du code. – ludenus

0

J'ai trouvé quelques indices ici: https://stackoverflow.com/a/3987430/1073584 et finalement réussi à obtenir ce que je voulais en mettant en place 3 classes: CustomPageFactory, CustomElementLocator, CustomAnnotations.

Maintenant, je suis en mesure d'extérioriser mes localisateurs à Typesafe config et utiliser le code suivant pour initialiser la page des objets

// ======= LoginPage

public class LoginPage extends AbstractPage { 
    Config config; 

    @FindBy(using = "CONFIG") // take locator from config 
    WebElement passwordFld; 

    // .. other fields skipped 


    public LoginPage(WebDriver webDriver, Config config) throws IOException { 
     super(webDriver); 
     this.config = config; 
     PageFactory.initElements(new CustomPageFactory(webDriver, config), this); 
    } 

} 

// == === login.page.typesafe.config:

passwordFld = { 
    name = "password field" 
} 

// ============= CustomPageFactory

package com.company.pages.support; 

import com.typesafe.config.Config; 
import org.openqa.selenium.WebDriver; 
import org.openqa.selenium.support.pagefactory.ElementLocator; 
import org.openqa.selenium.support.pagefactory.ElementLocatorFactory; 

import java.lang.reflect.Field; 

public class CustomPageFactory implements ElementLocatorFactory { 
    private Config config; 
    private WebDriver driver; 

    public CustomPageFactory(WebDriver driver, Config config) { 
     this.driver = driver; 
     this.config = config; 
    } 

    public ElementLocator createLocator(Field field) { 
     return new CustomElementLocator(driver, field, config); 
    } 
} 

// ================ = CustomElementLocator

package com.company.pages.support; 

import com.typesafe.config.Config; 
import org.openqa.selenium.SearchContext; 
import org.openqa.selenium.support.pagefactory.DefaultElementLocator; 

import java.lang.reflect.Field; 

public class CustomElementLocator extends DefaultElementLocator { 

    private Config config; 

    public CustomElementLocator(SearchContext searchContext, Field field, Config config) { 
     super(searchContext, new CustomAnnotations(field, config)); 
     this.config = config; 
    } 


} 

// ====== CustomAnnotations

package com.company.pages.support; 

import com.typesafe.config.Config; 
import com.typesafe.config.ConfigObject; 
import org.openqa.selenium.By; 
import org.openqa.selenium.support.FindBy; 
import org.openqa.selenium.support.pagefactory.Annotations; 

import java.lang.reflect.Field; 

public class CustomAnnotations extends Annotations { 

    Config config; 

    public CustomAnnotations(Field field, Config config) { 
     super(field); 
     this.config = config; 
    } 

    @Override 
    protected By buildByFromShortFindBy(FindBy findBy) { 

     if (findBy.using().equals("CONFIG")) { 

      if (null != config) { 

       ConfigObject fieldLocators = config.getObject(getField().getName()); 

       if (fieldLocators.keySet().contains("className")) 
        return By.className(fieldLocators.get("className").unwrapped().toString()); 

       if (fieldLocators.keySet().contains("css")) 
        return By.cssSelector(fieldLocators.get("css").unwrapped().toString()); 

       if (fieldLocators.keySet().contains("id")) 
        return By.id(fieldLocators.get("id").unwrapped().toString()); 

       if (fieldLocators.keySet().contains("linkText")) 
        return By.linkText(fieldLocators.get("linkText").unwrapped().toString()); 

       if (fieldLocators.keySet().contains("name")) 
        return By.name(fieldLocators.get("name").unwrapped().toString()); 

       if (fieldLocators.keySet().contains("partialLinkText")) 
        return By.partialLinkText(fieldLocators.get("partialLinkText").unwrapped().toString()); 

       if (fieldLocators.keySet().contains("tagName")) 
        return By.tagName(fieldLocators.get("tagName").unwrapped().toString()); 

       if (fieldLocators.keySet().contains("xpath")) 
        return By.xpath(fieldLocators.get("xpath").unwrapped().toString()); 
      } 

     } 

     return super.buildByFromShortFindBy(findBy); 
    } 

} 
+1

merci pour votre aide, mais cette solution est applicable pour le sélénium v2.53. J'ai mis à jour par selenium-java à 3.5.3 et ne pouvais pas voir la méthode buildByFromShortFindBy appartenir à la classe Annotations plus, il a été déplacé à AbstractFindByBuilder et donc se demandant comment câbler et redéfinir cette fonction pour implémenter mon CustomPageFactory? –

+0

@BhuvaneshMani vous avez raison, ce code ne fonctionne plus avec les nouvelles versions de sélénium – ludenus

0

Je pense que vous vous demandez d'effectuer les opérations sur WebElements par leur nom

U besoin de mettre un peu de code dans votre constructeur

public AnyConstructorName(AndroidDriver<AndroidElement> driver) { 
     this.driver =driver; 
     PageFactory.initElements(driver, this); 
} 

code ci-dessus ne fonctionnera que si vous acceptez le conducteur d'une autre classe Si supprime pas le cas this.driver = pilote de votre constructeur

Initialiser les éléments web comme

@FindBy(xpath ="//android.widget.TextView[@text='Payments']") 

WebElement pay; 

u peut effectuer pay.click();

ou toute autre opération que vous souhaitez

Mais peut effectuer ces actions au sein de votre classe page seulement

+0

'@FindBy (xpath =" // android.widget.TextView [@ text = 'Paiements'] ")' Je cherchais un moyen PAS pour localiser le localisateur dans @FindBy mais pour le lire à partir d'une source externe – ludenus