GithubHelp home page GithubHelp logo

behatpageobjectextension's People

Stargazers

 avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar

Watchers

 avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar

behatpageobjectextension's Issues

Taking screenshots with Selenium2

Hi everyone,

I'd like to be able to take screenshots with Selenium2 for each failed step (with the AfterStep hook in FeatureContext.php).

But since now my FeatureContext extends PageObjectContext, which extends BehatContext, I can't get to ask the WebDriver for a screencap directly from the FeatureContext, this has to be done in each Page.

I've read the Assertion inside page object issue, and I understand why you don't want to extend MinkContext for most cases. But then how else can one have a "clean" way of getting screenshots?

I'm fairly new to PHP, so please excuse me if it's a dumb or trivial question.
For now I added this snippet to each page :

public function getScreenshot(){
    return $this->getSession()->getDriver()->getScreenshot();
}

And I call it after each step in FeatureContext :

/**
 * @AfterStep
 */
public function screenshotFailings($event)
{
    // 4 corresponds to the FAILED StepEvent state
    if($event->getResult() !== 4) {
        return;
    }
    $screenshot = $this->page->getScreenshot();
    if (is_null($screenshot)) {
        return;
    }
    $target_dir = "reports/screenshots/".$event->getLogicalParent()->getFeature()->getTitle().'_'.$event->getLogicalParent()->getTitle();
    if (!is_dir($target_dir)) {
        mkdir($target_dir, 0777, true);
    }
    $filename = sprintf('%s.%s', date('c'), 'png');
    file_put_contents($target_dir.'/'.$filename, $screenshot);
}

Wouldn't it be more efficient to just extend MinkContext in PageObjectContext or am I just missing something?

Access to entity manager inside page object in V3

Hi,

Using behat V3, i'm trying to do what I used to do in V2 with page object: convert a name into an ID in order to access the url.

Simple example:

class AdminListFaq extends Page
{
    /**
     * @var string $path
     */
    protected $path = '/{id}/faq/list';

    /**
     * {@inheritdoc}
     */
    protected function getPath()
    {
       $site = $this->entityManager->getRepository('Model:Site')->findOneByHost($this->parameter);

        return strtr($this->path, ['{id}' => $site->getId()]);
    }

And in Page

/**
 * Page
 */
class Page extends BasePage
{
    /**
     * The symfony kernel
     *
     * @var KernelInterface $kernel
     */
    protected $kernel;

    /**
     * The symfony container
     * @var Symfony\Component\DependencyInjection\ContainerInterface container
     */
    protected $container;

    /**
     * The doctrine entity manager
     * @var Doctrine\ORM\EntityManager
     */
    protected $entityManager;

    /**
     * The parameter
     *
     * @var mixed parameter
     */
    protected $parameter;

    /**
     * Set KernelInterface
     *
     * @param KernelInterface $kernel
     */
    public function setKernel(KernelInterface $kernel)
    {
        $this->kernel        = $kernel;
        $this->container     = $kernel->getContainer();
        $this->entityManager = $this->container->get('doctrine.orm.entity_manager');
    }

.....

It was working in V2, but not in V3 (the entityManager is null). Am I missing something please ?

PS: My Behat config is :

default:
    autoload: %paths.base%/features/Context
    extensions:
        Behat\MinkExtension:
            base_url:   http://example.dev/admin_behat.php
            files_path: apps/features/files
            default_session:    goutte
            javascript_session: selenium2
            goutte:    ~
            selenium2: ~
        Behat\Symfony2Extension:
            kernel:
                env:   behat
                debug: true
                path: admin/AdminKernel.php
                class: AdminKernel
                bootstrap: apps/autoload.php
        SensioLabs\Behat\PageObjectExtension:
            namespaces:
                page: Context\Page
                element: Context\Page\Element
        VIPSoft\DoctrineDataFixturesExtension\Extension:
            lifetime:    feature
            autoload:    false
            directories:
                - apps/features/Context/Fixtures
            fixtures:    ~
            migrations: ~
        Sanpi\Behatch\Extension: ~
    suites:
        default:
            contexts:
                - Context\FeatureContext
                - Context\UserContext
                - Context\PageContext
                - behatch:browser
                - behatch:debug

Wait till element is presented

Hi there
Just wondered how we should wait elements
For ex.

 public function openLoginForm() {
        $login_link = $this->findLink('Login');
        if ($login_link) {
            $login_link->click();
        }
        // The next page element will be presented after js is finished.
       // for now. it always ElementNotFoundException
        return $this->getElement('Login Popup');
    }

I used to wait ajax with the following

$this->getSession()->wait(30000, '(typeof(jQuery)=="undefined" || (0 === jQuery.active && 0 === jQuery(\':animated\').length))');

but page classes seem are not good places to work with session

What is a suggestion to wait elements on a page?

Thanks

Fatal error: Call to a member function open() on null

My project works on Vagrant with Docker Engine. I have the follow configurations in behat:

###behat.yml
default:

    extensions:
        Behat\MinkExtension:
            base_url: "http://192.168.33.10/app_test.php/"
#            default_session: chrome
            javascript_session: chrome
            sessions:
                chrome:
                    selenium2:
                        wd_host: "http://192.168.33.10:4444/wd/hub/"
                        browser: chrome
                        capabilities:
                            browserName: chrome
                            browser: chrome
                            version: ""
                            chrome:
                                switches:
                                    - "start-fullscreen"
                                    - "start-maximized"
                                    - "no-sandbox"

        SensioLabs\Behat\PageObjectExtension: ~

### sign_in.feature
@security
Feature: Sign in into Application

    @ui
    Scenario: Search
        Given I am on the login
        When I fill the username with "[email protected]"
        And I fill the password with "password"
        When I want a login
        Then I should see "Credenciales no válidas."
class SecurityContext implements Context
{

private $loginPage;

public function __construct(LoginPage $loginPage)
{
  $this->loginPage = $loginPage;
}

/**
 * @Given I am on the login
 */
public function iAmOnTheLogin()
{
   $this->loginPage->open();
}

When I run behat it fails and throws me the error: Fatal error: Call to a member function open() on null (Behat\Testwork\Call\Exception\FatalThrowableError)

Decouple PageObjects from Behat Implementation?

Hi there,

I have been thinking of using the page object pattern in a way to share the Objects with Symfony Webtestcases and found that the entire SensioLabs\Behat\PageObjectExtension\PageObject namespace could become an own library, making it possible to build Pageobjects for an application reusable among different test suites.

Technically it is already possible to use the PageObject Namespace in any context, however putting them in their own lib would be cleaner and not needed, Behat specific classes are not included in the project.

What do you think?

verifyUrl() can report expected and found URL as the same

verifyUrl() checks the current URL against the expected URL. If they are different, it throws UnexpectedPageException and generates the text of the exception message by calling the methods again to get the current and expected URL. By the time of the 2nd call the browser can be on the expected page, resulting in a confusing message like:

exception 'SensioLabs\Behat\PageObjectExtension\PageObject\Exception\UnexpectedPageException' with message 'Expected to be on "http://owncloud:8889/index.php/settings/users" but found "http://owncloud:8889/index.php/settings/users" instead' in /home/travis/build/[secure]/core/lib/composer/sensiolabs/behat-page-object-extension/src/SensioLabs/Behat/PageObjectExtension/PageObject/Page.php:217

It would be nice to know what were the original values of current and expected URL that were (hopefully) different and caused the test to fail.

Option to ignore protocol in verifyUrl

I encountered a problem for a particular case where only one page uses HTTPS but the verifyUrl kept throwing an Exception as the expected and actual URL are different upon redirection on HTTPS.

Currently I overriden the verifyUrl to ignore the protocol used, however it would be great to have this configurable through the pageParameters (maybe it is already present? Am I missing something?).

Idea would be to specify the allowed protocols in the parameters.

Remove verify() from $page->open() function

In open() function you have verify() function that check opened URL with current but this is wrong solution. For example: you have http://example.com/url1 that redirect you to http://example.com/url2. You want to check this redirection but with this implementation of open() function this wouldn't work.

All checkers and asserts shouldn't be in action function.

 public function open(array $urlParameters = array())
    {
        $url = $this->getUrl($urlParameters);
        $this->getDriver()->visit($url);
        $this->verify($urlParameters); //this should be removed
        return $this;
    }

I suggest to remove this from open() function.

getDriver method is not available in Page class

When I trying to use open() function then I see error message:

"getDriver" method is not available on the LoginPage (BadMethodCallException)

I noticed that it's true, Page class doesn't have this method. I use $this->getSession()->getDriver(); instead of $this->getDriver(); and it's works.

Maybe this is a simple mistake?

Dupplicate session window created

Hi all,

I'm trying to implement the PageObject extension into an existing Behat\Mink test harness. Before trying, I've been having some success with the following architecture:

  • a ParentContext loaded as default
  • specific context files related to one or more feature files
  • Mink sessions started and stopped respectively in the @beforeScenario and the @afterScenario hooks

I'm starting on a new suite and thought I'd give the following architecture a try:

  • still a ParentContext loaded as default
  • a PopinContext related to a popin/connected.feature file
  • a secondary LoginContext referenced in PopinContext through the $environment available in the BeforeScenarioHookScope with one function called in a PopinContext step
  • a LoginPage object that I would like to open

If I leave the $session->start() in my ParentContext, a first browser window spawns, like it would before, but before any other instructions are executed (even if I comment out the $loginPage->open(), a second browser window spawns.

If I remove the $session->start() in the ParentContext, any subsequent attempt at $loginPage->open() fails, as if it would need a session to not use it at all.

Could someone give me his/her opinion on the architecture I've chosen and maybe some pointers on what I'm doing wrong ?

Thanks in advance

Invalid xpath from Selenium when using find in Element subclass

Hi,
Is this a bug in BehatPageObjectExtension?

We get this error:

exception 'Selenium\Exception' with message 'Unexpected response from Selenium server:
ERROR: Invalid xpath [2]: (///descendant-or-self::*[@id = 'del_name_4'])[1]' 
in /Users/vs/MyProjectNameHere/vendor/alexandresalome/php-selenium/src/Selenium/Driver.php:87

And this is the type of code we use:

use SensioLabs\Behat\PageObjectExtension\PageObject\Element;

class TestModule extends Element
{
    protected $selector = array('xpath' => '//div[@id="mod"]');

    public function doSomething()
    {
        $element = $this->find('css', 'del_name_4');
    }

}

User Notice: Undefined property: Page\HomePage::$factory

The entire error message:

 User Notice: Undefined property: Page\HomePage::$factory in vendor/sensiolabs/behat-page-object-extension/src/SensioLabs/Behat/PageObjectExtension/PageObject/Page.php on line 132 in /private/var/folders/8w/4f5rtpzx61dcskp4ysvp18fr0000gn/T/ProxyManagerGeneratedProxy__PM__PageHomePageGenerateddf1a4d12fa9461ec568e547ef31a67a0.php line 446

My behat.yml is

default:
  suites:
    features:
      paths:
         - "%paths.base%/features/"
      contexts:
        - HomeContext
  extensions:
    Behat\MinkExtension:
      browser_name: 'chrome'
      goutte: ~
      javascript_session: selenium2
      selenium2:
        wd_host: http://0.0.0.0:4444/wd/hub
        capabilities: { "browser": "chrome", "version": "", 'chrome': {'switches':['--start-maximized']}}
      base_url: https://www.twlw.dev
    SensioLabs\Behat\PageObjectExtension: ~
    LeanPHP\Behat\CodeCoverage\Extension:
      auth:       ~
      drivers:
        - local
      filter: 
        whitelist:
          include:
            directories:
              'src':
                prefix: 'src'
              'tests':
                prefix: 'src'      
      report:
        format:   html
        options:
          target: coverage/behat

My HomeContext is

<?php
require_once __DIR__ . '/../../vendor/autoload.php';
require_once __DIR__ . '/../../vendor/phpunit/phpunit/src/Framework/Assert/Functions.php';
require_once __DIR__ . '/../../src/page-objects/page/HomePage.php';

use Behat\Behat\Context\Context;
use Page\HomePage;
use Page\ContactPage;
use Page\ContactConfirmation;
use SensioLabs\Behat\PageObjectExtension\PageObject\Page;

class HomeContext extends FeatureContext {
    protected $homepage;
    
    public function __construct(HomePage $homepage) {
        $this->homepage = $homepage;
    }
    /**
     * @When I go to the :arg1 page
     */
    public function iGoToThePage($arg1) {
        if ($arg1 === 'home') {
            $this->homepage->open();            
            assertTrue($this->homepage->verifyPage());
        }        
    }
    /**
     * @Then I should see :arg1 link
     */
    public function iShouldSeeLink($arg1)
    {
        //\Psy\Shell::debug(get_defined_vars(),$this);
        assertNotNull($this->homepage->hasElement($arg1));
    }
}

My FeatureContext:

<?php
require_once __DIR__ . '/../../vendor/autoload.php';
require_once __DIR__ . '/../../vendor/phpunit/phpunit/src/Framework/Assert/Functions.php';
require_once __DIR__ . '/../../src/page-objects/page/HomePage.php';

use Behat\Behat\Context\Context;
use Page\HomePage;
use Page\ContactPage;
use Page\ContactConfirmation;
use SensioLabs\Behat\PageObjectExtension\PageObject\Page;

class FeatureContext extends Page implements Context, \Behat\Behat\Context\SnippetAcceptingContext
{

    /**
     * @param Session $session
     * @param Factory $factory
     * @param array   $parameters
     */
    public function __construct(Session $session, Factory $factory, array $parameters = array())
    {
        parent::__construct($session, $factory, $parameters);
    }

    /**  @BeforeScenario   */
    public function before( $scope)  {
        $this->driver = new \Behat\Mink\Driver\Selenium2Driver('chrome');
        $this->session = new \Behat\Mink\Session($this->driver);
        $this->session->start();

    }
    /**   @AfterScenario  */
    public function after($scope)
    {
        $this->session->stop();
    }
    /**
     * @Given I am a visitor
     */
    public function iAmAVisitor()
    {
        return true;
    }

}

My HomePage:

<?php
namespace Page;
use SensioLabs\Behat\PageObjectExtension\PageObject\Page;
class HomePage extends Page {
    protected $path='/';

    protected $elements = array(
          'My Account' => 
        [
            'xpath' => '//a[contains(text(),"My Account")]'
        ],
        'Dashboard' => 
        [
            'xpath' => '//a[contains(text(),"Dashboard")]'
        ],
        'Log out' => 
        [
            'xpath' => '//a[contains(text(),"Log out")]'
        ]
    );

    public function verifyPage() {
        return ($this->getDriver()->getCurrentUrl()
                ===
                $this->getParameter('base_url')
                . $this->path);
    }
}

What is best practice for using BPOE?

It seems to me, when using BPOE, that each Feature needs to have it's own pair or Context & POM. That keeps things organized, at least in my mind.

It also means there is duplicated code. I currently have 2 Features and both of my Contexts have "I am on the ?? page" and "I should see the ?? link". I want to make sure when confirming certain links are visible, that I'm testing with the correct POM.

That's all fine when testing Features/Scenarios. But I have to also run my tests w/ CrossBrowserTesting so we can verify all the pages perform correctly and look right. Rather then have our UI guy look through all the screen shots from multiple Features, I'd like to build one Feature that contains happy path solution. My problem is, if I separate all the Features/Contexts, I don't think I'll be able to make this happy path solution as I won't the appropriate Context.

Do you have a recommendation on how to address this? Also, do you have any example projects that are using BPOE that you would recommend? Yes - I've searched for them. I've not found one that is anything more then a simple example.

Thanks.

ERROR 500

How to fix?

Error details: Type: Exception Message: Call to a member function open() on null File: /home/submysno/public_html/ow_plugins/ivideo/controllers/action.php Line: 151 Trace: #0 [internal function]: IVIDEO_CTRL_Action->upload(Array) #1 /home/submysno/public_html/ow_core/request_handler.php(250): ReflectionMethod->invokeArgs(Object(IVIDEO_CTRL_Action), Array) #2 /home/submysno/public_html/ow_core/request_handler.php(226): OW_RequestHandler->processControllerAction(Object(ReflectionMethod), Object(IVIDEO_CTRL_Action)) #3 /home/submysno/public_html/ow_core/application.php(346): OW_RequestHandler->dispatch() #4 /home/submysno/public_html/index.php(76): OW_Application->handleRequest() #5 {main}

Ability to reuse code among page/element objects

Unless I'm missing something, doesn't seem the extension allows for DI into page/element objects.

Example:

# LoginPage
class LoginPage extends Page
# ...
    public function loginWith($username, $password)
    {
        $this->getElement(Username::class)->setValue($username);
        $this->getElement(Password::class)->setValue($password);
        $this->getElement(LoginButton::class)->click();
    }


# CheckoutStepOnePage
class RegisterPage extends Page
# ...
    public function loginWith($username, $password)
    {
        $this->getElement(Username::class)->setValue($username);
        $this->getElement(Password::class)->setValue($password);
        $this->getElement(LoginButton::class)->click();
    }

If I wanted to extract the three getElement calls, the only way to share this code would be via traits it seems. That is something I am trying hard to avoid.

What I am also trying hard to avoid, is to get into the inner workings of the extension. That is, to use the factories, etc myself and change them to suit my needs (too much boilerplate).

What would be the best way for page/element objects reuse code (i.e. helpers, etc)?

Thank you.

isOpen method hiding exception messages

Hi,

I'm using the isOpen method (from SensioLabs\Behat\PageObjectExtension\PageObject\Page class) like this:

PHPUnit_Framework_Assert::assertTrue(
            $this->getPage('Home page')->isOpen(),
            'Home page is not open'
        );

The problem is that when the page fails to open I have no clue why, just my generic message 'Home page is not open'. This is because isOpen method is catching all the exceptions thrown during verification (and their useful messages).

I did a workaround and overrode the isOpen method without the try/catch, but, from my point of view Page public interface should provide this.

What do you think?

Thanks!

PageObjectAware interface must not be deprecated

In our application we have many bundles. Each of bundle can have own Pages (suppose 3 page for every entity that have CRUD).
We develop Common context that can interact with any page:

class FeatureContext extends MinkContext implements Context, SnippetAcceptingContext, PageObjectAware
{
    /** @var  SensioLabs\Behat\PageObjectExtension\PageObject\Factory */
    protected $pageObjectFactory;

    /** @var  \SensioLabs\Behat\PageObjectExtension\PageObject\Page */
    protected $currentPage;

    public function setPageObjectFactory(PageObjectFactory $pageObjectFactory)
    {
        $this->pageObjectFactory = $pageObjectFactory;
    }

    /**
     * @Given /^(?:|I )open (?:|the )"(?P<pageName>.*?)" page$/
     * @Given /^(?:|I )visited (?:|the )"(?P<pageName>.*?)"$/
     */
    public function iOpenPage($pageName)
    {
        $this->currentPage = $this->pageObjectFactory->createPage($pageName);
        $this->currentPage->open();
    }
}

So we strongly needed PageObjectAware interface.
Page constructor injection it's great feature, but it is not necessary to oppose - we need both approach for retrieve page objects in context.

better error reporting needed

I'm running tests with Mink on Saucelabs (Sauce Connect 4.4.6)
At some point tests with firefox started to fail with
Expected to be on "http://localhost/owncloud-core/index.php/login" but found "" instead (SensioLabs\Behat\PageObjectExtension\PageObject\Exception\UnexpectedPageException)

It took me a while and some network sniffing to find out that the Error Message send from Sauce Connect actually is:
The requested combination of browser, version and OS is unsupported by the requested Selenium version and would lead to a test failure. Please set a different Selenium version, or set none to get the default, working Selenium version for this platform: firefox 47.0. on Windows 2008 with Selenium 2.31.0

I had to set the selenium version. No idea why it did work automatically before. But a better error reporting on the console or log file would be good.
I did report this problem in MinkSelenium2Driver but was told the problem is in the PageObjectExtension, so I open this issue here

sensiolabs/BehatPageObjectExtension(lastest version)- Cross-Site Scripting (XSS)

Product: sensiolabs/BehatPageObjectExtension
Download: https://github.com/sensiolabs/BehatPageObjectExtension
Vunlerable Version: v2.0.1 and probably prior
Tested Version: v2.0.1
Author: ADLab of Venustech

Advisory Details:
I have discovered a Cross-Site Scripting (XSS) in “sensiolabs/BehatPageObjectExtension”, which can be exploited to execute arbitrary code.
The vulnerability exists due to insufficient filtration of user-supplied data in “query” HTTP GET parameter passed to “BehatPageObjectExtension-master\features\application\index.php” url. An attacker could execute arbitrary HTML and script code in browser in context of the vulnerable website.
The exploitation examples below uses the "alert()" JavaScript function to see a pop-up messagebox:
Poc:
http://localhost/.../sensiolabs_master/BehatPageObjectExtension-master/features/application/index.php?query=%22%3E%3Cscript%3Ealert(1);%3C/script%3E%3C%22

Remove path and selector from page and element

Hi @jakzal, why are we using a protected property?
Shouldn't be better to declare this an abstract method (as well as selector for elements)?

If this could be an improvement, we can include this for a next major release and introduce a deprecation message to the current one.

What do you think?

Feature: Verifying if a page is open

@wip
Feature: Verifying if a page is open
  In order to avoid endless debugging nights
  As a Developer
  I need to check if a page is open

  Scenario: Verifying if a page is open without parameters
    Given I configured the page object extension
    And a context file "features/bootstrap/SearchContext.php" contains:
    """
    <?php

    use SensioLabs\Behat\PageObjectExtension\Context\PageObjectContext;

    class SearchContext extends PageObjectContext
    {
        /**
         * @Given /^I visited the news list$/
         */
        public function iVisitedTheNewsList()
        {
            $this->getPage('News list')->open();
        }

        /**
         * @When /^I should see a list of recent news articles$/
         */
        public function iShouldSeeListOfRecentNewsArticles()
        {
            $isNewsListOpen = $this->getPage('News list')->isOpen();

            if (!$isNewsListOpen) {
                throw new \LogicException('Expected the news list page to be open');
            }
        }
    }
    """
    And a page object file "features/bootstrap/Page/Homepage.php" contains:
    """
    <?php

    use SensioLabs\Behat\PageObjectExtension\PageObject\Page;

    class NewsList extends Page
    {
        /**
         * @var string $path
         */
        protected $path = '/news';

        protected function verifyResponse()
        {
            // no exception
        }

        protected function verifyPage()
        {
            // no exception
        }

        protected function verifyUrl(array $urlParameters = array())
        {
            // no exception
        }
    }
    """
    And a feature file "features/news.feature" contains:
    """
    Feature: Viewing the news list
      In order to find news I might be interested in
      As a Visitor
      I want to view a list of news

      Scenario: Viewing recent news articles
        Given I visited the news list
         Then I should see a list of recent news articles
    """
    When I run behat
    Then it should pass with:
    """
    ..

    1 scenario (1 passed)
    2 steps (2 passed)
    """

  Scenario: Verifying if a page is open by url
    Given I configured the page object extension
    And a context file "features/bootstrap/SearchContext.php" contains:
    """
    <?php

    use SensioLabs\Behat\PageObjectExtension\Context\PageObjectContext;

    class SearchContext extends PageObjectContext
    {
        /**
         * @Given /^I visited a news page$/
         */
        public function iVisitedNewsPage()
        {
            // opening a wrong page
            $this->getPage('News')->open(array('slug' => 'page-object-extension-2.0-released'));
        }

        /**
         * @When /^I should see the news article$/
         */
        public function isShouldSeeTheNewsArticle()
        {
            $isNewsOpen = $this->getPage('News')->isOpen(array('behat-3-released'));

            if (!$isNewsOpen) {
                throw new \LogicException('Expected the news page to be open');
            }
        }
    }
    """
    And a page object file "features/bootstrap/Page/News.php" contains:
    """
    <?php

    use SensioLabs\Behat\PageObjectExtension\PageObject\Page;

    class News extends Page
    {
        /**
         * @var string $path
         */
        protected $path = '/news/{slug}';

        protected function verifyUrl(array $urlParameters = array())
        {
            if ($this->getUrl($urlParameters) !== $this->getSession()->getCurrentUrl()) {
                throw new \LogicException(sprintf('The current url "%s" does not match the expected "%s"', $this->getSession()->getCurrentUrl(), $this->getUrl($urlParameters)));
            }
        }
    }
    """
    And a feature file "features/news.feature" contains:
    """
    Feature: Reading news
      In order to be up to date with recent events
      As a Visitor
      I want to read news articles

      Scenario: Reading a news article
        Given I visited a news page
         Then I should see the news article
    """
    When I run behat
    Then it should fail with:
    """
    .F

    (::) failed steps (::)

    01. The current url "http://localhost:8000/news/page-object-extension-2.0-released" does not match the expected "http://localhost:8000/news/behat-3-released"
        In step `Then I should see the news article'. # SearchContext::isShouldSeeTheNewsArticle()
        From scenario `Reading a news article'.       # features/news.feature:6
        Of feature `Reading news'.                    # features/news.feature

    1 scenario (1 failed)
    2 steps (1 passed, 1 failed)
    """

  Scenario: Verifying if a page is open by its contents
    Given I configured the page object extension
    And a context file "features/bootstrap/SearchContext.php" contains:
    """
    <?php

    use SensioLabs\Behat\PageObjectExtension\Context\PageObjectContext;

    class SearchContext extends PageObjectContext
    {
        /**
         * @Given /^I visited the news list$/
         */
        public function iVisitedTheNewsList()
        {
            try {
                $this->getPage('News list')->open();
            } catch (\Exception $e) {
                // Opening a page with open() would trigger verification if page is open.
                // We want to verify the behavior is also trigger from isOpen().
            }
        }

        /**
         * @When /^I should see a list of recent news articles$/
         */
        public function iShouldSeeListOfRecentNewsArticles()
        {
            $isNewsListOpen = $this->getPage('News list')->isOpen();

            if (!$isNewsListOpen) {
                throw new \LogicException('Expected the news list page to be open');
            }
        }
    }
    """
    And a page object file "features/bootstrap/Page/Homepage.php" contains:
    """
    <?php

    use SensioLabs\Behat\PageObjectExtension\PageObject\Page;

    class NewsList extends Page
    {
        /**
         * @var string $path
         */
        protected $path = '/news';

        /**
         * @return boolean
         */
        protected function verifyPage()
        {
            throw new \InvalidArgumentException('The page does not look like a news list page');
        }
    }
    """
    And a feature file "features/news.feature" contains:
    """
    Feature: Viewing the news list
      In order to find news I might be interested in
      As a Visitor
      I want to view a list of news

      Scenario: Viewing recent news articles
        Given I visited the news list
         Then I should see a list of recent news articles
    """
    When I run behat
    Then it should fail with:
    """
    .F

    (::) failed steps (::)

    01. The page does not look like a news list page
        In step `Then I should see a list of recent news articles'. # SearchContext::iShouldSeeListOfRecentNewsArticles()
        From scenario `Viewing recent news articles'.               # features/news.feature:6
        Of feature `Viewing the news list'.                         # features/news.feature

    1 scenario (1 failed)
    2 steps (1 passed, 1 failed)
    """

  Scenario: Verifying if a page is open by the response
    Given I configured the page object extension
    And a context file "features/bootstrap/SearchContext.php" contains:
    """
    <?php

    use SensioLabs\Behat\PageObjectExtension\Context\PageObjectContext;

    class SearchContext extends PageObjectContext
    {
        /**
         * @Given /^I visited the news list$/
         */
        public function iVisitedTheNewsList()
        {
            try {
                $this->getPage('News list')->open();
            } catch (\Exception $e) {
                // Opening a page with open() would trigger verification if response is valid.
                // We want to verify the behavior is also trigger from isOpen().
            }
        }

        /**
         * @When /^I should see a list of recent news articles$/
         */
        public function iShouldSeeListOfRecentNewsArticles()
        {
            $isNewsListOpen = $this->getPage('News list')->isOpen();

            if (!$isNewsListOpen) {
                throw new \LogicException('Expected the news list page to be open');
            }
        }
    }
    """
    And a page object file "features/bootstrap/Page/Homepage.php" contains:
    """
    <?php

    use SensioLabs\Behat\PageObjectExtension\PageObject\Page;

    class NewsList extends Page
    {
        /**
         * @var string $path
         */
        protected $path = '/news';

        /**
         * @return boolean
         */
        protected function verifyResponse()
        {
            throw new \InvalidArgumentException('The request to the News List did not return a successful response');
        }
    }
    """
    And a feature file "features/news.feature" contains:
    """
    Feature: Viewing the news list
      In order to find news I might be interested in
      As a Visitor
      I want to view a list of news

      Scenario: Viewing recent news articles
        Given I visited the news list
         Then I should see a list of recent news articles
    """
    When I run behat
    Then it should fail with:
    """
    .F

    (::) failed steps (::)

    01. The request to the News List did not return a successful response
        In step `Then I should see a list of recent news articles'. # SearchContext::iShouldSeeListOfRecentNewsArticles()
        From scenario `Viewing recent news articles'.               # features/news.feature:6
        Of feature `Viewing the news list'.                         # features/news.feature

    1 scenario (1 failed)
    2 steps (1 passed, 1 failed)
    """

Elements are not checked for existence

InlineElement and Element instances are created without checking if the selector would return an actual html node.

Either getElement should return null, or we need a way to verify that element exists ($element->isPresent()?).

Element can't return other elements

Why can't Element returns an Element itself?

Is it "by design"? There are motivations that, at the moment, I can't understand?

If it is an interesting behavior let me know and I'll make a PR.

Expected to be on "xxx" but found "" instead

I'm using MinkExtension and running locally is fine. But when I try to run w/ CrossBrowserTesting I fail. I'm working on extending MinkExtension w/ a CBT Factory patterned after BrowserStack Factory.

Anyway, here is my behat-cbt.yml

default:
  suites:
    default:
      paths: ["%paths.base%/features/temp/home.feature"]
      local_screenshots: false      
      contexts: [ThinkWellContext]

  extensions:
    Behat\MinkExtension:
      default_session: my_session
      base_url: https://twlw.wpengine.com
      browser_name: 'chrome'      
      sessions:
        my_session:
          cross_browser_testing:
            server: "crossbrowsertesting.com"
            user: "[email protected]"
            key: "ud"
            capabilities: 
              name: "Name"
              build: "1.0.0"
              record_video: true
              record_network: true
              max_duration: "14400"
              browserName: 'Chrome'
              version: '62x64'
              platform: 'Mac OSX 10.12'
              screenResolution: '1366x768'
              
    SensioLabs\Behat\PageObjectExtension:
      namespaces:
        page: [Page]
        element: [Page]

Heres the output:

~/projects/thinkwell-livewell/wp-content/themes/jumpoff (master)$ vendor/bin/behat --config behat-cbt.yml 
Feature: Home
  In order to navigate the site
  As a visitor
  I want to test navigation

  Background:                           # features/temp/home.feature:6
    Given I reset "all" data            # ThinkwellContext::iResetData()
      │ Reset Post Data
      │ Reset User Program Data
      │ Reset User Data
      │ 
    Given I am a visitor                # ThinkwellContext::iAmAVisitor()
    When I go to the "Home" page        # ThinkwellContext::iGoToThePage()
      Expected to be on "https://twlw.wpengine.com/" but found "" instead (SensioLabs\Behat\PageObjectExtension\PageObject\Exception\UnexpectedPageException)
    And I take a screenshot "home page" # ThinkwellContext::iTakeAScreenshot()

  @javascript
  Scenario: I can see multiple links            # features/temp/home.feature:13
    Then I should see "Programmes" link         # ThinkwellContext::iShouldSeeLink()
    And I should see "How It Works" link        # ThinkwellContext::iShouldSeeLink()
    And I should see "Self-Help/Resources" link # ThinkwellContext::iShouldSeeLink()
    And I should see "Try It Free" link         # ThinkwellContext::iShouldSeeLink()
    And I take a screenshot "Home page"         # ThinkwellContext::iTakeAScreenshot()

--- Failed scenarios:

    features/temp/home.feature:13

1 scenario (1 failed)
9 steps (2 passed, 1 failed, 6 skipped)

I'm not seeing any activity w/in CBT. I'm kind of lost to how to debug this. My experience has been when I see a similar message to this that the browser was never started. I don't see any browser starting.

Using PageObjectExtension in multiple Symfony2 bundles

Hi,
Is there a way to use PageObjectExtension when features are organized in Symfony2 bundles? The issue I'm running into is that I will end up having some classes Page in each of my bundles, next to the features, but I can only specify one namespace in namespaces.page.

default:
    extensions:
        Behat\MinkExtension\Extension:
            base_url: 'http://short'
            goutte: ~
            selenium2: ~
            default_session: 'symfony2'
        SensioLabs\Behat\PageObjectExtension\Extension:
            namespaces:
                page: Short\SiteBundle\Features\Page
        Behat\Symfony2Extension\Extension:
            mink_driver: true

A workaround, maybe, is to define one profile per bundle, but we can't run all tests at once:

site:
    # ...
    extensions:
        Behat\Symfony2Extension\Extension:
            bundle: ShortSiteBundle
        SensioLabs\Behat\PageObjectExtension\Extension:
            namespaces:
                page: Short\SiteBundle\Features\Page
admin:
    # ...
    extensions:
        Behat\Symfony2Extension\Extension:
            bundle: ShortAdminBundle
        SensioLabs\Behat\PageObjectExtension\Extension:
            namespaces:
                page: Short\AdminBundle\Features\Page

Add in support for WebAssert

As I currently understand it if you want to use WebAssert within the PageObject you have to create your own instance of it. I think it would be nice if there was a nice way to do built in.

Simplify and speedup Elements

For now we have two types of Elements, but they do the same things, and have the same purpose.
First of all Elements configuration must be unified.
I suggest to add names for Custom elements, and add classes for Inline Elements - it's only that needed for merge them.
So we will have the next configuration in behat.yml:

default:
  extensions:
    SensioLabs\Behat\PageObjectExtension:
      elements:
        Search form:
          selector: 'form#search'
          class: SensioLabs\Behat\PageObjectExtension\PageObject\InlineElement
        Navigation:
          selector: {'css': '.header div.navigation'}
          class: Acme\DemoBundle\Tests\Behat\Page\Element\Navigation

And configuration for Inline Elements:

<?php

namespace Page;

use SensioLabs\Behat\PageObjectExtension\PageObject\Page;

class Homepage extends Page
{
    // ...

    protected $elements = array(
        'Search form' => [
          'selector' => 'form#search',
          'class' => 'SensioLabs\Behat\PageObjectExtension\PageObject\InlineElement',
        ],
        'Navigation' => array(
          'selector' => ['css' => '.header div.navigation'],
          'class' => Acme\DemoBundle\Tests\Behat\Page\Element\Navigation,
        )
      );

    // ...

So what do you think about it?
As side effect you don't need to guess Element class anymore (that can be performance issue by the way), you are always know what instance you must create.

Support for multiple mink sessions?

I have come across this issue when using multiple sessions in mink. Instantiate() method is restricted by Factory interface and cannot pass session as parameter. It would be very useful to have such feature. Have you been thinking about such implementation?

Excluding by tags no longer works

Since I installed the Page Object extension, when I use the tags option to exclude a tag, it no longer gets excluded. Is anyone else experiencing this issue?

Cannot debug page objects / elements with Xdebug

I cannot execute code in page object classes step by step using Xdebug, because the 3rd party Proxy manager will replace my real classes with generated code like /tmp/ProxyManagerGeneratedProxy_PM_PageDummy2b00042f7481c7b056c4b410d28f33cf.php.

Replacing lazy factory with default factory does not help to resolve this.
Is there a way to get rid of the proxy manager at least while I'm debugging my tests?

Clicking link, when emulating device, does not work

When I run my script against Chrome browser, it works fine.
When I change the capabilities to emulate Galaxy S5, the click on link does not work

        capabilities:
          extra_capabilities:
            chromeOptions:
              mobileEmulation:
                deviceName: Galaxy S5

I use PsySh and can use these statements (which the code also does)

>>>  $link = $parent->getElement($linkname);
=> SensioLabs\Behat\PageObjectExtension\PageObject\InlineElement {#8076}
>>> $link->click()
=> null

Nothing happens & I see no error in the developers console.

The xpath is //a[contains(@href,'register/one-month')]

When I use the following w/in the developers console, the click works:

foo = $x("//a[contains(@href,'register/one-month')]")[0]
foo.click()

Any suggestion on how to debug this?

Thanks!

Calling function visit() on a non-object ?

Hello !

I am trying to use this promising extension with Behat 3 but I face a bug I don't really understand. I followed the tutorial on http://extensions.behat.org/page-object/#page-objects but can't make it to work.

Configuration

I have a CorePage like this :

namespace myCompany\PageObjects\Pages;
use SensioLabs\Behat\PageObjectExtension\PageObject\Page;

class CorePage extends Page
{
    protected $path;

    public function __construct(){
    }

    public function closePopin() {
        // Do stuff
    }
}

And some other pages like this :

namespace myCompany\PageObjects\Pages;
class Homepage extends CorePage
{
    public function __construct(){
        $this->path = "http://someUrl.com";
    }
}

Then, I try to use my new pages in some feature context :

namespace myCompany\Features;
use SensioLabs\Behat\PageObjectExtension\Context\PageObjectContext;

class SomeFeatureContext extends PageObjectContext
{
    public function __construct(){
    }

    /**
     * @Then I do stuff
     */
    public function iDoStuff()
    {
        $homepage = $this->getPage('Homepage');
        var_dump($homepage);

        $homepage->open();
        if($homepage->isOpen()){
            $homepage->closePopin();
        }
    }
}

Console output

PHP Fatal error:  Call to a member function visit() on a non-object in /data/myself/behat/vendor/sensiolabs/behat-page-object-extension/src/SensioLabs/Behat/PageObjectExtension/PageObject/Page.php on line 5

object(myCompany\PageObjects\Pages\Homepage)#3669 (5) {
  ["path":protected]=>
  string(60) "http://someUrl.com"
  ["elements":protected]=>
  array(0) {
  }
  ["factory":"SensioLabs\Behat\PageObjectExtension\PageObject\Page":private]=>
  NULL
  ["parameters":"SensioLabs\Behat\PageObjectExtension\PageObject\Page":private]=>
  array(0) {
  }
  ["session":"Behat\Mink\Element\Element":private]=>
  NULL
}

Any idea of what I am doing wrong ?

Have Page subclasses to hold site-wide element locators and methods

In the site I want to test there are element locators and methods to click/get them that are present in various pages.
I'd like to have a MySitePage class inheriting from Page to hold those locators and methods, and then have, for instance MySiteHomePage, MySiteAboutPage, etc. inheriting from
MySitePage to obtain those element locators and methods plus the ones specific to the page in the site they model.
Like so:

abstract MySitePage extends Page
{
    protected $elements = array(
        'Facebook Share' => '.headerbar-share .share-facebook',
    );
}

MySiteHomePage extends MySitePage
{
    protected $elements = array(
        'Title H1' => 'h1.ng-binding'
    );
}

So far I've tried this but I got the following error when I try to get one of the elements whose locators are in the MySitePage class from one of the feature context methods:

class MySiteHomePageContext extends PageObjectContext
{
    public function iClickOnTheFacebookShareIcon()
    {
        $this->getPage('My Site Homepage')->getElement('Facebook Share')->click();
    }
}

Could not find a class for the "Facebook Share" element. None of the configured namespaces worked: "\Pages\Element\FacebookShare, \Pages\MySite\Element\FacebookShare" (InvalidArgumentException)

I'm aware that the $element array is being overriden by MySiteHomePage, so I thought I'd need to access the same property in the parent class and merge them, but that doesn't seem to work too.

Is there any approach for having inheritance work with Pages or any other plausible approach to achieve the same kind of element locator reuse?

Thanks in advance!

Extension not found

Hi there,

I have followed the docs to install the extension, however I cannot get it to work. I keep getting

[Behat\Testwork\ServiceContainer\Exception\ExtensionInitializationException]
`SensioLabs\Behat\PageObjectExtension` extension file or class could not be located.

Here my config:

default:
        extensions:
                SensioLabs\Behat\PageObjectExtension: ~
                Behat\MinkExtension:
                        base_url:  'http://localhost'
                        sessions:
                                default:
                                        selenium2: ~

Directory structure is standard composer. I did a behat --init also in my project folder.

Assertion inside page object

Hello,

I'd like to take advantage of the WebAssert class methods (https://github.com/Behat/Mink/blob/develop/src/Behat/Mink/WebAssert.php) inside a page object.

For example, I have a step "I should see item1" defined in a FeatureContext. Then, I could implement it like so:

public function iShouldSeeItem($item)
{
    $this->assertSession()->elementTextContains('css', '.items', $item)
}

But it means declaring page-related information (css selector here), inside my feature context, which is the opposite of what this extension intends to solve ;)

So, what d'you think should be the best option here? Should I just inject the WebAssert in my Page class?

This is kinda trivial question, sorry if it's already been answered (I've read all the doc before).

Add a way to have several namespaces

Feature request: Add a way to have several namespaces

The Page Objects location is define in behat.yml:

SensioLabs\Behat\PageObjectExtension\Extension:
namespaces:
page: Company\WSBundle\Features\Context\Page

That means all the objects must be in that directory.
If I have many objects, that directory gets busy.

It would be nice to have the possibility to have several subdirectories:
Company\WSBundle\Features\Context\Page
Company\WSBundle\Features\Context\Page\Offer
Company\WSBundle\Features\Context\Page\Offer\Create
Company\WSBundle\Features\Context\Page\Offer\Book

Custom Element throwing exception

I have tried to access a custom element class 'Wait' using $this->getElement('Wait')->waitForPageLoad();. It's throwing the following exception:
invalid selector: Unable to locate an element with the xpath expression //html// because of the following error:
SyntaxError: Failed to execute 'evaluate' on 'Document': The string '//html//' is not a valid XPath expression.
(Session info: chrome=58.0.3029.110)
(Driver info: chromedriver=2.29.461585 (0be2cd95f834e9ee7c46bcc7cf405b483f5ae83b),platform=Mac OS X 10.12.5 x86_64) (WARNING: The server did not provide any stacktrace information)
Command duration or timeout: 25 milliseconds
For documentation on this error, please visit: http://seleniumhq.org/exceptions/invalid_selector_exception.html
Build info: version: '3.4.0', revision: 'unknown', time: 'unknown'
System info: host: 'GMX203734MC.local', ip: 'fe80:0:0:0:cf1:95e8:899e:17a3%en0', os.name: 'Mac OS X', os.arch: 'x86_64', os.version: '10.12.5', java.version: '1.8.0_131'
Driver info: org.openqa.selenium.chrome.ChromeDriver
Capabilities [{applicationCacheEnabled=false, rotatable=false, mobileEmulationEnabled=false, networkConnectionEnabled=false, chrome={chromedriverVersion=2.29.461585 (0be2cd95f834e9ee7c46bcc7cf405b483f5ae83b), userDataDir=/var/folders/55/5mcn6tyj6tq5bz8l4scqb21hmmpjkm/T/.org.chromium.Chromium.34qI5A}, takesHeapSnapshot=true, pageLoadStrategy=normal, databaseEnabled=false, handlesAlerts=true, hasTouchScreen=false, version=58.0.3029.110, platform=MAC, browserConnectionEnabled=false, nativeEvents=true, acceptSslCerts=true, locationContextEnabled=true, webStorageEnabled=true, browserName=chrome, takesScreenshot=true, javascriptEnabled=true, cssSelectorsEnabled=true, unexpectedAlertBehaviour=}]
Session ID: 8500abba0171fe3998cb3541ea695aa2

Scenario works fine on pc but fails on Docker/Bitbucket

When I execute the Features from my mac to the Staging site, all is fine.

When I run w/in Docker against the same Staging site, I get the error shown below.

Here is the error from running w/in Docker on Bitbucket

  @javascript
  Scenario: I can create Voice files                                                                         # features/temp/adminWayIn.feature:22
    When I go to the "Admin menu" page                                                                       # ThinkwellContext::iGoToThePage()
    And I click the "Way In" link                                                                            # ThinkwellContext::iClickTheLink()
      Could not find a class for the "Way In" element. None of the configured namespaces worked: "\Page\Element\WayIn" (InvalidArgumentException)
    Then I should see the "Voice list" page                                                                  # ThinkwellContext::iShouldSeeThePage()

Here is yaml

default:
  suites:
    home:
       paths: ["%paths.base%/features/temp/home.feature"]
       contexts: [ThinkWellContext]
    wayin:
       paths: ["%paths.base%/features/temp/adminWayIn.feature"]
       contexts: [ThinkWellContext]
    music:
       paths: ["%paths.base%/features/temp/adminMusic.feature"]
       contexts: [ThinkWellContext]

  extensions:
    Behat\MinkExtension:
      browser_name: 'chrome'
      goutte: ~
      javascript_session: selenium2
      selenium2:
        wd_host: http://0.0.0.0:4444/wd/hub
        capabilities: { "browser": "chrome", "version": "", 'chrome': {'switches':['--start-maximized']}}
      base_url: https://www.twlw.dev

    SensioLabs\Behat\PageObjectExtension:
      namespaces:
        page:
          - Page

staging: #for bitbucket pipeline
  extensions:
    Behat\MinkExtension:
      base_url: http://twlw.staging.wpengine.com
      selenium2:
        # http://selenium is the URL alias to the Selenium docker container.
        wd_host: http://127.0.0.1:4444/wd/hub
        capabilities:
          browser: chrome
          chrome:
            switches:
              - "--headless"
              - "--start-maximized" 

My directory structure: features/bootstrap/Page

I've not had to do anything specifically for an Element

Is this a issue w/ composer install having different versions or what?

Fatal error: Call to a member function createPage() on null (Behat\Testwork\Call\Exception\FatalThrowableError)

My Context:

    /**
     * @When I go to the :arg1 page
     */
    public function iGoToThePage($arg1) {
        $this->currentPage = $this->getPage($arg1);
        assertTrue($this->currentPage->verifyPage());
    }

The Feature

   When I go to the "Home Page" page  

behat.yml

default:
  suites:
    home:
      paths:
         - "%paths.base%/features/home.feature"
      page:
        - ["%paths.base%/features/bootstrap/page-objects/page"]
      contexts:
        - HomeContext

My HomePage in features/bootstrap/page-objects/page/HomePage.php

<?php
namespace Page;
use SensioLabs\Behat\PageObjectExtension\PageObject\Page;
class HomePage extends Page {
  protected $path = '';
  protected $elements = [
....

When I run vendor/bin/behat --config behat.yml --colors --tags wip

~/projects/uwce/www (develop)$ vendor/bin/behat --config behat.yml --colors --tags wip
Feature: HomePage
  In order to navigate all the campuses
  As a visitor
  I want to access any campus
  
  Background

  @javascript @wip
  Scenario: I can login to my campus                     # features/home.feature:48
    Given I am a visitor                                 # HomeContext::iAmAVisitor()
    When I go to the "Home Page" page                    # HomeContext::iGoToThePage()
      Fatal error: Call to a member function createPage() on null (Behat\Testwork\Call\Exception\FatalThrowableError)
    And I click the "UW-Fond du Lac (Staging)" link      # HomeContext::iClickTheLink()

Opening a page URL optionally with query string parameters

Currently, the only way I can see to open a page with query string parameters it seems to be the following:

class Homepage extends Page {
   protected $path = '/?{QS}';
}

class MyContext ... {
   public function aStep() 
   {
      $this->homepage->open(array('QS' => 'utm_source=1&utm_campaign=foo'));
   }
}

This produces the expected result:

<schema>://<hostname>/?utm_source=1&utm_campaign=foo

But this way one is always forced to pass something. By leaving the array parameter passed to the open method, this is the resulting absolute URL:

<schema>://<hostname>/?{QS}

There might be cases where the behaviour of a page (or element) might vary depending on whether something's included in the URL as a query string parameter. And both cases to be verified. Creating the same page object twice (i.e. Homepage and ParameterisedHomepage) feels wrong (even if it just extend the non-parameterised page object class).

How should one deal with optional query string parameters?

Configuration workaround needed to allow for modular, per-page elements

Hi,

In order to achieve something like the following in terms of code/folders structure:

behat.yml
application/
  features/
    bootstrap/
      Page/
        Homepage/
          Element/
            LoginButton.php
            RegisterButton.php
        Registration/
          Element/
            Email.php
            Password.php
            SubmitButton.php

It seems like the configuration setup below is necessary for the extension to work - note the empty namespace declaration for the elements:

# behat.yml
default:
  autoload:
    '': %paths.base%/application/features/bootstrap
  extensions:
    SensioLabs\Behat\PageObjectExtension:
      namespaces:
        element:
          -

Otherwise, an error like the following would be thrown:

# ...

 When the user clicks on the register button

 Could not find a class for the "Page\Homepage\Element\RegisterButton" element. None of the configured namespaces worked: "\Page\Element\Page\Homepage\Element\RegisterButton" (InvalidArgumentException)

# ...

Is the need to configure an empty namespace the only way to make the extension happy?
Would it make sense to have the extension just get the element class based on what's passed to the Page::getElement method (after perhaps checking the existence in the "base namespace", so to avoid breaking other people's code out there....), like so:

# SomeContext
# RegisterButton::class -> Page\Homepage\Element\RegisterButton
$this->homepage->getElement(RegisterButton::class)->click();

Note this is not a declaration of a bug. Rather a genuine question as to what's the best way to achieve separation of components on a page basis (because not all pages have the same elements and namespacing them after the page they're contained within makes sense and gives a sense of cleanliness and structure - IMO).

Thanks

To create pages you need to pass a factory with setPageObjectFactory() (RuntimeException)

My behat.yml

default:
        autoload:
                '': %paths.base%/src
        suites:
                default:
                        contexts: [Context\SearchContext]
                        extensions:
                                SensioLabs\Behat\PageObjectExtension: ~
                                Behat\MinkExtension:
                                        base_url:  'http://en.wikipedia.org'
                                        sessions:
                                                default:
                                                        selenium2: ~

My Context:

<?php

namespace Context;

use Behat\Behat\Tester\Exception\PendingException;
use Behat\Behat\Context\Context;
use Behat\Behat\Context\SnippetAcceptingContext;
use Behat\MinkExtension\Context\MinkContext;
use SensioLabs\Behat\PageObjectExtension\Context\PageObjectAware;

/**
 * Defines application features from the specific context.
 */
class SearchContext extends ContextBase {

    /**
     * Initializes context.
     *
     * Every scenario gets its own context instance.
     * You can also pass arbitrary arguments to the
     * context constructor through behat.yml.
     */
    public function __construct() {

    }

    /**
     * @Then I should see the login flyout open
     */
    public function iShouldSeeTheLoginFlyoutOpen() {
        throw new PendingException();
    }
}

Context Base:

<?php

/*
 * To change this license header, choose License Headers in Project Properties.
 * To change this template file, choose Tools | Templates
 * and open the template in the editor.
 */

namespace Context;

use Behat\Behat\Context\SnippetAcceptingContext;
use Behat\Behat\Tester\Exception\PendingException;
use SensioLabs\Behat\PageObjectExtension\Context\PageObjectContext;


/**
 * Description of ContextBase
 *
 * @author KEH
 */
abstract class ContextBase extends PageObjectContext implements SnippetAcceptingContext {

    /**
     *
     * @var SensioLabs\Behat\PageObjectExtension\PageObject\Page
     */
    protected $page = null;

    /**
     * @Given I am on the homepage
     */
    public function iAmOnTheHomepage() {
        $this->iVisitedThePage('Homepage');
    }

    /**
     * @Given /^(?:|I )visited (?:|the )(?P<pageName>.*?)$/
     */
    public function iVisitedThePage($pageName) {
        $this->page = $pageName;
        $this->getPage($pageName)->open();
    }

    /**
     * @When I mouse over :elementSelector
     */
    public function iMouseOver($elementSelector) {
        throw new PendingException();
    }

}

And my folder structure:

|->bin
|->features
|  |-> login.feature
|->src
|  |->Context 
|  |  |->SearchContext.php (namespace Context)
|  |  |->ContextBase.php (namespace Context)
|  |
|  |->Page
|  |   |->Homepage.php (namespace Page)
|->vendor
|->behat.yml

Can you help me get the factory right?

Create PageObject Element based on parameters

Sometimes we have elements that have very same logic but different parameters describes them so its impossible to get them directly from Page Object.
It would be nice to have additional parameter in getElement method in Page class.

$params = array('id' => 'element_id'); 
PageObject\Page::getElement($name, $params);

Call verifyPage() automatically when a page object is accessed from another page object

This is a follow up feature request, I created this issue to avoid polluting Pull Requests (links below).

The idea is: "what if we called verifyPage() automatically when a page object is accessed from another page object (but not if it's created with a factory)?"
from #15 (comment)

I saw this comment in another PR:
"too many things to go wrong. I think it's better to leave it up to the end user. Especially because it also needs to be called from the open() method."
from https://github.com/sensiolabs/BehatPageObjectExtension/pull/30/files#r12889856

But it would be really nice to call all verify() functions automatically when accessing a page object with getPage() from another object.

The way I have it working now is to have all my page object inherit BasePageObject which has
$this->verifyResponse();
$this->verifyPageUrl();
$this->verifyPage();
in its constructor.

However, that means I can't use open() anymore (because when calling open(), the constructor is called first, and the url is not set yet). So I have to create functions in my context file to set the url first.
Maybe changing the getPage() function...?

Recommend Projects

  • React photo React

    A declarative, efficient, and flexible JavaScript library for building user interfaces.

  • Vue.js photo Vue.js

    🖖 Vue.js is a progressive, incrementally-adoptable JavaScript framework for building UI on the web.

  • Typescript photo Typescript

    TypeScript is a superset of JavaScript that compiles to clean JavaScript output.

  • TensorFlow photo TensorFlow

    An Open Source Machine Learning Framework for Everyone

  • Django photo Django

    The Web framework for perfectionists with deadlines.

  • D3 photo D3

    Bring data to life with SVG, Canvas and HTML. 📊📈🎉

Recommend Topics

  • javascript

    JavaScript (JS) is a lightweight interpreted programming language with first-class functions.

  • web

    Some thing interesting about web. New door for the world.

  • server

    A server is a program made to process requests and deliver data to clients.

  • Machine learning

    Machine learning is a way of modeling and interpreting data that allows a piece of software to respond intelligently.

  • Game

    Some thing interesting about game, make everyone happy.

Recommend Org

  • Facebook photo Facebook

    We are working to build community through open source technology. NB: members must have two-factor auth.

  • Microsoft photo Microsoft

    Open source projects and samples from Microsoft.

  • Google photo Google

    Google ❤️ Open Source for everyone.

  • D3 photo D3

    Data-Driven Documents codes.