Behat - Implement Step definitions - php

I am having difficulties in setting Behat framework correctly. The test runs fine, however the step definitions are not being picked up from the FeatureContext file. Have tried out answers from other questions with no luck. The behat.yml file is currently stored in the root of the project:
default:
paths:
features: 'bin/features'
bootstrap: 'bin/features/bootstrap'
context:
class: 'FeatureContext'
extensions:
Behat\MinkExtension\Extension:
goutte: ~
selenium2: ~
In the root of the project I have a bin folder which contains the standard behat files: behat.bat, webunit.bat etc. Within the bin folder I have the features folder which contains the file search.feature:
Feature: Search
In order to find a word
As a website user
I need to be able to search for a word
#javascript
Scenario: Searching for a word that does exist
Given I am on "http://drupalcamp.mx/search/"
When I fill in "Escriba las palabras clave" with "behat, mink"
And I press "Buscar"
Then I should see "Behavior Driven Development"
And I follow "Behavior Driven Development"
And I wait for 5 seconds
Within the features folder I there is a bootstrap folder which contains the file "FeatureContext"
namespace features;
use Behat\Behat\Context\ClosuredContextInterface,
Behat\Behat\Context\TranslatedContextInterface,
Behat\Behat\Context\BehatContext,
Behat\Behat\Exception\PendingException;
use Behat\Gherkin\Node\PyStringNode,
Behat\Gherkin\Node\TableNode;
use Behat\MinkExtension\Context\MinkContext;
/**
* Features context.
*/
class FeatureContext extends MinkContext
{
/**
* Initializes context.
* Every scenario gets it's own context object.
*
* #param array $parameters context parameters (set them up through behat.yml)
*/
public function __construct(array $parameters)
{
// Initialize your context here
}
/**
* #Given /^I wait for (\d+) seconds$/
*/
public function iWaitForSeconds($seconds)
{
$this->getSession()->wait($seconds*1000);
}
When I run "behat" from the root directory of the project in CL, it fires the test in the browser, passes all but fails to recognise the step definition, it states "You can implement step definitions for undefined steps with these snippets:" which then it gives an example.

You need to configure the autoloader to load classes from your custom bootstrap folder.
If you're using composer, here's an example of how it could be done:
"autoload": {
"psr-0": { "": ["src/", "bin/features/bootstrap/"]}
}
Note: bin folder is a really odd location to put your features or context files.
Related question with an answer: Behat + Symfony, Custom definitions are not loaded (actually, might be a duplicate).

Related

Behat with Page Objects Extension using Facebook/webdriver

I'm new to Behat and incorporating page objects through Selenium using php facebook/webriver extension. I've used Java/testNG with selenium webdriver using the page object model to Automate web applications in the past but the setup with Behat and php I'm just having issues with.
So what I want to do is instead of using the selenium2driver with mink, I want to incorporate the the facebook/webdriver with a format similar to this for my project:
Project
|-bin(folder)
|-features(folder)
--NewFeature.Feature
|--bootstrap(folder)
--FeatureContext.php
|--Page(folder)
---HomePage.php
---RegistrationPage.php
|-vendor
I want all the pages to have their own class, and to be able to call each one from the FeatureContext.php file; so I can keep it as clean as possible.
My composer looks like this:
"require": {
"facebook/webdriver": "~1.0",
"behat/behat": "3.4.2",
"behat/mink-goutte-driver" : "*",
"behat/mink-selenium2-driver": "1.3.1",
"sensiolabs/behat-page-object-extension": "^2.0",
"behat/mink": "1.7.1"
},
"config": {
"bin-dir": "bin/"
},
and my behat.yml is similar to this
default:
extensions:
SensioLabs\Behat\PageObjectExtension: ~
Behat\MinkExtension:
base_url: https://myurl.com
selenium2:
wd_host: localhost:4444/wd/hub
I'm not really sure it's it's possible to initiate webdriver(or just navigate to a page) within the pages using the page object extension or not, I can get firefox to launch through selenium-server-standalone-3.8.1.jar, but I'm not sure how to implement the page files correctly so it will read.
Featurecontext file:
use Behat\Behat\Context\Context;
use Behat\Gherkin\Node\PyStringNode;
use Behat\Gherkin\Node\TableNode;
use Page\Homepage;
/**
* Defines application features from the specific context.
*/
class FeatureContext implements Context
{
private $homepage;
/**
* 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(Homepage $homepage)
{
$this->homepage = $homepage;
}
/**
* #Given \/^(?:|I )visited homepage$\/
*/
public function iVisitedHomepage()
{
$this->homepage->navigateToSite();
}
}
HomePage file setup:
use SensioLabs\Behat\PageObjectExtension\PageObject\Page;
use Facebook\WebDriver\Remote\DesiredCapabilities,
Facebook\WebDriver\Remote\RemoteWebDriver;
use Behat\Mink\Mink,
Behat\Mink\Session,
Behat\Mink\Driver\Selenium2Driver;
class Homepage extends Page{
protected $path = '/';
//setup facebookwebdriver
public function navigateToSite(){
//facebook webdriver code
}
}
Just not sure if I'm even on the right track or if I should create a new BasePage class that setups the driver separately? and how would I format behat.yml so it would know to look for the files correctly?
Of what I know by using Mink extension you are actually using a fork of PHP Facebook\WebDriver.
If you look into Vendor folder you should see an "instalclick" folder that contains the "WebDriver" used by MinkExtension.
When you told behat in config.yaml to use "Behat\MinkExtension:" a driver is created and available in behat context base of "instalclick" and not the base of actual "Facebook\WebDriver\".
I have never used 'SensioLabs\Behat\PageObjectExtension' but my assumption is an instance of the mink session and driver are available automatically (they are initialized in base class). So you just start and code your classes implementation.

Either the step is already declared or Mink instance has not been set on Mink context class

I'm trying to use make some tests around a Drupal project (but Behat is out of it), however I'm having trouble around Mink and its session, and I must admit I have no clue about what I am doing.
Here are my files so far:
FeatureContext.php
use Drupal\DrupalExtension\Context\RawDrupalContext; #not used
use Behat\Mink\Exception\ExpectationException;
use Behat\Behat\Context\SnippetAcceptingContext;
use Behat\Mink\Exception\ElementNotFoundException;
use Behat\Mink\Session;
use Behat\MinkExtension\Context\MinkContext;
use Behat\Behat\Context\Context; #not used
use Behat\Mink\Mink;
use DMore\ChromeDriver\ChromeDriver;
/**
* Defines application features from the specific context.
*/
class FeatureContext extends MinkContext implements SnippetAcceptingContext {
protected $mink;
/**
* FeatureContext constructor.
* Initializes context.
* PLEASE NOTE THAT I'M NOT SURE ABOUT THIS, BUT IT SEEMS TO WORK SO FAR
* Every scenario gets its own context instance.
* You can also pass arbitrary arguments to the
* context constructor through behat.yml.
*/
public function __construct() {
$this->mink = new Mink(array(
'browser' => new Session(new ChromeDriver('http://localhost:9222', null, 'http://www.website.rec'))
));
// The rest of my custom functions
}
}
behat.yml
default:
suites:
default:
contexts:
- FeatureContext
- Drupal\DrupalExtension\Context\DrupalContext
- Drupal\DrupalExtension\Context\MinkContext
- Drupal\DrupalExtension\Context\MessageContext
- Drupal\DrupalExtension\Context\DrushContext
extensions:
DMore\ChromeExtension\Behat\ServiceContainer\ChromeExtension: ~
Behat\MinkExtension:
browser_name: chrome
base_url: http://www.spheria.rec
sessions:
default:
chrome:
api_url: http://localhost:9222
Drupal\DrupalExtension:
blackbox: ~
test.feature
Feature: Sample feature
Scenario: Arrived on website, checking out what's around me
Given I am an anonymous user
And I go to "/"
And I should see "Se connecter"
And I should see "Nom d'utilisateur"
And I should see "Mot de passe"
When I fill in "admin#spheria.com" for "name"
And I fill in "admin" for "pass"
And I press "Se connecter"
Then I should get a 200 HTTP response
And the url should match "/dashboard"
And I should see "Tableau de bord"
My problem is that if I use MinkContext in the behat file and in FeatureContext, the consoles returns me that every single function have been declared twice (at least, MincContext::PressButton but I wouldn't be surprised if the problem would happen with something else)
When I remove it from both behat.yml and FeatureContext, it doesn't recognize anything, and asks me to define these functions, which makes sense I guess.
And when I use MinkContext in only the behat file or the FeatureContext one, I get an error saying this:
Mink instance has not been set on Mink context class. Have you enabled the Mink Extension? (RuntimeException)
I'm using the DMore Chrome Driver, because I have trouble running Chrome with Selenium properly, and I have the feeling that instancing Mink in the constructor is creating some troubles.
It's an euphemism to say that I'm totally lost on what I should do.
How can I solve this problem?
Thank you in advance
You need to extend MinkContext only once else it will see duplicate steps for each time you extend it.
One of the contexts from behat.yml is already extending MinkContext so you need to:
remove that class and extend it in your FeatureContext
or
your FeatureContext should not extend MinkContext but RawMinkContext

Getting feature file path within BehatContext

I have a FeatureContext class, whose methods implement step definitions for my feature scenarios.
Is it possible to obtain information about current scenario or feature within FeatureContext class? To be specific, I need the path of the feature file, so I can create directory paths relative to dirname('file.feature').
The context class resides somewhere in vendor directory structure, i.e. far away from the actual feature file, so getting the path using __DIR__ would not work.
Any ideas?
Thanks, Sascha
I did find an answer to my question: "hooks"
class MyContext implements Context
{
// ...
/**
* #BeforeScenario
*/
public function beforeScenario(BeforeScenarioScope $scope)
{
var_dump($scope->getFeature()->getFile());
}
}

Codeception autoloading classes

I'm having issues using the Codeception autoloader to load some abstract test classes. The reason for the abstract class is to mimic the structure of classes that are used in the application to cut down on the amount of code needed to test the application thoroughly.
Lets say I have an abstract class for testing, let say "AbstractRepositoryTester" which is only used in the "repositories" test suite (I like to separate things out for organisational purposes).
Each and every repository that I test that implements a "RepositoryContract" will have a test that also extends the "AbstractRepositoryTester" with some overridden abstract methods.
Now when doing this, the abstract class won't be loaded during testing and has to be loaded manually in a bootstrap file. There is also another abstraction that extends the vanilla Codeception test class so that I can set a few variables (namely for laracasts/testdummy).
Both classes will fail to load with out manual entry in to the _boostrap file. To add to this, the suite specific bootstrap file fails to load files or seemingly execute at all so I am forced to place all bootstrap code for all suites in to the global _bootstrap file.
I also attempted to use Codeceptions autoloading class \Codeception\Util\Autoload:: with the "load" method but it doesn't seem to work.
Right now I'm using require_once in the global _bootstrap so finally to the question:
Is there a correct way of autoloading (or just loading) a class to be used as part of a test both globally and per suite?
Am I on the right track overall in abstracting my tests like this? TDD is new to me and I am trying to better my development workflow (with help from Laracasts).
I've searched every where for an answer to load the classes I need but usually all I'll find is PHPUnit specific answers which don't appear to work. I have also peered through the Codeception documentation which feels a bit sparse on the subject and the API docs don't explain the method call usage in the case of Autoload::load
Cheers,
- Everon.
You can do this for your whole test suit, or just individual components. For example, for Unit tests only, do the following:
Add bootstrap: my_bootstrap_file.php to tests/unit.suite.yml:
# Codeception Test Suite Configuration
#
# Suite for unit or integration tests.
actor: UnitTester
bootstrap: my_bootstrap_file.php
modules:
enabled:
- Asserts
- \Helper\Unit
Call my_bootstrap_file.php something sensible like just bootstrap.php
Create tests/unit/my_bootstrap_file.php
<?php
\Codeception\Util\Autoload::addNamespace('', 'src');
The directory structure should look like this:
<project root>
src/
tests/
unit/
my_bootstrap_file.php
unit.suite.yml
Replace unit in the instructions above with acceptance, functional, etc. to apply it to different single components.
The PhpDoc for \Codeception\Util\Autoload::addNamespace():
/**
* Adds a base directory for a namespace prefix.
*
* Example:
*
* ```php
* <?php
* // app\Codeception\UserHelper will be loaded from
* '/path/to/helpers/UserHelper.php'
*
* Autoload::addNamespace('app\Codeception', '/path/to/helpers');
*
* // LoginPage will be loaded from '/path/to/pageobjects/LoginPage.php'
* Autoload::addNamespace('', '/path/to/pageobjects');
*
* Autoload::addNamespace('app\Codeception', '/path/to/controllers');
* ?>
* ```
*
* #param string $prefix The namespace prefix.
* #param string $base_dir A base directory for class files in the namespace.
* #param bool $prepend If true, prepend the base directory to the stack instead
* of appending it; this causes it to be searched
* first rather than last.
* #return void
*/
public static function addNamespace($prefix, $base_dir, $prepend = false)
If you want this to apply to your whole test suite, not just Unit tests, use codeception.yml instead of tests/unit.suite.yml, and tests/my_bootstrap_file.php instead of tests/unit/my_bootstrap_file.php.
<project root>
src/
tests/
my_bootstrap_file.php
codeception.yml

Page Object Extension with Behat 3 and Mink 1.5 throws an Exception

I've a project running with Behat 2.4, Mink 1.4 and Behat Page Object Extension, with this version my tests is fine, 100% passed.
But now i'm migrating to Behat 3 due to the fully integration with Browserstack, Behat 2 doesn't support BrowserStack flags and the integration is poor.
I've changed my composer file and I updated project, but when I run the tests, it is returning an Exception on Page Object Extension.
To create pages you need to pass a factory with setPageObjectFactory() (RuntimeException)
Looking at Page Object Docs I don't see anything about setPageObjectFatory, this isn't needed.
In configuration section, only specifies factory if you create a custom factory or/and custom class name resolver.
My composer with all dependencies is
{
"require-dev" : {
"behat/behat" : "master-dev",
"behat/mink-goutte-driver" : "master-dev",
"behat/mink-browserkit-driver" : "master-dev",
"sensiolabs/behat-page-object-extension" : "master-dev",
"behat/mink-extension" : "master-dev",
"behat/mink-selenium2-driver" : "master-dev",
"behat/mink" : "master-dev"
}
}
And my behat.yml bellow
default:
suites:
default:
contexts:
- FeatureContext
- ProductDetailsContext
- CartContext
extensions:
SensioLabs\Behat\PageObjectExtension:
namespaces:
page: [Features\Page]
element: [Features\Page\Element]
Behat\MinkExtension:
sessions:
my_session:
browser_stack:
username: my_username
access_key: my_password
capabilities:
browserName: "Chrome"
browserVersion: "35"
platform: "WIN8"
My FeatureContext extending MinkContext
<?php
use Behat\MinkExtension\Context\MinkContext;
/**
* Behat context class.
*/
class FeatureContext extends MinkContext
{
}
And ProductDetailsContext extending PageObjectContext
<?php
use SensioLabs\Behat\PageObjectExtension\Context\PageObjectContext;
/**
*
*
*/
class ProductDetailsContext extends PageObjectContext
{
/**
* #Given /^I am on product details "([^"]*)"$/
*
* #param string $url
*/
public function iAmOnProductDetails($url)
{
$this->getPage("ProductDetails")->open(array("productUrl" => $url));
}
/**
* #Given /^I am at a random product details$/
*/
public function iAmAtARandomProductDetails()
{
$catalog = $this->getPage("Catalog");
$catalog->open(array('category' => 'calcados-femininos'));
$catalog->openRandomProduct();
}
/**
* #When /^I select product size$/
*/
public function iSelectProductSize()
{
$this->getPage("ProductDetails")->selectProductSize();
}
/**
* #Then /^I add product to cart$/
*/
public function iAddProductToCart() {
$this->getPage("ProductDetails")->addProductToCart();
}
/**
* #Then /^I add product to wishlist$/
*/
public function iAddProductToWishlist()
{
$this->getPage("ProductDetails")->addProductToWishlist();
}
}
I don't know how can solve this and I need help.
The problem is because Extensions was configured inside of suites, put the Extensions outside suites and it works.
default:
suites:
default:
contexts:
- FeatureContext
- ProductDetailsContext
- CartContext
extensions:
SensioLabs\Behat\PageObjectExtension:
namespaces:
page: [Features\Page]
element: [Features\Page\Element]
Behat\MinkExtension:
sessions:
my_session:
browser_stack:
username: my_username
access_key: my_password
capabilities:
browserName: "Chrome"
browserVersion: "35"
platform: "WIN8"
This is a long shot, but here we go… In previous Behat version you've used single / root context, which must have extended PageObjectContext. It must have been the only / first context that was initialised, it was also the right context to initialise page factory and the pages themselves. Now you have multiple contexts, the logical step would be to ensure that they all implement SensioLabs\Behat\PageObjectExtension\Context\PageObjectAwareInterface as said in the docs here.
I also don't see 'SensioLabs\Behat\PageObjectExtension' entry under extensions. I don't think Behat would initialise the extension without it being on the list (it can't just randomly guess that it must be loaded, right?). This is probably the first thing you should change. Based on the docs about configuration and given you followed the default convention, everything should work.
Try to install
composer require --dev --ignore-platform-reqs sensiolabs/behat-page-object-extension:^2.0
In the composer I've add --ignore-platform-reqs because i'm use php 7.* for my works

Categories