22

I am getting started using Laravel Dusk for browser testing, and have created a couple of tests to test my login form. I have the following code:

class LoginTest extends DuskTestCase
{

public function testLogin()
{
    $this->browse(function (Browser $browser) {
        $browser->visit('/admin')
            ->type('email', 'inigo@mydomain.co.uk')
            ->type('password', 'MyPass')
            ->press('Login')
            ->assertSee('Loading...');
    });
}

public function testLoginFailure(){
    $this->browse(function (Browser $browser){

        $browser->visit('/admin/logout'); // I have to add this to logout first, otherwise it's already logged in for this test!

        $browser->visit('/admin')
            ->type('email', 'someemail@afakedomain.com')
            ->type('password', 'somefakepasswordthatdoesntwork')
            ->press('Login')
            ->assertSee('These credentials do not match our records.');
    });
}

See the comment. The first function runs fine, but when it comes to the second function, I have to logout first, since the user is already logged in as a result of running the first function. This came as a surprise to me as I thought unit tests were completely independent, with session data being destroyed automatically.

Is there a better way of doing this- some Dusk method that I'm missing perhaps- than having to call $browser->visit('/admin/logout'); ?

Thanks

EDIT Thanks for the 2 answers so far, which both seem valid solutions. I've updated the second function to the following:

public function testLoginFailure(){
    $this->createBrowsersFor(function(Browser $browser){
        $browser->visit('/admin')
            ->type('email', 'someshit@afakedomain.com')
            ->type('password', 'somefakepasswordthatdoesntwork')
            ->press('Login')
            ->assertSee('These credentials do not match our records.');
    });
}

Which does the job. So

  1. I can safely assume that this second browser only exists for the duration of this single function, correct?
  2. What are the obvious advantages/disadvantages of creating a second browser instance rather than using the teardown method?
Jeff Puckett
  • 37,464
  • 17
  • 118
  • 167
Inigo
  • 8,110
  • 18
  • 62
  • 110

8 Answers8

25

In my case, tearDown() was not enough, for some reason, a logged users was still persisted between tests, so I placed deleteAllCookies() at setUp().

So, in my DuskTestCase.php I added:

/**
 * Temporal solution for cleaning up session
 */
protected function setUp()
{
    parent::setUp();
    foreach (static::$browsers as $browser) {
        $browser->driver->manage()->deleteAllCookies();
    }
}

It was the only way I could flush up session around all tests. I hope it helps.

Note: I'm using Homestead and Windows 10.

Zalo
  • 642
  • 12
  • 24
  • 1
    I'm on Homestead and Win 10 too, and even though I upvoted this a long time ago, I'm back again, and it's not working for me. Even when I prove via `Log::debug('cookies=' . json_encode($browser->driver->manage()->getCookies()));` that the cookies have been deleted, the Laravel login session continues. It's crazy! See also https://github.com/laravel/dusk/issues/100 and https://github.com/laravel/dusk/pull/268 – Ryan Apr 26 '19 at 12:50
  • 2
    Wow, it took me about 15 hours of work over the past couple days, and there were lots of changes that I made to finally get it to work. This time, I think one of the most important hints was a different answer on this page: https://stackoverflow.com/a/50293010/470749 – Ryan Apr 26 '19 at 21:36
  • It don't works for me. The 2nd test will start with browser already logged – realtebo Nov 23 '22 at 13:27
10

You could flush the session in a tearDown() method:

class LoginTest extends DuskTestCase
{
    // Your tests

    public function tearDown()
    {
        session()->flush();

        parent::tearDown();
    }
}
Martin Bean
  • 38,379
  • 25
  • 128
  • 201
10

If you just want to logout your signed in user, after login test, simply use:

 $browser->visit('/login')
     ->loginAs(\App\User::find(1))
     ...
     some assertions
     ...
     ->logout();
branci
  • 501
  • 5
  • 6
7

If in case it helps some one else. You can use tearDown method to clear all cookies. Following is the example of doing so, you can add this method in DuskTestCase.php file

public function tearDown()
{
    parent::tearDown();

    $this->browse(function (Browser $browser) {
        $browser->driver->manage()->deleteAllCookies();
    });
}

I hope this will help.

hashmi
  • 651
  • 2
  • 9
  • 24
6

I'm not sure if its the answer you are looking for, but you can create multiple browsers to perform a test that requires clean history/cookies/cache.

For example, if you have lots of tests where the user should be logged in and you don't want to log out for only one test that checks reset password form, then you can create an additional browser for this exact case and switch to the previous browser when moving to the next tests.

It might look the next way:

public function testfirstTest()
{
  $this->browse(function ($browser) {
    //some actions where you login etc
  });
}

public function testSecondTestWhereYouNeedToBeLoggedOut()
{
  $this->browse(function ($currentBrowser, $newBrowser) {
  //here the new browser will be created, at the same time your previous browser window won't be closed, but history/cookies/cache will be empty for the newly created browser window
    $newBrowser->visit('/admin')
    //do some actions
    $newBrowser->quit();//here you close this window
  });
}

public function testWhereYouShouldContinueWorkingWithUserLoggedIn()
{
  $this->browse(function ($browser) {
    $browser->doSomething()//here all actions will be performed in the initially opened browser with user logged in
  });
}
  • Thank you so much for this example! I spent so many hours fighting with this problem. – Ryan Apr 26 '19 at 21:37
  • This solution worked when trying to switch from an ordinary user in a previous test, to an admin user in a subsequent test. Thanks for sharing. – w5m Aug 20 '19 at 10:59
4

You can call

$this->logout();

InteractsWithAuthentication - Github

Edit:

This is the behaviour of the Dusk Test case, the primary browser instance remains for the other tests.

A second solution, create a second browser instance, which will be destroyed after the single test

See Dusk/TestCase #L103

OuailB
  • 1,099
  • 7
  • 9
  • Thank you. Probably just calling `$this->logout()` will suffice for what I'm currently trying to achieve. Please take a look at the edit to my question :) – Inigo Jul 04 '17 at 14:31
  • Also, here is a recent unanswered question on SO that I came across while researching this: https://stackoverflow.com/questions/44657869/how-to-reuse-a-dusk-test-browser-instance I guess the OP is actually incorrect in his assumption that *"I'm spawning 4-5 browser instances per test class"* if, as you say, *" the primary browser instance remains for the other tests"*? – Inigo Jul 04 '17 at 14:33
  • This is incorrect. `InteractsWithAuthentication` is available on the `Browser` instance. You'd have to call `$this->browse(fn (Browser $browser) => $browser->logout()`, not `$this->logout()` in the test itself. – Steve Bauman Sep 16 '22 at 16:36
1

You can pair the setUp method, alongside Dusk's logout method on the Browser instance:

/**
 * Log the user out before each test.
 *
 * @return void
 */
protected function setUp(): void
{
    parent::setUp();

    $this->browse(
        fn (Browser $browser) => $browser->logout()
    );
}
Steve Bauman
  • 8,165
  • 7
  • 40
  • 56
0

If you are interested in only logging out the current user you can achieve it by doing

     $this->browse(function($browser) {
        $browser->visit('/logout')
                ->waitForText('Sign Into Your Account');
    });

Note: Please change the text 'Sign Into Your Account' by the text displayed on your login page.

Saransh Kumar
  • 421
  • 4
  • 6