I'm doing my firsts acceptance tests for a Laravel 5 application using Behat 3 and Mink.
The application runs under a Homestead VM.
The test is straightforward and is located in the features/example.feature file. This is the test:
Feature: Sample
In order to learn Behat
As a programmer
I need a simple url testing
Scenario: Registration
Given I am not logged in
When I go to the registration form
Then I will be automatically logged in
The FeatureContext.php has this class:
<?php
use Behat\Behat\Tester\Exception\PendingException;
use Behat\Behat\Context\Context;
use Behat\Behat\Context\SnippetAcceptingContext;
use Behat\Gherkin\Node\PyStringNode;
use Behat\Gherkin\Node\TableNode;
use Behat\MinkExtension\Context\MinkContext;
/**
* Defines application features from the specific context.
*/
class FeatureContext extends MinkContext implements Context, SnippetAcceptingContext
{
/**
* 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()
{
}
/**
* #Given I am not logged in
*/
public function iAmNotLoggedIn()
{
Auth::guest();
}
/**
* #When I go to the registration form
*/
public function iGoToTheRegistrationForm()
{
$this->visit(url('my/url'));
}
/**
* #Then I will be automatically logged in
*/
public function iWillBeAutomaticallyLoggedIn()
{
Auth::check();
}
}
Then, when I run behat from the command line, I expect the test to fail because there is no my/url route (the routes.php file only has a route for /).
However, the test returns green, and this is what I see:
Feature: Sample
In order to learn Behat
As a programmer
I need a simple url testing
Scenario: Registration # features/example.feature:7
Given I am not logged in # FeatureContext::iAmNotLoggedIn()
When I go to the registration form # FeatureContext::iGoToTheRegistrationForm()
Then I will be automatically logged in # FeatureContext::iWillBeAutomaticallyLoggedIn()
1 scenario (1 passed)
3 steps (3 passed)
0m0.45s (22.82Mb)
Of course, I'm using the laracasts/behat-laravel-extension package and this is the content of the beat.yml file:
default:
extensions:
Laracasts\Behat: ~
Behat\MinkExtension:
default_session: laravel
laravel: ~
Thank you very much for any help!
Behat is very simple. It treats a step as failed if an exception is thrown while executing it. It treats a step as successful otherwise.
As far as I can tell from the docs, Auth::check() does not throw an exception if user is not authenticated. It simply returns a boolean value.
Your step should be rather implemented more like the following:
/**
* #Then I will be automatically logged in
*/
public function iWillBeAutomaticallyLoggedIn()
{
if (!Auth::check()) {
throw new \LogicException('User was not logged in');
}
}
Your "I go to the registration form" step succeeds since you don't really verify if the page you visited is the one you expected to load. Again, you should throw an exception if a page you visited is not the right one.
Related
I'm trying to register an eventListener which would be called before the /login_check tries to login the user.
I'm writing a DDoS protection, iIlog in database each try (date, user_id, is_failure), and if an user has more than N wrong attempts to login, I generate a token sent by email to the right user email. Anyone without this token will be forbidden to try another login during 10 minutes.
To proceed, I need to:
either be able to register an eventListener at the start of /login_check
either be able to rewrite /login_check to add the event
I didn't find any event about "pre_authentication", do you have a solution ?
I won't write the code in a repository method to lad an user, it's not its place.
Thanks
I had a similar problem a few days ago. And like you said i couldn't find a suitable "pre_authentication" either at which point i could execute my checks even before the authentication was attempted. (AuthenticationSuccess and AuthenticationFailure Handler weren't an option in my case since i wanted to block the attempt before it was even tried)
But in the end i found an approach that did work in my case (although there may be a better one but i couldn't find it).
If your application is using the default username/password authentication you could do this:
Extend the UsernamePasswordFormAuthenticationListener
class UsernamePasswordFormAuthenticationListener extends \Symfony\Component\Security\Http\Firewall\UsernamePasswordFormAuthenticationListener
{
/** #var EntityManagerInterface */
protected $entityManager;
/**
* setter is called through DI container
*
* #param EntityManagerInterface $entityManager
*/
public function setEntityManager(EntityManagerInterface $entityManager)
{
$this->entityManager = $entityManager;
}
/**
*
* #param Request $request
*
* #return null|RedirectResponse|\Symfony\Component\HttpFoundation\Response|\Symfony\Component\Security\Core\Authentication\Token\TokenInterface
*/
protected function attemptAuthentication(Request $request)
{
//do your logic and whatnot here
// i.E. return a redirect repsonse if the token is needed but missing
return parent::attemptAuthentication($request);
}
}
Overwrite the original service in your services.yml
security.authentication.listener.form:
class: AppBundle\Listener\UsernamePasswordFormAuthenticationListener
parent: security.authentication.listener.abstract
abstract: true
calls: [ [setEntityManager, ["#doctrine.orm.entity_manager"]] ]
(Using setter injection here because the constructor needs like a ton of parameters)
Maybe this approach could fit your needs and nobody has a better idea
I am writing automation tests using codeception framework. I have a test cases which are verifying some functionality after user is logged in. There are around 20 test cases with different functionality. All test cases need an user to be logged in to the system, so I have written a login functionality under _before callback. When I execute all the test cases, before every test cases login functionality is checked which takes a lot of time. Can we write login functionality as precondition and once user is logged in it should execute all test cases?
You can use what are known as Helpers in codeception. You can generate a helper using the following command:
vendor/bin/codecept generate:helper Login
Then you could put a method into the class for logging in a user like so:
<?php
namespace Helper;
// here you can define custom actions
// all public methods declared in helper class will be available in $I
class Login extends \Codeception\Module
{
public function login($username, $password)
{
/** #var \Codeception\Module\WebDriver $webDriver */
$webDriver = $this->getModule('WebDriver');
// if snapshot exists - skipping login
if ($webDriver->loadSessionSnapshot('login')) {
return;
}
// logging in
$webDriver->amOnPage('/login');
$webDriver->submitForm('#loginForm', [
'login' => $username,
'password' => $password
]);
$webDriver->see($username, '.navbar');
// saving snapshot
$webDriver->saveSessionSnapshot('login');
}
}
See http://codeception.com/docs/06-ReusingTestCode#session-snapshot for more info on snapshots.
Your acceptance.suite.yml should look something like this:
# Codeception Test Suite Configuration
#
# Suite for acceptance tests.
# Perform tests in browser using the WebDriver or PhpBrowser.
# If you need both WebDriver and PHPBrowser tests - create a separate suite.
class_name: AcceptanceTester
modules:
enabled:
# Note we must use WebDriver for us to use session snapshots
- WebDriver:
url: http://localhost/myapp
browser: chrome
- \Helper\Acceptance
# Note that we must add the Login Helper class we generated here
- \Helper\Login
Now we have a helper class that can be reused in all our tests. Let's look at an example:
<?php
class UserCest
{
// tests
public function testUserCanLogin(AcceptanceTester $I)
{
$I->login('username', 'password');
}
public function testUserCanCarryOutTask(AcceptanceTester $I)
{
$I->login('username', 'password');
$I->amOnPage('/task-page');
$I->see('Task Page');
// other assertions below
}
public function testUserCanCarryOutAnotherTask(AcceptanceTester $I)
{
$I->login('username', 'password');
$I->amOnPage('/another-task-page');
$I->see('Another Task Page');
// other assertions below
}
}
Now when running the UserCest test, it should only login the user once.
I am trying to write some Behat tests for an application and I need to separate the Contexts so I can use some core elements in different other contexts for example the logged in user step.
behat.yml
suites:
common:
type: symfony_bundle
bundle: CommonBundle
mink_session: symfony2
mink_javascript_session: selenium2
autoload:
'CommonContext': %paths.base%/src/xxx/CommonBundle/Features/Context
contexts: [ xxx\CommonBundle\Features\Context\CoreContext ]
user:
type: symfony_bundle
bundle: UserBundle
mink_session: symfony2
mink_javascript_session: selenium2
autoload:
'UserContext': %paths.base%/src/xxx/UserBundle/Features/Context
contexts:
- Behat\MinkExtension\Context\MinkContext
- xxx\CommonBundle\Features\Context\CoreContext
- xxx\UserBundle\Features\Context\UserContext
DefaultContext.php
namespace XXX\CommonBundle\Features\Context;
use ...
/**
* Class DefaultContext
*/
class DefaultContext extends MinkContext implements Context, KernelAwareContext
{
/**
* #param AbstractEntity $oEntity
*
* #return object
*/
protected function getRepository(AbstractEntity $oEntity)
{
return $this->getService($oEntity);
}
/**
* #return mixed
*/
protected function getEntityManager()
{
return $this->getService('doctrine')->getManager();
}
/**
* #param $id
*
* #return object
*/
protected function getService($id)
{
return $this->getContainer()->get($id);
}
CoreContext.php
namespace XXX\CommonBundle\Features\Context;
use ...
/**
* Class SubContext
*/
class CoreContext extends DefaultContext implements Context, SnippetAcceptingContext
{
/**
* #Given I visit the homepage
* #When I visit the homepage
*/
public function iVisitHomepage()
{
$this->visitPath('/');
}
/**
* #And /^I am logged in as "([^"]*)"$/
* #Then /^I am logged in as "([^"]*)"$/
*/
public function iAmLoggedInAs($username)
{
$user = $this->getContainer()->get('fos_user.user_manager')->findUserByUsername($username);
$this->visit('/login');
$this->fillField('_username', $user->getEmailCanonical());
$this->fillField('_password', self::USER_PASSWORD);
$this->pressButton('_submit');
}
UserContext.php
namespace xxx\UserBundle\Features\Context;
use ...
/**
* Defines application features from the specific context.
*/
class UserContext extends CoreContext implements Context, SnippetAcceptingContext
{
/**
* 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()
{
}
}
register_user.feature
Feature: Register user
#javascript
Scenario: Register user
Given I am on homepage
And I go to "/register/"
And I am logged in as "foo#bar.com"
Then I should see "Terms and Conditions"
So when I run the test I get an error saying this:
Given I am on homepage
Step "I visit the homepage" is already defined in xxx\CommonBundle\Features\Context\CoreContext::iVisitHomepage()
xxx\CommonBundle\Features\Context\CoreContext::iVisitHomepage()
xxx\CommonBundle\Features\Context\CoreContext::iVisitHomepage()
And I go to "/register/"
Step "I visit the homepage" is already defined in xxx\CommonBundle\Features\Context\CoreContext::iVisitHomepage()
xxx\CommonBundle\Features\Context\CoreContext::iVisitHomepage()
xxx\CommonBundle\Features\Context\CoreContext::iVisitHomepage()
Do i understood this completely wrong or I am missing some settings here?
Do not extend contexts that provide step definitions. There's no way around this.
Good extensions provide contexts with handy methods but no step definitions. In case of the Mink extension, next to the MinkContext you also have RawMinkContext. First provides step definitions and should not be extended. The other one provides helper methods you might be interested in. The RawMinkContext is the one you should extend. Alternatively you can also use the MinkAwareTrait.
There has been a shift on what Contexts where in Behat2 and what they are now in Behat3. Contexts in Behat3 are more about "situations" in which your tests might occur. For instance:
Logged in
Anonymous visitor
Public API call
Authenticated API call
Disabled javascript browser
Enabled javascript browser
And so on.
So I'm afraid that the problem is in your configuration of the CoreContext being used in two suites. You can try to avoid this by running your suites separately so it does not load both configurations (and it does not autoload twice the same Context) or to change the Context strategy to not use the autoload, but something different like encapsulating common step logics into their own classes that are then being used from different Contexts.
Hope it helps
I set up a simple test scenario to learn behat, but I'm running into some problems. I'm following THIS tutorial.
Here is my feature show:
Feature: show
This is a behat feature to test the article pages.
##TODO
Scenario: I want to view a detailed article page
Given I am logged in
And I'm on "/articles"
When I press an article Image
Then I should see a title
And I should see an Image
And I should see some text
and here is my FeatureContext.php file
<?php
use Behat\MinkExtension\Context\MinkContext;
/**
* Features context.
*/
class FeatureContext extends MinkContext
{
/**
* Initializes context.
* Every scenario gets its own context object.
*/
public function __construct()
{
}
/**
* #Given /^I am on "([^"]*)"$/
*/
public function iAmOn($arg1)
{
throw new PendingException();
}
/**
* #Given /^I press "([^"]*)"$/
*/
public function iPress($arg1)
{
throw new PendingException();
}
/**
* #When /^I fill in "([^"]*)" with "([^"]*)"$/
*/
public function iFillInWith($arg1, $arg2)
{
throw new PendingException();
}
/**
* #Then /^I should see "([^"]*)" in the "([^"]*)" element$/
*/
public function iShouldSeeInTheElement($arg1, $arg2)
{
throw new PendingException();
}
}
However everytime I try to run the feature I get the same result, which looks like this:
Feature: show
This is a behat feature to test the article pages.
Scenario: I want to view a detailed article page # features\show.feature:5
Given I am logged in
And I'm on "/articles"
When I press an article Image
Then I should see a title
And I should see an Image
And I should see some text
1 scenario (1 undefined)
6 steps (6 undefined)
0m0.32s (4.78Mb)
I'm not sure what's causing this problem. I've been looking for a solution but I can't find it. I hope one of you can help me out!
thanks in advance
Your steps do not match your step definitions.
You can let Behat create stubs of your step definitions by implementing SnippetAcceptingContext in your FeatureContext and running Behat with the --append-snippets argument as described here:
http://behat.org/en/latest/quick_start.html#defining-steps
My target is to perform a logout from a controller being able to have base logout mechanism correctly performing.
After googling and stack-overflowing on some similar questions I have tried to implement an extension of class LogoutListener as suggested in the answer here How to log user out of Symfony 2 application using it's internal handlers
I have created the class and I've added the parameter "security.logout_listener.class" in parameters.yml which is imported from config.yml.
Exactly my new class is
# MyBundle/MyLogoutListener.php
namespace MyBundle;
use Symfony\Component\Security\Http\Firewall\LogoutListener;
use Symfony\Component\HttpFoundation\Session\SessionInterface;
use Symfony\Component\HttpFoundation\Request;
class MyLogoutListener extends LogoutListener{
/**
* Whether this request is asking for logout.
*
* #param Request $request
*
* #return Boolean
*/
protected function requiresLogout(Request $request)
{
if ($request->hasSession())
if ( $request->getSession->get('logout') ) {
return true;
}
}
return parent::requiresLogout($request);
}
}
And the row I've added in parameters.yml is
security.logout_listener.class: MyBundle\MyLogoutListener
Whit this things I have a well working logout functionality activable in the login of a controller but if I try to use the base logout functionality of Symfony2 I receive this error :
Runtime Notice: Declaration of
MyBundle\MyLogoutListener::requiresLogout() should be compatible with
Symfony\Component\Security\Http\Firewall\LogoutListener::requiresLogout(Symfony\Component\HttpFoundation\Request
$request)
For base logout functionality I mean the configuration in the firewall with :
…
logout:
path: /logout
target: /someaction
…
Please can somebody explain where is the problem ?
Do I need to define a service with MyLogoutListener ?