Browse Source

update tests

update
jqh 5 years ago
parent
commit
3de2bed815

+ 2 - 0
browser-tests/.ide-helper.php

@@ -2,6 +2,7 @@
 
 namespace Laravel\Dusk
 {
+    use Laravel\Dusk\Component;
 
     /**
      * @method $this whenTextAvailable(string $text, $callbackOrSeconds = null, int $seconds = null)
@@ -9,6 +10,7 @@ namespace Laravel\Dusk
      * @method $this hasInput($field)
      * @method $this wait(int $seconds, $callback = null)
      * @method $this assertHidden($selector)
+     * @method $this assert(Component $component)
      */
     class Browser
     {

+ 1 - 1
browser-tests/Browser/AuthTest.php

@@ -16,7 +16,7 @@ class AuthTest extends TestCase
     {
         $this->browse(function (Browser $browser) {
             $browser->visit(test_admin_path('auth/login'))
-                ->assertSee('Login');
+                ->assertSee(__('admin.login'));
         });
     }
 

+ 32 - 0
browser-tests/Browser/Components/Component.php

@@ -0,0 +1,32 @@
+<?php
+
+namespace Tests\Browser\Components;
+
+use Laravel\Dusk\Browser;
+use Laravel\Dusk\Component as BaseComponent;
+
+abstract class Component extends BaseComponent
+{
+    /**
+     * @param Browser $browser
+     *
+     * @return string
+     */
+    public function parentSelector(Browser $browser)
+    {
+        return str_replace($this->selector(), '', $browser->resolver->prefix);
+    }
+
+    /**
+     * 获取完整的css选择器
+     *
+     * @param Browser $browser
+     * @param string $selector
+     *
+     * @return string
+     */
+    public function formatSelector(Browser $browser, $selector = null)
+    {
+        return $this->parentSelector($browser).' '.($selector ?: $this->selector());
+    }
+}

+ 8 - 4
browser-tests/Browser/Components/MultipleSelect2.php → browser-tests/Browser/Components/Form/Field/MultipleSelect2.php

@@ -1,6 +1,6 @@
 <?php
 
-namespace Tests\Browser\Components;
+namespace Tests\Browser\Components\Form\Field;
 
 use Laravel\Dusk\Browser;
 
@@ -11,18 +11,22 @@ class MultipleSelect2 extends Select2
      *
      * @param  Browser $browser
      * @param  array   $values
-     * @param  int  $day
-     * @return void
+     *
+     * @return Browser
      */
     public function choose($browser, $values)
     {
         $values = implode(',', (array) $values);
 
+//dump($browser->resolver->prefix.' || '.$this->formatSelector($browser));
+
         $browser->script(
             <<<JS
 var values = '{$values}';
-$('{$this->selector()}').val(values.split(',')).change();
+$('{$this->formatSelector($browser)}').val(values.split(',')).change();
 JS
         );
+
+        return $browser;
     }
 }

+ 8 - 6
browser-tests/Browser/Components/Select2.php → browser-tests/Browser/Components/Form/Field/Select2.php

@@ -1,11 +1,11 @@
 <?php
 
-namespace Tests\Browser\Components;
+namespace Tests\Browser\Components\Form\Field;
 
 use Laravel\Dusk\Browser;
-use Laravel\Dusk\Component as BaseComponent;
+use Tests\Browser\Components\Component;
 
-class Select2 extends BaseComponent
+class Select2 extends Component
 {
     protected $selector;
 
@@ -54,14 +54,16 @@ class Select2 extends BaseComponent
      * @param  Browser  $browser
      * @param  mixed    $value
      *
-     * @return void
+     * @return Browser
      */
-    public function choose($browser, $value)
+    public function choose(Browser $browser, $value)
     {
         $browser->script(
             <<<JS
-$('{$this->selector()}').val('{$value}').change();
+$('{$this->formatSelector($browser)}').val('{$value}').change();
 JS
         );
+
+        return $browser;
     }
 }

+ 120 - 0
browser-tests/Browser/Components/Form/Field/Tree.php

@@ -0,0 +1,120 @@
+<?php
+
+namespace Tests\Browser\Components\Form\Field;
+
+use Laravel\Dusk\Browser;
+use Tests\Browser\Components\Component;
+
+class Tree extends Component
+{
+    protected $name;
+
+    public function __construct($name = null)
+    {
+        $this->name = $name;
+    }
+
+    /**
+     * 获取组件的 root selector
+     *
+     * @return string
+     */
+    public function selector()
+    {
+        return ".{$this->name}-tree-wrapper";
+    }
+
+    /**
+     * 浏览器包含组件的断言
+     *
+     * @param Browser $browser
+     * @return void
+     */
+    public function assert(Browser $browser)
+    {
+        $browser
+            ->whenElementAvailable('@tree', 2)
+            ->hasInput($this->name);
+    }
+
+    /**
+     * 读取组件的元素快捷方式
+     *
+     * @return array
+     */
+    public function elements()
+    {
+        return [
+            '@container' => $this->selector(),
+            '@tree'      => "{$this->selector()} .da-tree",
+            '@input'     => sprintf('input[name="%s"][type="hidden"]', $this->name),
+        ];
+    }
+
+    /**
+     * 选中下拉选框
+     *
+     * @param Browser $browser
+     * @param mixed   $values
+     *
+     * @return Browser
+     */
+    public function choose(Browser $browser, $values)
+    {
+        $values = json_encode((array) $values);
+
+        $browser->script(<<<JS
+var tree = $('{$this->getTreeSelector($browser)}');        
+        
+tree.jstree("uncheck_all");
+tree.jstree("select_node", {$values});
+JS
+        );
+
+        return $browser;
+    }
+
+    /**
+     * 选中所有
+     *
+     * @param Browser $browser
+     *
+     * @return Browser
+     */
+    public function checkAll(Browser $browser)
+    {
+        $browser->script(<<<JS
+$('{$this->getTreeSelector($browser)}').jstree("check_all");        
+JS
+        );
+
+        return $browser;
+    }
+
+    /**
+     * 取消选中所有
+     *
+     * @param Browser $browser
+     *
+     * @return Browser
+     */
+    public function unCheckAll(Browser $browser)
+    {
+        $browser->script(<<<JS
+$('{$this->getTreeSelector($browser)}').jstree("uncheck_all");        
+JS
+        );
+
+        return $browser;
+    }
+
+    /**
+     * @param \Laravel\Dusk\Browser $browser
+     *
+     * @return string
+     */
+    protected function getTreeSelector(Browser $browser)
+    {
+        return $this->formatSelector($browser, $this->elements()['@tree']);
+    }
+}

+ 130 - 0
browser-tests/Browser/Components/Form/MenuCreationForm.php

@@ -0,0 +1,130 @@
+<?php
+
+namespace Tests\Browser\Components\Form;
+
+use Laravel\Dusk\Browser;
+use Tests\Browser\Components\Component;
+use Tests\Browser\Components\Form\Field\MultipleSelect2;
+use Tests\Browser\Components\Form\Field\Select2;
+use Tests\Browser\Components\Form\Field\Tree;
+
+class MenuCreationForm extends Component
+{
+    protected $selector;
+
+    public function __construct($selector = 'form[method="POST"]')
+    {
+        $this->selector = $selector;
+    }
+
+    /**
+     * 获取组件的 css selector
+     *
+     * @return string
+     */
+    public function selector()
+    {
+        return $this->selector;
+    }
+
+    /**
+     * 浏览器包含组件的断言
+     *
+     * @param  Browser  $browser
+     * @return void
+     */
+    public function assert(Browser $browser)
+    {
+        $browser->assertSee(__('admin.submit'))
+            ->assertSee(__('admin.reset'))
+            ->within('@form', function (Browser $browser) {
+                $browser
+                    ->assertSee(__('admin.parent_id'))
+                    ->assertSee(__('admin.title'))
+                    ->assertSee(__('admin.icon'))
+                    ->assertSee(__('admin.uri'))
+                    ->assertSee(__('admin.roles'))
+                    ->assertSee(__('admin.permission'))
+                    ->assertSee(__('admin.selectall'))
+                    ->assertSee(__('admin.expand'))
+                    ->hasInput('title')
+                    ->hasInput('icon')
+                    ->hasInput('uri')
+                    ->assertSelected('parent_id', 0)
+                    ->assert(new Tree('permissions'))
+                    ->assert(new Select2('select[name="parent_id"]'))
+                    ->assert(new MultipleSelect2('select[name="roles[]"]'));
+            });
+    }
+
+    /**
+     * 注入表单
+     *
+     * @param Browser $browser
+     * @param array $input
+     *
+     * @return Browser
+     */
+    public function fill(Browser $browser, array $input)
+    {
+        $inputKeys = [
+            'title',
+            'icon',
+            'uri',
+        ];
+
+        $selectKeys = [
+            'parent_id'
+        ];
+
+        $multipleSelectKeys = [
+            'roles',
+        ];
+
+        foreach ($input as $key => $value) {
+            if (in_array($key, $inputKeys, true)) {
+                $browser->type($key, $value);
+
+                continue;
+            }
+
+            if (in_array($key, $selectKeys, true)) {
+                $selector = sprintf('select[name="%s"]', $key);
+                $browser->within(new Select2($selector), function ($browser) use ($value) {
+                    $browser->choose($value);
+                });
+
+                continue;
+            }
+
+            if (in_array($key, $multipleSelectKeys, true)) {
+                $selector = sprintf('select[name="%s[]"]', $key);
+                $browser->within(new MultipleSelect2($selector), function ($browser) use ($value) {
+                    $browser->choose($value);
+                });
+
+                continue;
+            }
+
+            if ($key === 'permissions') {
+                $browser->within(new Tree($key), function ($browser) use ($value) {
+                    $browser->choose($value);
+                });
+            }
+        }
+
+        return $browser;
+    }
+
+    /**
+     * 读取组件的元素快捷方式
+     *
+     * @return array
+     */
+    public function elements()
+    {
+        return [
+            '@form' => $this->selector,
+        ];
+    }
+}

+ 67 - 0
browser-tests/Browser/Components/Form/MenuEditForm.php

@@ -0,0 +1,67 @@
+<?php
+
+namespace Tests\Browser\Components\Form;
+
+use Dcat\Admin\Models\Menu;
+use Laravel\Dusk\Browser;
+use Tests\Browser\Components\Form\Field\MultipleSelect2;
+use Tests\Browser\Components\Form\Field\Select2;
+use Tests\Browser\Components\Form\Field\Tree;
+
+class MenuEditForm extends MenuCreationForm
+{
+    protected $id;
+    protected $selector;
+
+    public function __construct($id = null, $selector = 'form[method="POST"]')
+    {
+        if ($id && ! is_numeric($id)) {
+            $selector = $id;
+            $id = null;
+        }
+
+        $this->id = $id;
+        $this->selector = $selector;
+    }
+
+    /**
+     * 浏览器包含组件的断言
+     *
+     * @param  Browser  $browser
+     * @return void
+     */
+    public function assert(Browser $browser)
+    {
+        $browser->assertSee(__('admin.submit'))
+            ->assertSee(__('admin.reset'))
+            ->within('@form', function (Browser $browser) {
+                $browser
+                    ->assertSee('ID')
+                    ->assertSee(__('admin.parent_id'))
+                    ->assertSee(__('admin.title'))
+                    ->assertSee(__('admin.icon'))
+                    ->assertSee(__('admin.uri'))
+                    ->assertSee(__('admin.roles'))
+                    ->assertSee(__('admin.permission'))
+                    ->assertSee(__('admin.created_at'))
+                    ->assertSee(__('admin.updated_at'))
+                    ->assertSee(__('admin.selectall'))
+                    ->assertSee(__('admin.expand'))
+                    ->hasInput('title')
+                    ->hasInput('icon')
+                    ->hasInput('uri')
+                    ->assert(new Tree('permissions'))
+                    ->assert(new Select2('select[name="parent_id"]'))
+                    ->assert(new MultipleSelect2('select[name="roles[]"]'));
+
+                if (! $this->id) {
+                    return;
+                }
+
+                $menu = Menu::find($this->id);
+                if ($menu) {
+                    $browser->assertSelected('parent_id', $menu->parent_id);
+                }
+            });
+    }
+}

+ 6 - 4
browser-tests/Browser/IndexTest.php

@@ -44,14 +44,16 @@ class IndexTest extends TestCase
                         ->assertPathIs(test_admin_path('auth/menu'))
                         ->clickLink('Operation log')
                         ->assertPathIs(test_admin_path('auth/logs'))
+                        ->clickLink('Helpers')
+                        ->whenTextAvailable('Extensions', 2)
                         ->clickLink('Extensions')
-                        ->assertPathIs(test_admin_path('auth/extensions'))
+                        ->assertPathIs(test_admin_path('helpers/extensions'))
                         ->clickLink('Scaffold')
-                        ->assertPathIs(test_admin_path('auth/scaffold'))
+                        ->assertPathIs(test_admin_path('helpers/scaffold'))
                         ->clickLink('Routes')
-                        ->assertPathIs(test_admin_path('auth/routes'))
+                        ->assertPathIs(test_admin_path('helpers/routes'))
                         ->clickLink('Icons')
-                        ->assertPathIs(test_admin_path('auth/icons'));
+                        ->assertPathIs(test_admin_path('helpers/icons'));
                 });
         });
     }

+ 87 - 31
browser-tests/Browser/MenuTest.php

@@ -4,7 +4,10 @@ namespace Tests\Browser;
 
 use Dcat\Admin\Models\Menu;
 use Laravel\Dusk\Browser;
-use Tests\Browser\Components\MultipleSelect2;
+use Tests\Browser\Components\Form\MenuEditForm;
+use Tests\Browser\Components\Form\Field\MultipleSelect2;
+use Tests\Browser\Components\Form\Field\Select2;
+use Tests\Browser\Pages\MenuEditPage;
 use Tests\Browser\Pages\MenuPage;
 use Tests\TestCase;
 
@@ -28,28 +31,20 @@ class MenuTest extends TestCase
                 'title'       => 'Test',
                 'uri'         => 'test',
                 'icon'        => 'fa-user',
+                'roles'       => [1],
+                'permissions' => [4, 5],
             ];
 
-            $roles = [1];
-
-            $browser->visit(new MenuPage())
-                ->select('parent_id', $item['parent_id'])
-                ->type('title', $item['title'])
-                ->type('uri', $item['uri'])
-                ->type('icon', $item['icon'])
-                ->click('.row')
-                ->within(new MultipleSelect2('select[name="roles[]"]'), function (Browser $browser) use ($item, $roles) {
-                    $browser->choose($roles);
-                })
-                ->pressAndWaitFor('Submit')
-                ->waitForText(__('admin.save_succeeded'), 2)
-                ->assertPathIs(test_admin_path('auth/menu'));
+            $browser
+                ->visit(new MenuPage())
+                ->newMenu($item)
+                ->waitForText(__('admin.save_succeeded'), 2);
 
             $newMenuId = Menu::query()->orderByDesc('id')->first()->id;
 
-            $this->seeInDatabase(config('admin.database.menu_table'), $item)
-                ->seeInDatabase(config('admin.database.role_menu_table'), ['role_id' => $roles, 'menu_id' => $newMenuId])
-                ->assertEquals(8, Menu::count());
+            // 检测是否写入数据库
+            $this->assertDatabase($newMenuId, $item);
+            $this->assertEquals(8, Menu::count());
         });
     }
 
@@ -62,24 +57,85 @@ class MenuTest extends TestCase
     public function testEditMenu()
     {
         $this->browse(function (Browser $browser) {
-            $browser->visit(test_admin_path('auth/menu/1/edit'))
-                ->assertSee('Menu')
-                ->assertSee('Edit')
+            $browser->visit(new MenuEditPage(1))
                 ->type('title', 'blablabla')
-                ->press('Submit')
+                ->press(__('admin.submit'))
                 ->waitForLocation(test_admin_path('auth/menu'), 2);
 
             $this->seeInDatabase(config('admin.database.menu_table'), ['title' => 'blablabla'])
                 ->assertEquals(7, Menu::count());
         });
     }
-    //
-    //public function testEditMenuParent()
-    //{
-    //    $this->expectException(\Laravel\BrowserKitTesting\HttpException::class);
-    //
-    //    $this->visit('admin/auth/menu/5/edit')
-    //        ->see('Menu')
-    //        ->submitForm('Submit', ['parent_id' => 5]);
-    //}
+
+    public function testEditMenuParent()
+    {
+        $this->browse(function (Browser $browser) {
+            $id = 5;
+
+            $browser->visit(new MenuEditPage($id))
+                ->within(new Select2('select[name="parent_id"]'), function ($browser) use ($id) {
+                    $browser->choose($id);
+                })
+                ->press(__('admin.submit'))
+                ->waitForText('500 Internal Server Error', 2);
+        });
+    }
+
+    public function testQuickEditMenu()
+    {
+        $this->browse(function (Browser $browser) {
+            $id = 5;
+
+            $updates = [
+                'title'       => 'balabala',
+                'icon'        => 'fa-list',
+                'parent_id'   => 0,
+                'roles'       => 1,
+                'permissions' => [4, 5, 6],
+            ];
+
+            $browser->visit(new MenuPage())
+                ->within(sprintf('li[data-id="%d"]', $id), function (Browser $browser) {
+                    $browser->click('.tree-quick-edit');
+                })
+                ->whenAvailable('.layui-layer-page', function (Browser $browser) use ($id, $updates) {
+                    $browser->whenElementAvailable(new MenuEditForm($id), function (Browser $browser) use ($updates) {
+                        // 检测表单
+                        $browser->fill($updates);
+                    }, 3)
+                        ->assertSee(__('admin.edit'))
+                        ->click('div')
+                        ->whenElementAvailable(new MultipleSelect2('select[name="roles[]"]'), function (Browser $browser) {
+                            $browser->choose(1);
+                        }, 2)
+                        ->clickLink(__('admin.submit'));
+                }, 3)
+                ->waitForText(__('admin.update_succeeded'), 3)
+                ->waitForLocation(test_admin_path('auth/menu'), 2)
+                ->waitForText('balabala', 2);
+
+            // 检测是否写入数据库
+            $this->assertDatabase($id, $updates);
+        });
+    }
+
+    private function assertDatabase($id, $updates)
+    {
+        $roles = $updates['roles'];
+        $permissions = $updates['permissions'];
+
+        unset($updates['roles'], $updates['permissions']);
+
+        // 检测是否写入数据库
+        return $this
+            ->seeInDatabase(config('admin.database.menu_table'), $updates)
+            ->seeInDatabase(
+                config('admin.database.role_menu_table'),
+                ['role_id' => $roles, 'menu_id' => $id]
+            )
+            ->seeInDatabase(
+                config('admin.database.permission_menu_table'),
+                ['permission_id' => $permissions, 'menu_id' => $id]
+            );
+    }
 }

+ 55 - 0
browser-tests/Browser/Pages/MenuEditPage.php

@@ -0,0 +1,55 @@
+<?php
+
+namespace Tests\Browser\Pages;
+
+use Laravel\Dusk\Browser;
+use Tests\Browser\Components\Form\MenuEditForm;
+
+class MenuEditPage extends Page
+{
+    protected $id;
+
+    public function __construct($id)
+    {
+        $this->id = $id;
+    }
+
+    /**
+     * Get the URL for the page.
+     *
+     * @return string
+     */
+    public function url()
+    {
+        return test_admin_path("auth/menu/{$this->id}/edit");
+    }
+
+    /**
+     * Assert that the browser is on the page.
+     *
+     * @param  \Laravel\Dusk\Browser  $browser
+     * @return void
+     */
+    public function assert(Browser $browser)
+    {
+        $browser->assertSee(__('admin.menu'))
+            ->assertSee(__('admin.edit'))
+            ->assertSee(__('admin.list'))
+            ->assertSee(__('admin.delete'))
+            ->assertSee(__('admin.submit'))
+            ->assertSee(__('admin.reset'))
+            ->assert(new MenuEditForm($this->id));
+    }
+
+    /**
+     * Get the element shortcuts for the page.
+     *
+     * @return array
+     */
+    public function elements()
+    {
+        return [
+            '@form' => 'form[method="POST"]',
+        ];
+    }
+}

+ 27 - 7
browser-tests/Browser/Pages/MenuPage.php

@@ -3,6 +3,7 @@
 namespace Tests\Browser\Pages;
 
 use Laravel\Dusk\Browser;
+use Tests\Browser\Components\Form\MenuCreationForm;
 
 class MenuPage extends Page
 {
@@ -39,13 +40,14 @@ class MenuPage extends Page
                     ->assertSee('Operation log');
             }, 1)
             ->within('@form', function (Browser $browser) {
-                $browser->assertSee('Parent')
-                    ->assertSee('Title')
-                    ->assertSee('Icon')
-                    ->assertSee('URI')
-                    ->assertSee('Roles')
-                    ->assertSee('Permission')
-                    ->assertSee('Select all')
+                $browser->assertSee(__('admin.parent_id'))
+                    ->assertSee(__('admin.title'))
+                    ->assertSee(__('admin.icon'))
+                    ->assertSee(__('admin.uri'))
+                    ->assertSee(__('admin.roles'))
+                    ->assertSee(__('admin.permissions'))
+                    ->assertSee(__('admin.selectall'))
+                    ->assertSee(__('admin.expand'))
                     ->assertSelected('parent_id', 0)
                     ->hasInput('title')
                     ->hasInput('icon')
@@ -55,6 +57,24 @@ class MenuPage extends Page
             });
     }
 
+    /**
+     * 创建
+     *
+     * @param Browser $browser
+     * @param array $input
+     *
+     * @return Browser
+     */
+    public function newMenu(Browser $browser, array $input)
+    {
+        return $browser->within(new MenuCreationForm(), function (Browser $browser) use ($input) {
+            $browser->fill($input);
+
+            $browser->pressAndWaitFor(__('admin.submit'), 2);
+            $browser->waitForLocation($this->url(), 2);
+        });
+    }
+
     /**
      * Get the element shortcuts for the page.
      *

+ 60 - 94
browser-tests/BrowserExtension.php

@@ -6,119 +6,85 @@ use Facebook\WebDriver\Exception\TimeoutException;
 use Illuminate\Support\Arr;
 use Illuminate\Support\Str;
 use Laravel\Dusk\Browser;
+use Laravel\Dusk\Component;
 use PHPUnit\Framework\Assert as PHPUnit;
 
 trait BrowserExtension
 {
     public function extendBrowser()
     {
-        $this->extendBrowserWhenTextAvailable();
-        $this->extendBrowserWhenElementAvailable();
-        $this->extendBrowserWait();
-        $this->extendBrowserAssertHasInput();
-        $this->extendBrowserAssertHidden();
-    }
-
-    private function extendBrowserAssertHidden()
-    {
-        Browser::macro('assertHidden', function ($selector) {
-            $fullSelector = $this->resolver->format($selector);
-
-            PHPUnit::assertTrue(
-                $this->resolver->findOrFail($selector)->isDisplayed(),
-                "Element [{$fullSelector}] is visible."
-            );
-
-            return $this;
-        });
-    }
-
-    private function extendBrowserWait()
-    {
-        $self = $this;
-
-        Browser::macro('wait', function ($seconds, \Closure $callback = null) use ($self) {
-            $delayBrowser = $self->makeDelayBrowser($this);
-
-            try {
-                $this->waitUsing($seconds, 200, function () {});
-            } catch (TimeoutException $e) {
-                $callback && $callback();
-
-                $delayBrowser();
-            }
-
-            return $delayBrowser;
-        });
-    }
-
-    private function extendBrowserAssertHasInput()
-    {
-        Browser::macro('hasInput', function ($field) {
-            /* @var \Facebook\WebDriver\Remote\RemoteWebElement $element */
-            $this->resolver->resolveForTyping($field);
-
-            return $this;
-        });
-    }
-
-    private function extendBrowserWhenElementAvailable()
-    {
-        $self = $this;
-
-        Browser::macro('whenElementAvailable', function ($selector, $callbackOrSeconds = null, $seconds = null) use ($self) {
-            /* @var Browser $this */
-
-            $callback = null;
-            if (is_callable($callbackOrSeconds)) {
-                $callback = $callbackOrSeconds;
-            } elseif (is_int($callbackOrSeconds)) {
-                $seconds = $callbackOrSeconds;
-            }
+        $functions = [
+            'whenTextAvailable' => function ($text, $callbackOrSeconds = null, $seconds = null) {
+                $callback = null;
+
+                if (is_callable($callbackOrSeconds)) {
+                    $callback = $callbackOrSeconds;
+                } elseif (is_int($callbackOrSeconds)) {
+                    $seconds = $callbackOrSeconds;
+                }
 
-            $delayBrowser = $self->makeDelayBrowser($this);
+                $text = Arr::wrap($text);
+                $message = $this->formatTimeOutMessage('Waited %s seconds for text', implode("', '", $text));
 
-            $this->waitFor($selector, $seconds)->with($selector, function ($value) use ($callback, $delayBrowser) {
-                $callback && $callback($value);
+                return $this->waitUsing($seconds, 100, function () use ($text, $callback)  {
+                    $results = Str::contains($this->resolver->findOrFail('')->getText(), $text);
 
-                return $delayBrowser();
-            });
+                    if ($results) {
+                        $callback && $callback($this);
+                    }
 
-            return $delayBrowser;
-        });
-    }
+                    return $results;
+                }, $message);
+            },
 
-    private function extendBrowserWhenTextAvailable()
-    {
-        $self = $this;
+            'whenElementAvailable' => function ($selector, $callbackOrSeconds = null, $seconds = null) {
+                $callback = null;
+                if (is_callable($callbackOrSeconds)) {
+                    $callback = $callbackOrSeconds;
+                } elseif (is_int($callbackOrSeconds)) {
+                    $seconds = $callbackOrSeconds;
+                }
 
-        Browser::macro('whenTextAvailable', function ($text, $callbackOrSeconds = null, $seconds = null) use ($self) {
-            $callback = null;
+                return $this->whenAvailable($selector, function ($value) use ($callback) {
+                    $callback && $callback($value);
+                }, $seconds);
+            },
 
-            if (is_callable($callbackOrSeconds)) {
-                $callback = $callbackOrSeconds;
-            } elseif (is_int($callbackOrSeconds)) {
-                $seconds = $callbackOrSeconds;
-            }
+            'hasInput' => function ($field) {
+                /* @var \Facebook\WebDriver\Remote\RemoteWebElement $element */
+                $this->resolver->resolveForTyping($field);
 
-            $delayBrowser = $self->makeDelayBrowser($this);
-            $text = Arr::wrap($text);
-            $message = $this->formatTimeOutMessage('Waited %s seconds for text', implode("', '", $text));
+                return $this;
+            },
 
-            $this->waitUsing($seconds, 100, function () use ($text, $callback, $delayBrowser)  {
-                $results = Str::contains($this->resolver->findOrFail('')->getText(), $text);
+            'wait' => function ($seconds, \Closure $callback = null) {
+                try {
+                    $this->waitUsing($seconds, 200, function () {});
+                } catch (TimeoutException $e) {
+                    $callback && $callback();
+                }
 
-                if ($results) {
-                    $callback && $callback($this);
+                return $this;
+            },
 
-                    $delayBrowser();
-                }
+            'assertHidden' => function ($selector) {
+                $fullSelector = $this->resolver->format($selector);
 
-                return $results;
-            }, $message);
+                PHPUnit::assertTrue(
+                    $this->resolver->findOrFail($selector)->isDisplayed(),
+                    "Element [{$fullSelector}] is visible."
+                );
 
-            return $delayBrowser;
-        });
+                return $this;
+            },
+            'assert' => function (Component $component) {
+                return $this->with($component, function () {});
+            },
+        ];
+
+        foreach ($functions as $method => $callback) {
+            Browser::macro($method, $callback);
+        }
     }
 
     public function makeDelayBrowser($browser)