diff --git a/.github/workflows/tests.yml b/.github/workflows/tests.yml index d28f5cc6c..2093080a9 100644 --- a/.github/workflows/tests.yml +++ b/.github/workflows/tests.yml @@ -9,20 +9,28 @@ on: jobs: tests: runs-on: ubuntu-latest - + strategy: fail-fast: true matrix: php: [7.2, 7.3, 7.4, 8.0] laravel: [^6.0, ^7.0, ^8.0] - phpunit: [^7.5, ^8.4, ^9.0] + phpunit: [^8.4, ^9.0] + include: + - php: 7.2 + laravel: ^6.0 + phpunit: ^7.5 + - php: 7.3 + laravel: ^6.0 + phpunit: ^7.5 + - php: 7.4 + laravel: ^6.0 + phpunit: ^7.5 exclude: - php: 7.2 phpunit: ^9.0 - php: 7.2 laravel: ^8.0 - - php: 8.0 - phpunit: ^7.5 - php: 8.0 phpunit: ^8.4 diff --git a/bin/.gitignore b/bin/.gitignore new file mode 100644 index 000000000..d593978d2 --- /dev/null +++ b/bin/.gitignore @@ -0,0 +1 @@ +chromedriver-* diff --git a/composer.json b/composer.json index 2c138c80a..cefecef22 100644 --- a/composer.json +++ b/composer.json @@ -24,7 +24,8 @@ }, "require-dev": { "mockery/mockery": "^1.0", - "phpunit/phpunit": "^7.5.15|^8.4|^9.0" + "phpunit/phpunit": "^7.5.15|^8.4|^9.0", + "orchestra/testbench": "^4.16|^5.17.1|^6.12.1" }, "suggest": { "ext-pcntl": "Used to gracefully terminate Dusk when tests are running." diff --git a/src/Chrome/ChromeProcess.php b/src/Chrome/ChromeProcess.php index 031cc3b6d..eff172326 100644 --- a/src/Chrome/ChromeProcess.php +++ b/src/Chrome/ChromeProcess.php @@ -44,13 +44,15 @@ public function toProcess(array $arguments = []) return $this->process($arguments); } - if ($this->onWindows()) { - $this->driver = realpath(__DIR__.'/../../bin/chromedriver-win.exe'); - } elseif ($this->onMac()) { - $this->driver = realpath(__DIR__.'/../../bin/chromedriver-mac'); - } else { - $this->driver = realpath(__DIR__.'/../../bin/chromedriver-linux'); - } + $filenames = [ + 'linux' => 'chromedriver-linux', + 'mac' => 'chromedriver-mac', + 'mac-intel' => 'chromedriver-mac-intel', + 'mac-arm' => 'chromedriver-mac-arm', + 'win' => 'chromedriver-win.exe', + ]; + + $this->driver = realpath(__DIR__.'/../../bin').DIRECTORY_SEPARATOR.$filenames[$this->operatingSystemId()]; return $this->process($arguments); } @@ -64,7 +66,7 @@ public function toProcess(array $arguments = []) protected function process(array $arguments = []) { return new Process( - array_merge([realpath($this->driver)], $arguments), null, $this->chromeEnvironment() + array_merge([$this->driver], $arguments), null, $this->chromeEnvironment() ); } @@ -101,4 +103,14 @@ protected function onMac() { return OperatingSystem::onMac(); } + + /** + * Determine OS ID. + * + * @return string + */ + protected function operatingSystemId() + { + return OperatingSystem::id(); + } } diff --git a/src/Console/ChromeDriverCommand.php b/src/Console/ChromeDriverCommand.php index 651d38362..75bd92b99 100644 --- a/src/Console/ChromeDriverCommand.php +++ b/src/Console/ChromeDriverCommand.php @@ -59,6 +59,8 @@ class ChromeDriverCommand extends Command protected $slugs = [ 'linux' => 'linux64', 'mac' => 'mac64', + 'mac-intel' => 'mac64', + 'mac-arm' => 'mac64_m1', 'win' => 'win32', ]; @@ -119,6 +121,12 @@ class ChromeDriverCommand extends Command 'mac' => [ '/Applications/Google\ Chrome.app/Contents/MacOS/Google\ Chrome --version', ], + 'mac-intel' => [ + '/Applications/Google\ Chrome.app/Contents/MacOS/Google\ Chrome --version', + ], + 'mac-arm' => [ + '/Applications/Google\ Chrome.app/Contents/MacOS/Google\ Chrome --version', + ], 'win' => [ 'reg query "HKEY_CURRENT_USER\Software\Google\Chrome\BLBeacon" /v version', ], diff --git a/src/OperatingSystem.php b/src/OperatingSystem.php index d32a99c84..bd6021a85 100644 --- a/src/OperatingSystem.php +++ b/src/OperatingSystem.php @@ -13,7 +13,13 @@ class OperatingSystem */ public static function id() { - return static::onWindows() ? 'win' : (static::onMac() ? 'mac' : 'linux'); + if (static::onWindows()) { + return 'win'; + } elseif (static::onMac()) { + return static::macArchitectureId(); + } + + return 'linux'; } /** @@ -35,4 +41,21 @@ public static function onMac() { return PHP_OS === 'Darwin'; } + + /** + * Mac platform architecture. + * + * @return string + */ + public static function macArchitectureId() + { + switch (php_uname('m')) { + case 'arm64': + return 'mac-arm'; + case 'x86_64': + return 'mac-intel'; + default: + return 'mac'; + } + } } diff --git a/tests/ChromeProcessTest.php b/tests/ChromeProcessTest.php index 7354ecf83..f2310dd98 100644 --- a/tests/ChromeProcessTest.php +++ b/tests/ChromeProcessTest.php @@ -35,6 +35,22 @@ public function test_build_process_for_darwin() $this->assertStringContainsString('chromedriver-mac', $process->getCommandLine()); } + public function test_build_process_for_darwin_intel() + { + $process = (new ChromeProcessDarwinIntel)->toProcess(); + + $this->assertInstanceOf(Process::class, $process); + $this->assertStringContainsString('chromedriver-mac-intel', $process->getCommandLine()); + } + + public function test_build_process_for_darwin_arm() + { + $process = (new ChromeProcessDarwinArm)->toProcess(); + + $this->assertInstanceOf(Process::class, $process); + $this->assertStringContainsString('chromedriver-mac-arm', $process->getCommandLine()); + } + public function test_build_process_for_linux() { $process = (new ChromeProcessLinux)->toProcess(); @@ -53,10 +69,20 @@ public function test_invalid_path() class ChromeProcessWindows extends ChromeProcess { + protected function onMac() + { + return false; + } + protected function onWindows() { return true; } + + protected function operatingSystemId() + { + return 'win'; + } } class ChromeProcessDarwin extends ChromeProcess @@ -70,6 +96,47 @@ protected function onWindows() { return false; } + + protected function operatingSystemId() + { + return 'mac'; + } +} + +class ChromeProcessDarwinIntel extends ChromeProcess +{ + protected function onMac() + { + return true; + } + + protected function onWindows() + { + return false; + } + + protected function operatingSystemId() + { + return 'mac-intel'; + } +} + +class ChromeProcessDarwinArm extends ChromeProcess +{ + protected function onMac() + { + return true; + } + + protected function onWindows() + { + return false; + } + + protected function operatingSystemId() + { + return 'mac-arm'; + } } class ChromeProcessLinux extends ChromeProcess @@ -83,4 +150,9 @@ protected function onWindows() { return false; } + + protected function operatingSystemId() + { + return 'linux'; + } } diff --git a/tests/Concerns/SwapsUrlGenerator.php b/tests/Concerns/SwapsUrlGenerator.php new file mode 100644 index 000000000..455f39380 --- /dev/null +++ b/tests/Concerns/SwapsUrlGenerator.php @@ -0,0 +1,29 @@ +bind('url', function () { + return new class(new RouteCollection(), new Request()) extends UrlGenerator { + public function route($name, $parameters = [], $absolute = true) + { + $route = '/'.$name.'/'.implode('/', $parameters); + + if ($absolute) { + $route = 'http://www.google.com'.$route; + } + + return $route; + } + }; + }); + } +} diff --git a/tests/ElementResolverTest.php b/tests/ElementResolverTest.php index eb261f4a6..0ade5543d 100644 --- a/tests/ElementResolverTest.php +++ b/tests/ElementResolverTest.php @@ -11,6 +11,11 @@ class ElementResolverTest extends TestCase { + protected function tearDown(): void + { + m::close(); + } + /** * @throws \Exception */ @@ -65,10 +70,11 @@ public function test_resolve_for_radio_selection_falls_back_to_selectors_without public function test_resolve_for_radio_selection_throws_exception_without_id_and_without_value() { $driver = m::mock(stdClass::class); - $driver->shouldReceive('findElement')->once()->andReturn('foo'); $resolver = new ElementResolver($driver); + $this->expectException(InvalidArgumentException::class); $this->expectExceptionMessage('No value was provided for radio button [foo].'); + $resolver->resolveForRadioSelection('foo'); } diff --git a/tests/MakesAssertionsTest.php b/tests/MakesAssertionsTest.php index a4cfb1e13..51d07c881 100644 --- a/tests/MakesAssertionsTest.php +++ b/tests/MakesAssertionsTest.php @@ -11,6 +11,11 @@ class MakesAssertionsTest extends TestCase { + protected function tearDown(): void + { + m::close(); + } + public function test_assert_title() { $driver = m::mock(stdClass::class); @@ -875,7 +880,7 @@ public function test_assert_vue() '? JSON.parse(JSON.stringify(el.__vueParentComponent.ctx)).foo'. ': el.__vue__.foo' ) - ->once() + ->twice() ->andReturn('foo'); $resolver = m::mock(stdClass::class); @@ -928,7 +933,7 @@ public function test_assert_vue_is_not() '? JSON.parse(JSON.stringify(el.__vueParentComponent.ctx)).foo'. ': el.__vue__.foo' ) - ->once() + ->twice() ->andReturn('foo'); $resolver = m::mock(stdClass::class); diff --git a/tests/MakesUrlAssertionsTest.php b/tests/MakesUrlAssertionsTest.php index a9838fc07..69ad165c0 100644 --- a/tests/MakesUrlAssertionsTest.php +++ b/tests/MakesUrlAssertionsTest.php @@ -3,6 +3,7 @@ namespace Laravel\Dusk\Tests; use Laravel\Dusk\Browser; +use Laravel\Dusk\Tests\Concerns\SwapsUrlGenerator; use Mockery as m; use PHPUnit\Framework\ExpectationFailedException; use PHPUnit\Framework\TestCase; @@ -10,10 +11,17 @@ class MakesUrlAssertionsTest extends TestCase { + use SwapsUrlGenerator; + + protected function tearDown(): void + { + m::close(); + } + public function test_assert_url_is() { $driver = m::mock(stdClass::class); - $driver->shouldReceive('getCurrentURL')->once()->andReturn( + $driver->shouldReceive('getCurrentURL')->times(8)->andReturn( 'http://www.google.com?foo=bar', 'http://www.google.com:80/test?foo=bar' ); @@ -37,7 +45,7 @@ public function test_assert_url_is() public function test_assert_scheme_is() { $driver = m::mock(stdClass::class); - $driver->shouldReceive('getCurrentURL')->once()->andReturn( + $driver->shouldReceive('getCurrentURL')->times(5)->andReturn( 'http://www.google.com?foo=bar', 'https://www.google.com:80/test?foo=bar', 'ftp://www.google.com', @@ -89,7 +97,7 @@ public function test_assert_scheme_is_not() public function test_assert_host_is() { $driver = m::mock(stdClass::class); - $driver->shouldReceive('getCurrentURL')->once()->andReturn( + $driver->shouldReceive('getCurrentURL')->times(4)->andReturn( 'http://www.google.com?foo=bar', 'http://google.com?foo=bar', 'https://www.laravel.com:80/test?foo=bar', @@ -141,7 +149,7 @@ public function test_assert_host_is_not() public function test_assert_port_is() { $driver = m::mock(stdClass::class); - $driver->shouldReceive('getCurrentURL')->once()->andReturn( + $driver->shouldReceive('getCurrentURL')->times(4)->andReturn( 'http://www.laravel.com:80/test?foo=bar', 'https://www.laravel.com:443/test?foo=bar', 'https://www.laravel.com', @@ -262,7 +270,7 @@ public function test_assert_path_is_not() public function test_assert_route_is() { - require_once __DIR__.'/stubs/route.php'; + $this->swapUrlGenerator(); $driver = m::mock(stdClass::class); $driver->shouldReceive('getCurrentURL')->andReturn( diff --git a/tests/ProvidesBrowserTest.php b/tests/ProvidesBrowserTest.php index 74ca35883..ef3be07d1 100644 --- a/tests/ProvidesBrowserTest.php +++ b/tests/ProvidesBrowserTest.php @@ -11,6 +11,11 @@ class ProvidesBrowserTest extends TestCase { use ProvidesBrowser; + protected function tearDown(): void + { + m::close(); + } + /** * @dataProvider testData */ diff --git a/tests/SupportsChromeTest.php b/tests/SupportsChromeTest.php index eeb361f4a..a3c9365d0 100644 --- a/tests/SupportsChromeTest.php +++ b/tests/SupportsChromeTest.php @@ -3,12 +3,20 @@ namespace Laravel\Dusk\Tests; use Laravel\Dusk\Chrome\SupportsChrome; -use PHPUnit\Framework\TestCase; +use Laravel\Dusk\DuskServiceProvider; +use Orchestra\Testbench\TestCase; class SupportsChromeTest extends TestCase { use SupportsChrome; + protected function setUp(): void + { + parent::setUp(); + + $this->artisan('dusk:chrome-driver'); + } + public function test_it_can_run_chrome_process() { $process = static::buildChromeProcess(); @@ -23,4 +31,9 @@ public function test_it_can_run_chrome_process() $this->assertStringContainsString('Starting ChromeDriver', $process->getOutput()); $this->assertSame('', $process->getErrorOutput()); } + + public function getPackageProviders($app) + { + return [DuskServiceProvider::class]; + } } diff --git a/tests/WaitsForElementsTest.php b/tests/WaitsForElementsTest.php index 00f0b2756..dfa3c5028 100644 --- a/tests/WaitsForElementsTest.php +++ b/tests/WaitsForElementsTest.php @@ -4,12 +4,20 @@ use Facebook\WebDriver\Exception\TimeOutException; use Laravel\Dusk\Browser; +use Laravel\Dusk\Tests\Concerns\SwapsUrlGenerator; use Mockery as m; use PHPUnit\Framework\TestCase; use stdClass; class WaitsForElementsTest extends TestCase { + use SwapsUrlGenerator; + + protected function tearDown(): void + { + m::close(); + } + public function test_default_wait_time() { Browser::$waitSeconds = 2; @@ -77,6 +85,8 @@ public function test_can_wait_for_location() public function test_can_wait_for_route() { + $this->swapUrlGenerator(); + $driver = m::mock(stdClass::class); $driver->shouldReceive('executeScript')->with("return window.location.pathname == '/home/';")->andReturnTrue(); $browser = new Browser($driver); diff --git a/tests/stubs/route.php b/tests/stubs/route.php deleted file mode 100644 index 8ea305e99..000000000 --- a/tests/stubs/route.php +++ /dev/null @@ -1,12 +0,0 @@ -