docred5's avatar

Dusk Click Element in Iframe

Hello, i am trying to automate a process (using dusk) on another site but the last page has an iframe which i need to click a button in. is there any way to do this in dusk? i cant seem to find any information on iframes with dusk.

i have used selenium before and that has a swtich to iframe method. is there a dusk equivalent?

0 likes
10 replies
kyrpas's avatar
kyrpas
Best Answer
Level 14

Hey there,

there is no official, documented way to switch frames with Dusk but facebook's webdriver (used by Dusk) supports it, so it's possible.

Fortunately, the Browser class used in Dusk tests is Macroable (as mentioned in https://medium.com/@splatEric/writing-tests-with-laravel-dusk-bb755c0dcb1f#.47d4xdb5e) which means we can easily extend Dusk's functionality without making too much of a mess or messing with vendor files.

Following the directions in the linked article, we can first create a new ServiceProvider:

$ php artisan make:provider DuskBrowserServiceProvider

And add the following in its boot method:

Browser::macro('switchFrame', function($frame) {
    $this->driver->switchTo()->defaultContent()->switchTo()->frame($frame);
    return $this;
});

(Make sure to use Laravel\Dusk\Browser; in the ServiceProvider.)

Then, we add this new provider in the register method of AppServiceProvider. Right after the initial DuskServiceProvider:

public function register()
{
    if ($this->app->environment('local', 'testing')) {
        $this->app->register(DuskServiceProvider::class);
        $this->app->register(DuskBrowserServiceProvider::class);
    }
}

Now, we can call the switchFrame method in any Dusk test.

Here's how I did it while trying to test Paypal's sandbox login and payment:

$browser->loginAs($user)
    ->visit('/user')
    ->assertSee('Some Event')
    ->clickLink('Register Now')
    ->assertSee('Some Event')
    ->check('accept_terms')
    ->press('Submit Application')
    ->assertSee('Your application has been submitted.')
    ->press('.paypal-button')
    ->waitFor('#injectedUnifiedLogin', 30)
    ->switchFrame('injectedUl')  // <---------------- here's where we switch frames
    ->type('#email', env('PAYPAL_TEST_BUYER_USERNAME')) // to access this nasty field
    ->type('#password', env('PAYPAL_TEST_BUYER_PASSWORD')) // and this one.. 
    ->press('#btnLogin') // apparently, it remembers the frame switch
    ->waitFor('#confirmButtonTop', 30)
    ->waitUntilMissing('#spinner')
    ->press('#confirmButtonTop')
    ->waitForText('You paid', 30)
    ->waitUntilMissing('#spinner')
    ->press('#merchantReturnBtn')
    ->waitForText('Events Registration', 30)
    ->pause(10000) // waiting for IPN callback from paypal
    ->refresh()
    ->assertSee('Payment verified')
    ;

Not sure if it's the best way to do this but it definitely works. Hope it helps!

9 likes
nshontz's avatar

Thanks @kyrpas, I needed to switch back to the default frame and added this:


Browser::macro('switchFrameDefault', function () {
        $this->driver->switchTo()->defaultContent()->switchTo()->defaultContent();
        return $this;
});
dimsav's avatar

Very helpful @kyrpas. Here is a modified version allowing switching back to the main frame by passing no frame at all.

Browser::macro('switchFrame', function ($frame = null) {
    if ($frame) {
        $this->driver->switchTo()->defaultContent()->switchTo()->frame($frame);
    } else {
        // Main frame
        $this->driver->switchTo()->defaultContent();
    }
    return $this;
});
6 likes
scrapy's avatar

Hey @kyrpas @dimsav @lollypopgr , what is "injectedUl", is it ID or class, by which we can switch between IFrames

->switchFrame('injectedUl') // <---------------- here's where we switch frames

Please help :)

kyrpas's avatar

Hi @scrapy, "injectedUl" is the value of the name attribute of the iframe we want to switch to.

1 like
yerke's avatar

Thanks to @kyrpas for this post.

If you ran into a problem that Paypal login window opens in a separate window, and waitFor('#injectedUnifiedLogin', 30) doesn't work for you, you can switch to that new window:

        // Get the last opened tab
        $window = collect($browser->driver->getWindowHandles())->last();

        // Switch to the tab
        $browser->driver->switchTo()->window($window);

        $browser
            ->waitFor('iframe[name=injectedUl]', 30)
timothy's avatar

I found this very helpful! thank you @kyrpas and @dimsav :)

I've updated one line of code so that it can use the Page class' elements property or the css selector:

Browser::macro('switchFrame', function ($frame = null) {
            if ($frame) {
                // updated it so it can use the page's elements property or css selectors
                $this->driver->switchTo()->defaultContent()->switchTo()->frame($this->resolver->findOrFail($frame));
            } else {
                // Main frame
                $this->driver->switchTo()->defaultContent();
            }

            return $this;
        });
qeadzcwsx's avatar

Hi, I tried this, but doesn't appear to switch into iframes correctly. I'm using Laravel 5.5 and Dusk 2.2. What am I doing wrong?

mjlovefl's avatar

Does this still work for anyone using Dusk ^3.0 (Laravel 5.6). ChromeDriver: 2.45.615355

It worked for me using Laravel 5.4. But since upgrading, it is no longer working for me?

The macro itself does not seem to have any errors, but Dusk is unable to find any selectors in the iframe.

Please or to participate in this conversation.