From d9c722c46d5ec41998dbb8f4dc24c037ac5f244c Mon Sep 17 00:00:00 2001 From: Mior Muhammad Zaki Date: Mon, 4 Sep 2023 23:59:51 +0800 Subject: [PATCH] [7.x] Keyboard Support Improvements (#1053) * wip Signed-off-by: Mior Muhammad Zaki * Apply fixes from StyleCI * wip Signed-off-by: Mior Muhammad Zaki * wip Signed-off-by: Mior Muhammad Zaki * Apply fixes from StyleCI * wip Signed-off-by: Mior Muhammad Zaki * wip Signed-off-by: Mior Muhammad Zaki * wip Signed-off-by: Mior Muhammad Zaki * wip Signed-off-by: Mior Muhammad Zaki * wip Signed-off-by: Mior Muhammad Zaki * wip Signed-off-by: Mior Muhammad Zaki * formatting --------- Signed-off-by: Mior Muhammad Zaki Co-authored-by: StyleCI Bot Co-authored-by: Taylor Otwell --- .gitignore | 1 + src/Concerns/InteractsWithElements.php | 25 +----- src/Concerns/InteractsWithKeyboard.php | 42 ++++++++++ src/Concerns/InteractsWithMouse.php | 20 +++++ src/Keyboard.php | 101 +++++++++++++++++++++++++ tests/BrowserTest.php | 19 +++++ 6 files changed, 185 insertions(+), 23 deletions(-) create mode 100644 src/Concerns/InteractsWithKeyboard.php create mode 100644 src/Keyboard.php diff --git a/.gitignore b/.gitignore index 660fc15e4..c1c801e33 100644 --- a/.gitignore +++ b/.gitignore @@ -1,4 +1,5 @@ /vendor composer.lock /phpunit.xml +.phpunit.cache/* .phpunit.result.cache diff --git a/src/Concerns/InteractsWithElements.php b/src/Concerns/InteractsWithElements.php index a50171a38..36ea1e78a 100644 --- a/src/Concerns/InteractsWithElements.php +++ b/src/Concerns/InteractsWithElements.php @@ -5,13 +5,13 @@ use Facebook\WebDriver\Interactions\WebDriverActions; use Facebook\WebDriver\Remote\LocalFileDetector; use Facebook\WebDriver\WebDriverBy; -use Facebook\WebDriver\WebDriverKeys; use Facebook\WebDriver\WebDriverSelect; use Illuminate\Support\Arr; -use Illuminate\Support\Str; trait InteractsWithElements { + use InteractsWithKeyboard; + /** * Get all of the elements matching the given selector. * @@ -113,27 +113,6 @@ public function keys($selector, ...$keys) return $this; } - /** - * Parse the keys before sending to the keyboard. - * - * @param array $keys - * @return array - */ - protected function parseKeys($keys) - { - return collect($keys)->map(function ($key) { - if (is_string($key) && Str::startsWith($key, '{') && Str::endsWith($key, '}')) { - $key = constant(WebDriverKeys::class.'::'.strtoupper(trim($key, '{}'))); - } - - if (is_array($key) && Str::startsWith($key[0], '{')) { - $key[0] = constant(WebDriverKeys::class.'::'.strtoupper(trim($key[0], '{}'))); - } - - return $key; - })->all(); - } - /** * Type the given value in the given field. * diff --git a/src/Concerns/InteractsWithKeyboard.php b/src/Concerns/InteractsWithKeyboard.php new file mode 100644 index 000000000..4fbc6d100 --- /dev/null +++ b/src/Concerns/InteractsWithKeyboard.php @@ -0,0 +1,42 @@ + $callback(new Keyboard($this))); + } + + /** + * Parse the keys before sending to the keyboard. + * + * @param array $keys + * @return array + */ + protected function parseKeys($keys) + { + return collect($keys)->map(function ($key) { + if (is_string($key) && Str::startsWith($key, '{') && Str::endsWith($key, '}')) { + $key = constant(WebDriverKeys::class.'::'.strtoupper(trim($key, '{}'))); + } + + if (is_array($key) && Str::startsWith($key[0], '{')) { + $key[0] = constant(WebDriverKeys::class.'::'.strtoupper(trim($key[0], '{}'))); + } + + return $key; + })->all(); + } +} diff --git a/src/Concerns/InteractsWithMouse.php b/src/Concerns/InteractsWithMouse.php index ea1594878..2e1944633 100644 --- a/src/Concerns/InteractsWithMouse.php +++ b/src/Concerns/InteractsWithMouse.php @@ -6,6 +6,9 @@ use Facebook\WebDriver\Exception\NoSuchElementException; use Facebook\WebDriver\Interactions\WebDriverActions; use Facebook\WebDriver\WebDriverBy; +use Facebook\WebDriver\WebDriverKeys; +use Laravel\Dusk\Keyboard; +use Laravel\Dusk\OperatingSystem; trait InteractsWithMouse { @@ -153,6 +156,23 @@ public function rightClick($selector = null) return $this; } + /** + * Control click the element at the given selector. + * + * @param string|null $selector + * @return $this + */ + public function controlClick($selector = null) + { + return $this->withKeyboard(function (Keyboard $keyboard) use ($selector) { + $key = OperatingSystem::onMac() ? WebDriverKeys::META : WebDriverKeys::CONTROL; + + $keyboard->press($key); + $this->click($selector); + $keyboard->release($key); + }); + } + /** * Release the currently clicked mouse button. * diff --git a/src/Keyboard.php b/src/Keyboard.php new file mode 100644 index 000000000..9a2d8c3bb --- /dev/null +++ b/src/Keyboard.php @@ -0,0 +1,101 @@ +browser = $browser; + } + + /** + * Press the key using keyboard. + * + * @return $this + */ + public function press($key) + { + $this->pressKey($key); + + return $this; + } + + /** + * Release the given pressed key. + * + * @return $this + */ + public function release($key) + { + $this->releaseKey($key); + + return $this; + } + + /** + * Type the given keys using keyboard. + * + * @param string|array $keys + * @return $this + */ + public function type($keys) + { + $this->sendKeys($keys); + + return $this; + } + + /** + * Dynamically call a method on the keyboard. + * + * @param string $method + * @param array $parameters + * @return mixed + * + * @throws \BadMethodCallException + */ + public function __call($method, $parameters) + { + if (static::hasMacro($method)) { + return $this->macroCall($method, $parameters); + } + + $keyboard = $this->browser->driver->getKeyboard(); + + if (method_exists($keyboard, $method)) { + $response = $keyboard->{$method}(...$parameters); + + if ($response === $keyboard) { + return $this; + } else { + return $response; + } + } + + throw new BadMethodCallException("Call to undefined keyboard method [{$method}]."); + } +} diff --git a/tests/BrowserTest.php b/tests/BrowserTest.php index 04c15663f..736165517 100644 --- a/tests/BrowserTest.php +++ b/tests/BrowserTest.php @@ -2,8 +2,11 @@ namespace Laravel\Dusk\Tests; +use Facebook\WebDriver\Remote\RemoteKeyboard; use Facebook\WebDriver\Remote\WebDriverBrowserType; +use Facebook\WebDriver\WebDriverKeys; use Laravel\Dusk\Browser; +use Laravel\Dusk\Keyboard; use Laravel\Dusk\Page; use Mockery as m; use PHPUnit\Framework\TestCase; @@ -192,6 +195,22 @@ public function test_disable_console() $browser->storeConsoleLog('file'); } + public function test_uses_keyboard() + { + $driver = m::mock(stdClass::class); + $remoteKeyboard = m::mock(RemoteKeyboard::class); + $driver->shouldReceive('getKeyboard')->once()->andReturn($remoteKeyboard); + $remoteKeyboard->shouldReceive('pressKey')->once()->with(WebDriverKeys::CONTROL)->andReturnNull(); + $browser = new Browser($driver); + + $browser->withKeyboard(function ($keyboard) use ($browser) { + $this->assertInstanceof(Keyboard::class, $keyboard); + $this->assertSame($browser, $keyboard->browser); + + $keyboard->press(WebDriverKeys::CONTROL); + }); + } + public function test_screenshot() { $this->driver->shouldReceive('takeScreenshot')->andReturnUsing(function ($filePath) {