Codeception test cases precondition - php

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.

Related

Codeception, don't print a specific action to report

In codeception, I want check if an element exist in the page and do another test if the first element exist. I can do that simply :
// $I is a AcceptanceTester Object and extends \Codeception\Actor class
try{
$I->see('.firstElement');
}catch(ElementNotFound $e){
// do some actions
}
// do some anothers actions
But If I do that, in the report file I can see the line "I see '.firstElement'". I don't want see this test in this report.
My question : How can I call a \Codeception\Actor method quietly ? I just want do a simple DOM element html check and not print this action into the generated report
You can create a simple helper module to check elements existence. It can use WebDriver module or PhpBrowser module to elements finding. For example:
class ElementChecker extends \Codeception\Module
{
public function checkExistence($locator)
{
$elements = $this->getModule('WebDriver')->_findElements($locator);
return !empty($elements);
}
},
After it, you should add this helper to your codeception configuration. For example:
actor: SomeTester
modules:
enabled:
# some modules
- ElementChecker
And new methods will be included in the tester class. You can use them:
if ($I->checkExistence('.firstElement')) {
// some code
}
Also, you can read more about helpers in the official documentation

Simplify procedure for #Route call in Symfony with an EventListener

I have a "standard procedure" that I need in EVERY route I call in Symfony.
I basically create a short (relatively) unique number, check the permission and log that the function has been called.
Here is one example:
**
* #Route("/_ajax/_saveNewClient", name="saveNewClient")
*/
public function saveNewClientAction(Request $request)
{
/* Create Unique TrackNumber */
$unique= $this->get('log')->createUnique();
/* Check Permission */
if (!$permission = $this->get('permission')->needsLevel(2, $unique)) {
/* Log Action */
$this->get('log')->writeLog('No Permission, 2 needed', __LINE__, 4);
return new JsonResponse(array(
'result' => 'error',
'message' => 'Insufficient Permission'
)
);
}
/* Log Action */
$this->get('log')->writeLog('called '.__FUNCTION__, __LINE__, 1, $unique);
return $this->render(':admin:index.html.twig', array());
}
Is there a way to put all that in one function somewhere?
The writeLog gets called at other parts in the functions as well, so I don't want to combine it with the permisson check, although that would be possible of course.
When creating an EventListener, do I still have to call it in every function or is it possible to have it automatically called?
Any hint appreciated!
You could try to make a beforefilter.
http://symfony.com/doc/current/cookbook/event_dispatcher/before_after_filters.html
How to Set Up Before and After Filters
It is quite common in web application development to need some logic to be executed just before or just after your controller actions acting as filters or hooks.
Some web frameworks define methods like preExecute() and postExecute(), but there is no such thing in Symfony. The good news is that there is a much better way to interfere with the Request -> Response process using the EventDispatcher component.
Token Validation Example
Imagine that you need to develop an API where some controllers are public but some others are restricted to one or some clients. For these private features, you might provide a token to your clients to identify themselves.
So, before executing your controller action, you need to check if the action is restricted or not. If it is restricted, you need to validate the provided token.
Please note that for simplicity in this recipe, tokens will be defined in config and neither database setup nor authentication via the Security component will be used.
Before Filters with the kernel.controller Event
First, store some basic token configuration using config.yml and the parameters key:
YAML
# app/config/config.yml
parameters:
tokens:
client1: pass1
client2: pass2
Tag Controllers to Be Checked
A kernel.controller listener gets notified on every request, right before the controller is executed. So, first, you need some way to identify if the controller that matches the request needs token validation.
A clean and easy way is to create an empty interface and make the controllers implement it:
namespace AppBundle\Controller;
interface TokenAuthenticatedController
{
// ...
}
A controller that implements this interface simply looks like this:
namespace AppBundle\Controller;
use AppBundle\Controller\TokenAuthenticatedController;
use Symfony\Bundle\FrameworkBundle\Controller\Controller;
class FooController extends Controller implements TokenAuthenticatedController
{
// An action that needs authentication
public function barAction()
{
// ...
}
}
Creating an Event Listener
Next, you'll need to create an event listener, which will hold the logic that you want executed before your controllers. If you're not familiar with event listeners, you can learn more about them at How to Create Event Listeners and Subscribers:
// src/AppBundle/EventListener/TokenListener.php
namespace AppBundle\EventListener;
use AppBundle\Controller\TokenAuthenticatedController;
use Symfony\Component\HttpKernel\Exception\AccessDeniedHttpException;
use Symfony\Component\HttpKernel\Event\FilterControllerEvent;
class TokenListener
{
private $tokens;
public function __construct($tokens)
{
$this->tokens = $tokens;
}
public function onKernelController(FilterControllerEvent $event)
{
$controller = $event->getController();
/*
* $controller passed can be either a class or a Closure.
* This is not usual in Symfony but it may happen.
* If it is a class, it comes in array format
*/
if (!is_array($controller)) {
return;
}
if ($controller[0] instanceof TokenAuthenticatedController) {
$token = $event->getRequest()->query->get('token');
if (!in_array($token, $this->tokens)) {
throw new AccessDeniedHttpException('This action needs a valid token!');
}
}
}
}
Registering the Listener
Finally, register your listener as a service and tag it as an event listener. By listening on kernel.controller, you're telling Symfony that you want your listener to be called just before any controller is executed.
YAML
# app/config/services.yml
services:
app.tokens.action_listener:
class: AppBundle\EventListener\TokenListener
arguments: ['%tokens%']
tags:
- { name: kernel.event_listener, event: kernel.controller, method: onKernelController }
With this configuration, your TokenListener onKernelController method will be executed on each request. If the controller that is about to be executed implements TokenAuthenticatedController, token authentication is applied. This lets you have a "before" filter on any controller that you want.

Behat 3 with Laravel 5: acceptance test passes but it should not

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.

Codeception, architecture Global and Suite Helpers / PHP

I'm learning this amazing Test Framework (Codeception) I already wrote a bunch of test as exercise and all are passed correctly.
Obviously I came a cross a same questions regarding the architecture and the reusability of the tests and the best practice on how use the different Class to structure the tests.
Cest and Cept:
How I read on the documentation there are typology of tests cept and cest.
I experimented both writing tests but still the quantity that I done did not understand me when is the best use of each of them. The only thing I read on the documentation is the reason that if your test is too long create a Cest Class is the best approach.
Global helpers
Going to create a functional test for my login form I structured it like so:
$I = new TestGuy($scenario);
$I->am('A member');
$I->wantTo('Login in the application');
$I->amOnPage('/');
$I->signIn(); // This custom method belongs to a helper
$I->seeInCurrentUrl('/home');
Review the SignIn method
/**
* Class TestHelper
*
* #package Codeception\Module
*/
class TestHelper extends \Codeception\Module
{
/**
* Sign In a user
*/
public function signIn()
{
$email = 'test#test.com';
$password = 'Hello123';
$username = 'user3';
// create a dummy account
$this->haveAnAccount(compact('email','password','username'));
$I = $this->getModule('Laravel4');
$I->fillField('.navbar-collapse input[name="email"]',$email);
$I->fillField('.navbar-collapse input[name="password"]',$password);
$I->click('LOG IN');
}
}
This test pass correctly.
Now If I want to use the signIn() Method in my functional tests is pretty straight forward just use it.
The issue is came when I had to create an Acceptance test with Selenium and it require the exactly the same process to get the user logged in and I need to reuse the exactly the same method that in this circumstance I cannot use for acceptance.
So witch is the best practice to share and make global this helpers?
This helper is already 'global'.
Everything you need is to add this helper to needed suite config - acceptance.suite.yml:
class_name: AcceptanceTester
modules:
enabled: [AcceptanceHelper, YourHelper]
and then do not forget to run
php codeception.phar build

PHPUnit and Selenium - run tests from another class

I am using PHPUnit and Selenium to test my web application.
At the moment I have 2 test classes - UserTest and PermissionsTest.
In UserTest I have methods which test that the program can successfully create a new user.
In PermissionsTest I turn certain permissions on and off and test the outcome.
For instance, I might turn the 'Create User' permission off and then test that the 'Create User' button is disabled. However, if I turn the 'Create User' permission back on, I want to test that it is possible to create a user.
All of the logic for being able to create a user is already in the UserTest class - so is there any way of running tests from the UserTest class from the PermissionsTest class?
At the moment I am trying the following code:
public function testUserPermission(){
$userTest = new UserTest();
if($this->hasPermission = true){
$userTest->testCanCreateUser();
}
}
However when I run this test, I get the error "There is currently no active session to execute the 'execute' command. You're probably trying to set some option in setup() with an incorrect setter name..."
Thanks!
It sounds to me like you're missing separation of your test implementation with its logic - I'm not talking about PHP issue but general test model.It will allow you to reuse your test components in various test cases.
You can take a look on some
material about Page Objects in PHP here or general selenium wiki.
The solution was as follows:
//instantiate and set up other test class
$userTest = new UserTest();
$userTest->setUpSessionStrategy($this::$browsers[0]);
$userTest->prepareSession();
//carry out a test from this class
$userTest->testCanCreateUser();
This works nicely. I can't see why using functionality from another test class is a bad idea in this case, because if I didn't do that I'd have to just rewrite that functionality into my new class, which seems less 'pure'...
For Selenium 1 (RC),
I made the following modifications instead (as well as applying the Page Object design pattern):
Specific Test class
//instantiate and set up other test class
$userTest = new UserTest($this->getSessionId());
//carry out a test from this class
$userTest->createUser();
//asserts as normal
$userTest->assertTextPresent();
...
Base Page Object class
class PageObject extends PHPUnit_Extensions_SeleniumTestCase {
public function __construct($session_id) {
parent::__construct();
$this->setSessionId($session_id);
$this->setBrowserUrl(BASE_URL);
}
}
Specific Page Object class
class UserTest extends PageObject {
public function createUser() {
// Page action
}
}

Categories