Crinsane's avatar

Laravel 5, Behat 3 and JavaScript

Okay, I've got this problem I just can't find a correct solution for. I've recently been learning Behat and I really like it. At work I really want to have these kinds of tests for our packages, and Behat really seems to fit perfectly. I've got Behat running with Laravel 5 perfectly with the package @JeffreyWay wrote, and it all tests run fast and the testing environment variables are used. All perfect and simple.

But here's the problem. Let's say I want to test if clicking a link will delete a user. The route behind this button is a DELETE request. I've used some simple Javascript that, when clicking link, will transform it into a form and submit the form (so you can do a DELETE request by clicking a link) (actually, @JeffreyWay wrote this little bit of JavaScript once)

Now I have a problem, because Behat can't execute the JavaScript and will just click the link and do a GET request.

So I've searched the internet and it tells me to use Selenium2, so I set it up, but first thing I notice is that it now uses the normal environment, so it will not use my sqlite db, but the normal MySQL database. Also it now has to open a browser (I've tried using PhantomJS, but no luck there). Which of course slows the tests down significantly.

The setup I would really like, would be all my Behat test using the settings from my .env.behat file. When I mark a test with @javascript, it will use another drive that executes JavaScript, preferably using all headless stuff (For instance Selenium2 and PhantomJS)

But I just can't find anything online what will truly help me. Has anyone here struggle with the same thing and found a solution? Is it even possible?

0 likes
14 replies
bobbybouwmann's avatar

I sadly don't know any solution for that..

Can't you update the link to a mini form? So you style the form to look like a link and then Behat can handle your action.

{!! Form::open(['route' => 'delete', 'method' => 'delete']) !!}
    {!! Form::submit('delete', ['class' => 'delete-link']) !!}
{!! Form::close() !!}
Crinsane's avatar

Technically I could totally do that. But since I also want a little confirm popup box, the JavaScript problem would still be there.

JeffreyWay's avatar

@Crinsane - The Behat extension for Laravel I made would not apply to Selenium tests, naturally, since those go through an actual browser. So you need to use a different method to set the environment.

I'm currently using a custom domain name for my Selenium tests. For example, I have laracasts.dev, but then laracasts.behat for Selenium. That way, I have some kind of hook to set the environment.

Then, you could add this snippet to the bottom of bootstrap/app.php, substituting for your domain.

if (array_get($_SERVER, 'SERVER_NAME') == 'laracasts.behat') {
    $app->loadEnvironmentFrom('.env.behat');
}

If there's a better way, I'd love to hear. Luckily, if you use something like Laravel Homestead, setting up those new domains takes seconds.

6 likes
Crinsane's avatar

@JeffreyWay Thanks for the advice, I'd indeed been thinking about doing something like that, but I'm still struggling with my setup. Do you have it actually running through a real browser like Firefox? Or do you use PhantomJS or something?

Also, your packages makes all database actions be rolled back, which with this setup doesn't happen. How have you fixed that?

Meshal's avatar

@JeffreyWay how about that, since I'm not using Homestead

if (!empty($argv) ) {
        if( array_search('--port=8080',$argv))
        $app->loadEnvironmentFrom('.env.testing');


}

then I reserve my 8080 port for testing

ChristopherSFSD's avatar

I have behat tests using the testing database thanks to your suggestions and using the port 8080 tweak. Nice!

However the tests do not honor DatabaseTransactions. Any solutions for that?

iamfaiz's avatar

I was doing something similar to Jeffrey's solution in my app service provider. But instead of loading the whole environment I was resetting the database connection to 'testing'. I was doing:

if (array_get($_SERVER, 'SERVER_NAME') == 'testing.app')) {
    Config::set('database.default', 'testing');
}

And then addes a new domain in the hosts file:
127.0.0.1 testing.app

But I guess Jeffrey Way's solution is better. Loading a whole new testing environment.

andremellow's avatar

Hi guys,

 Even of this soluction, selenium still opening right?

  Is there any solution to make it works behind the scenes, even with integrated ?
eoghanobrien's avatar

Hi guys,

I like to run my tests off of the php artisan serve command so everything runs through localhost:8000 instead of an entirely new hostname on homestead. It also makes it easy to push the code up to CI servers, anyway, here's what I'm doing, I find it works pretty well...

I use the @BeforeScenario hook to remove the sqlite database, if it exists (usually because of a cancelled test or something), and then re-create it and run my migrations and seeds. The key here is once they've run, I simply copy my .env.behat file to .env and let my tests run through the browser as normal with all my testing environment related settings.

Once all my tests have run, I use the @AfterSuite hook, to remove the sqlite database and also the .env file. I'm toying with the idea of keeping a .env.local file with all my homestead related settings and once my tests have finished, copying that back to .env.

Hopefully that helps somebody :)

<?php

class FeatureContext extends MinkContext implements Context, SnippetAcceptingContext
{
    //... 

    /**
     * Flag the test database as ready for testing.
     *
     * @var bool
     */
    public static $databaseReady = false;

    /**
     * @BeforeScenario
     */
    public function onSetup()
    {
        if (!self::$databaseReady) {
            if (file_exists('storage/database.sqlite')) {
                shell_exec('rm storage/database.sqlite');
            }
            shell_exec('touch storage/database.sqlite');
            shell_exec('cp .env.behat .env');
            Artisan::call('migrate:refresh', ['--seed' => true]);

            self::$databaseReady = true;
        }
    }

    /**
     * @AfterSuite
     */
    public static function afterSuite()
    {
        shell_exec('rm .env');
        shell_exec('rm storage/database.sqlite');
    }

    // ...
}

And my environment file looks something like this...

APP_ENV=testing
APP_DEBUG=true
APP_KEY=SomeRandomKey

DB_CONNECTION=sqlite
CACHE_DRIVER=file
SESSION_DRIVER=database/file
QUEUE_DRIVER=sync

# Using the Chrome Driver, so these should be left blank for
# testing, chrome won't parse sessions/cookies under localhost
SESSION_DOMAIN=
COOKIE_DOMAIN=
4 likes
jimmck's avatar

@eoghanobrien Your Welcome! Thanks for the env follow up. This like free Robo Test. I like the SQLLite and .env idea !!

Please or to participate in this conversation.