Laravel version update

Laravel version update
This commit is contained in:
Manish Verma
2018-08-06 18:48:58 +05:30
parent d143048413
commit 126fbb0255
13678 changed files with 1031482 additions and 778530 deletions

386
vendor/laravel/dusk/src/Browser.php vendored Normal file
View File

@@ -0,0 +1,386 @@
<?php
namespace Laravel\Dusk;
use Closure;
use BadMethodCallException;
use Illuminate\Support\Str;
use Illuminate\Support\Traits\Macroable;
use Facebook\WebDriver\WebDriverDimension;
class Browser
{
use Concerns\InteractsWithAuthentication,
Concerns\InteractsWithCookies,
Concerns\InteractsWithElements,
Concerns\InteractsWithJavascript,
Concerns\InteractsWithMouse,
Concerns\MakesAssertions,
Concerns\WaitsForElements,
Macroable {
__call as macroCall;
}
/**
* The base URL for all URLs.
*
* @var string
*/
public static $baseUrl;
/**
* The directory that will contain any screenshots.
*
* @var string
*/
public static $storeScreenshotsAt;
/**
* The directory that will contain any console logs.
*
* @var string
*/
public static $storeConsoleLogAt;
/**
* Get the callback which resolves the default user to authenticate.
*
* @var \Closure
*/
public static $userResolver;
/**
* The RemoteWebDriver instance.
*
* @var \Facebook\WebDriver\Remote\RemoteWebDriver
*/
public $driver;
/**
* The element resolver instance.
*
* @var ElementResolver
*/
public $resolver;
/**
* The page object currently being viewed.
*
* @var mixed
*/
public $page;
/**
* Create a browser instance.
*
* @param \Facebook\WebDriver\Remote\RemoteWebDriver $driver
* @param ElementResolver $resolver
* @return void
*/
public function __construct($driver, $resolver = null)
{
$this->driver = $driver;
$this->resolver = $resolver ?: new ElementResolver($driver);
}
/**
* Browse to the given URL.
*
* @param string $url
* @return $this
*/
public function visit($url)
{
// First, if the URL is an object it means we are actually dealing with a page
// and we need to create this page then get the URL from the page object as
// it contains the URL. Once that is done, we will be ready to format it.
if (is_object($url)) {
$page = $url;
$url = $page->url();
}
// If the URL does not start with http or https, then we will prepend the base
// URL onto the URL and navigate to the URL. This will actually navigate to
// the URL in the browser. Then we will be ready to make assertions, etc.
if (! Str::startsWith($url, ['http://', 'https://'])) {
$url = static::$baseUrl.'/'.ltrim($url, '/');
}
$this->driver->navigate()->to($url);
// If the page variable was set, we will call the "on" method which will set a
// page instance variable and call an assert method on the page so that the
// page can have the chance to verify that we are within the right pages.
if (isset($page)) {
$this->on($page);
}
return $this;
}
/**
* Browse to the given route.
*
* @param string $route
* @param array $parameters
* @return $this
*/
public function visitRoute($route, $parameters = [])
{
return $this->visit(route($route, $parameters));
}
/**
* Set the current page object.
*
* @param mixed $page
* @return $this
*/
public function on($page)
{
$this->page = $page;
// Here we will set the page elements on the resolver instance, which will allow
// the developer to access short-cuts for CSS selectors on the page which can
// allow for more expressive navigation and interaction with all the pages.
$this->resolver->pageElements(array_merge(
$page::siteElements(), $page->elements()
));
$page->assert($this);
return $this;
}
/**
* Refresh the page.
*
* @return $this
*/
public function refresh()
{
$this->driver->navigate()->refresh();
return $this;
}
/**
* Navigate to the previous page.
*
* @return $this
*/
public function back()
{
$this->driver->navigate()->back();
return $this;
}
/**
* Maximize the browser window.
*
* @return $this
*/
public function maximize()
{
$this->driver->manage()->window()->maximize();
return $this;
}
/**
* Resize the browser window.
*
* @param int $width
* @param int $height
* @return $this
*/
public function resize($width, $height)
{
$this->driver->manage()->window()->setSize(
new WebDriverDimension($width, $height)
);
return $this;
}
/**
* Take a screenshot and store it with the given name.
*
* @param string $name
* @return $this
*/
public function screenshot($name)
{
$this->driver->takeScreenshot(
sprintf('%s/%s.png', rtrim(static::$storeScreenshotsAt, '/'), $name)
);
return $this;
}
/**
* Store the console output with the given name.
*
* @param string $name
* @return $this
*/
public function storeConsoleLog($name)
{
$console = $this->driver->manage()->getLog('browser');
if (! empty($console)) {
file_put_contents(
sprintf('%s/%s.log', rtrim(static::$storeConsoleLogAt, '/'), $name)
, json_encode($console, JSON_PRETTY_PRINT)
);
}
return $this;
}
/**
* Execute a Closure with a scoped browser instance.
*
* @param string $selector
* @param \Closure $callback
* @return $this
*/
public function within($selector, Closure $callback)
{
return $this->with($selector, $callback);
}
/**
* Execute a Closure with a scoped browser instance.
*
* @param string $selector
* @param \Closure $callback
* @return $this
*/
public function with($selector, Closure $callback)
{
$browser = new static(
$this->driver, new ElementResolver($this->driver, $this->resolver->format($selector))
);
if ($this->page) {
$browser->on($this->page);
}
call_user_func($callback, $browser);
return $this;
}
/**
* Ensure that jQuery is available on the page.
*
* @return void
*/
public function ensurejQueryIsAvailable()
{
if ($this->driver->executeScript("return window.jQuery == null")) {
$this->driver->executeScript(file_get_contents(__DIR__.'/../bin/jquery.js'));
}
}
/**
* Pause for the given amount of milliseconds.
*
* @param int $milliseconds
* @return $this
*/
public function pause($milliseconds)
{
usleep($milliseconds * 1000);
return $this;
}
/**
* Close the browser.
*
* @return void
*/
public function quit()
{
$this->driver->quit();
}
/**
* Tap the browser into a callback.
*
* @param \Closure $callback
* @return $this
*/
public function tap($callback)
{
$callback($this);
return $this;
}
/**
* Dump the content from the last response.
*
* @return void
*/
public function dump()
{
dd($this->driver->getPageSource());
}
/**
* Pause execution of test and open Laravel Tinker (PsySH) REPL.
*
* @return $this
*/
public function tinker()
{
\Psy\Shell::debug([
'browser' => $this,
'driver' => $this->driver,
'resolver' => $this->resolver,
'page' => $this->page,
], $this);
return $this;
}
/**
* Stop running tests but leave the browser open.
*
* @return void
*/
public function stop()
{
exit();
}
/**
* Dynamically call a method on the browser.
*
* @param string $method
* @param array $parameters
* @return mixed
*/
public function __call($method, $parameters)
{
if (static::hasMacro($method)) {
return $this->macroCall($method, $parameters);
}
if ($this->page && method_exists($this->page, $method)) {
array_unshift($parameters, $this);
$this->page->{$method}(...$parameters);
return $this;
}
throw new BadMethodCallException("Call to undefined method [{$method}].");
}
}

View File

@@ -0,0 +1,107 @@
<?php
namespace Laravel\Dusk\Concerns;
use Laravel\Dusk\Browser;
use PHPUnit\Framework\Assert as PHPUnit;
trait InteractsWithAuthentication
{
/**
* Log into the application as the default user.
*
* @return $this
*/
public function login()
{
return $this->loginAs(call_user_func(Browser::$userResolver));
}
/**
* Log into the application using a given user ID or email.
*
* @param object|string $userId
* @param string $guard
* @return $this
*/
public function loginAs($userId, $guard = null)
{
$userId = method_exists($userId, 'getKey') ? $userId->getKey() : $userId;
return $this->visit(rtrim('/_dusk/login/'.$userId.'/'.$guard, '/'));
}
/**
* Log out of the application.
*
* @param string $guard
* @return $this
*/
public function logout($guard = null)
{
return $this->visit(rtrim('/_dusk/logout/'.$guard, '/'));
}
/**
* Get the ID and the class name of the authenticated user.
*
* @param string|null $guard
* @return array
*/
protected function currentUserInfo($guard = null)
{
$response = $this->visit("/_dusk/user/{$guard}");
return json_decode(strip_tags($response->driver->getPageSource()), true);
}
/**
* Assert that the user is authenticated.
*
* @param string|null $guard
* @return $this
*/
public function assertAuthenticated($guard = null)
{
PHPUnit::assertNotEmpty($this->currentUserInfo($guard), 'The user is not authenticated.');
return $this;
}
/**
* Assert that the user is not authenticated.
*
* @param string|null $guard
* @return $this
*/
public function assertGuest($guard = null)
{
PHPUnit::assertEmpty(
$this->currentUserInfo($guard), 'The user is unexpectedly authenticated.'
);
return $this;
}
/**
* Assert that the user is authenticated as the given user.
*
* @param $user
* @param string|null $guard
* @return $this
*/
public function assertAuthenticatedAs($user, $guard = null)
{
$expected = [
'id' => $user->getAuthIdentifier(),
'className' => get_class($user),
];
PHPUnit::assertSame(
$expected, $this->currentUserInfo($guard),
'The currently authenticated user is not who was expected.'
);
return $this;
}
}

View File

@@ -0,0 +1,88 @@
<?php
namespace Laravel\Dusk\Concerns;
use DateTimeInterface;
trait InteractsWithCookies
{
/**
* Get or set an encrypted cookie's value.
*
* @param string $name
* @param string|null $value
* @param int|DateTimeInterface|null $expiry
* @param array $options
* @return string
*/
public function cookie($name, $value = null, $expiry = null, array $options = [])
{
if ($value) {
return $this->addCookie($name, $value, $expiry, $options);
}
if ($cookie = $this->driver->manage()->getCookieNamed($name)) {
return decrypt(rawurldecode($cookie['value']));
}
}
/**
* Get or set a plain cookie's value.
*
* @param string $name
* @param string|null $value
* @param int|DateTimeInterface|null $expiry
* @param array $options
* @return string
*/
public function plainCookie($name, $value = null, $expiry = null, array $options = [])
{
if ($value) {
return $this->addCookie($name, $value, $expiry, $options, false);
}
if ($cookie = $this->driver->manage()->getCookieNamed($name)) {
return rawurldecode($cookie['value']);
}
}
/**
* Add the given cookie.
*
* @param string $name
* @param string $value
* @param int|DateTimeInterface|null $expiry
* @param array $options
* @param bool $encrypt
* @return $this
*/
public function addCookie($name, $value, $expiry = null, array $options = [], $encrypt = true)
{
if ($encrypt) {
$value = encrypt($value);
}
if ($expiry instanceof DateTimeInterface) {
$expiry = $expiry->getTimestamp();
}
$this->driver->manage()->addCookie(
array_merge($options, compact('expiry', 'name', 'value'))
);
return $this;
}
/**
* Delete the given cookie.
*
* @param string $name
* @return $this
*/
public function deleteCookie($name)
{
$this->driver->manage()->deleteCookieNamed($name);
return $this;
}
}

View File

@@ -0,0 +1,432 @@
<?php
namespace Laravel\Dusk\Concerns;
use Illuminate\Support\Str;
use Facebook\WebDriver\WebDriverBy;
use Facebook\WebDriver\WebDriverKeys;
use Facebook\WebDriver\Remote\LocalFileDetector;
use Facebook\WebDriver\Interactions\WebDriverActions;
trait InteractsWithElements
{
/**
* Get all of the elements matching the given selector.
*
* @param string $selector
* @return array
*/
public function elements($selector)
{
return $this->resolver->all($selector);
}
/**
* Get the element matching the given selector.
*
* @param string $selector
* @return \Facebook\WebDriver\Remote\RemoteWebElement|null
*/
public function element($selector)
{
return $this->resolver->find($selector);
}
/**
* Click the element at the given selector.
*
* @param string $selector
* @return $this
*/
public function click($selector)
{
$this->resolver->findOrFail($selector)->click();
return $this;
}
/**
* Right click the element at the given selector.
*
* @param string $selector
* @return $this
*/
public function rightClick($selector)
{
(new WebDriverActions($this->driver))->contextClick(
$this->resolver->findOrFail($selector)
)->perform();
return $this;
}
/**
* Click the link with the given text.
*
* @param string $link
* @return $this
*/
public function clickLink($link)
{
$this->ensurejQueryIsAvailable();
$selector = addslashes(trim($this->resolver->format("a:contains({$link})")));
$this->driver->executeScript("jQuery.find(\"{$selector}\")[0].click();");
return $this;
}
/**
* Directly get or set the value attribute of an input field.
*
* @param string $selector
* @param string|null $value
* @return $this
*/
public function value($selector, $value = null)
{
if (is_null($value)) {
return $this->resolver->findOrFail($selector)->getAttribute('value');
}
$selector = $this->resolver->format($selector);
$this->driver->executeScript(
"document.querySelector('{$selector}').value = '{$value}';"
);
return $this;
}
/**
* Get the text of the element matching the given selector.
*
* @param string $selector
* @return string
*/
public function text($selector)
{
return $this->resolver->findOrFail($selector)->getText();
}
/**
* Get the given attribute from the element matching the given selector.
*
* @param string $selector
* @param string $attribute
* @return string
*/
public function attribute($selector, $attribute)
{
return $this->resolver->findOrFail($selector)->getAttribute($attribute);
}
/**
* Send the given keys to the element matching the given selector.
*
* @param string $selector
* @param dynamic $keys
* @return $this
*/
public function keys($selector, ...$keys)
{
$this->resolver->findOrFail($selector)->sendKeys($this->parseKeys($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.
*
* @param string $field
* @param string $value
* @return $this
*/
public function type($field, $value)
{
$this->resolver->resolveForTyping($field)->clear()->sendKeys($value);
return $this;
}
/**
* Type the given value in the given field without clearing it.
*
* @param string $field
* @param string $value
* @return $this
*/
public function append($field, $value)
{
$this->resolver->resolveForTyping($field)->sendKeys($value);
return $this;
}
/**
* Clear the given field.
*
* @param string $field
* @return $this
*/
public function clear($field)
{
$this->resolver->resolveForTyping($field)->clear();
return $this;
}
/**
* Select the given value or random value of a drop-down field.
*
* @param string $field
* @param string $value
* @return $this
*/
public function select($field, $value = null)
{
$element = $this->resolver->resolveForSelection($field);
$options = $element->findElements(WebDriverBy::tagName('option'));
if (is_null($value)) {
$options[array_rand($options)]->click();
}
else {
foreach ($options as $option) {
if ((string) $option->getAttribute('value') === (string) $value) {
$option->click();
break;
}
}
}
return $this;
}
/**
* Select the given value of a radio button field.
*
* @param string $field
* @param string $value
* @return $this
*/
public function radio($field, $value)
{
$this->resolver->resolveForRadioSelection($field, $value)->click();
return $this;
}
/**
* Check the given checkbox.
*
* @param string $field
* @param string $value
* @return $this
*/
public function check($field, $value = null)
{
$element = $this->resolver->resolveForChecking($field, $value);
if (! $element->isSelected()) {
$element->click();
}
return $this;
}
/**
* Uncheck the given checkbox.
*
* @param string $field
* @param string $value
* @return $this
*/
public function uncheck($field, $value = null)
{
$element = $this->resolver->resolveForChecking($field, $value);
if ($element->isSelected()) {
$element->click();
}
return $this;
}
/**
* Attach the given file to the field.
*
* @param string $field
* @param string $path
* @return $this
*/
public function attach($field, $path)
{
$element = $this->resolver->resolveForAttachment($field);
$element->setFileDetector(new LocalFileDetector)->sendKeys($path);
return $this;
}
/**
* Press the button with the given text or name.
*
* @param string $button
* @return $this
*/
public function press($button)
{
$this->resolver->resolveForButtonPress($button)->click();
return $this;
}
/**
* Press the button with the given text or name.
*
* @param string $button
* @param int $seconds
* @return $this
*/
public function pressAndWaitFor($button, $seconds = 5)
{
$element = $this->resolver->resolveForButtonPress($button);
$element->click();
return $this->waitUsing($seconds, 100, function () use ($element) {
return $element->isEnabled();
});
}
/**
* Drag an element to another element using selectors.
*
* @param string $from
* @param string $to
* @return $this
*/
public function drag($from, $to)
{
(new WebDriverActions($this->driver))->dragAndDrop(
$this->resolver->findOrFail($from), $this->resolver->findOrFail($to)
)->perform();
return $this;
}
/**
* Drag an element up.
*
* @param string $selector
* @param int $offset
* @return $this
*/
public function dragUp($selector, $offset)
{
return $this->dragOffset($selector, 0, -$offset);
}
/**
* Drag an element down.
*
* @param string $selector
* @param int $offset
* @return $this
*/
public function dragDown($selector, $offset)
{
return $this->dragOffset($selector, 0, $offset);
}
/**
* Drag an element to the left.
*
* @param string $selector
* @param int $offset
* @return $this
*/
public function dragLeft($selector, $offset)
{
return $this->dragOffset($selector, -$offset, 0);
}
/**
* Drag an element to the right.
*
* @param string $selector
* @param int $offset
* @return $this
*/
public function dragRight($selector, $offset)
{
return $this->dragOffset($selector, $offset, 0);
}
/**
* Drag an element by the given offset.
*
* @param string $selector
* @param int $x
* @param int $y
* @return $this
*/
public function dragOffset($selector, $x = 0, $y = 0)
{
(new WebDriverActions($this->driver))->dragAndDropBy(
$this->resolver->findOrFail($selector), $x, $y
)->perform();
return $this;
}
/**
* Accept a JavaScript dialog.
*
* @return $this
*/
public function acceptDialog()
{
$this->driver->switchTo()->alert()->accept();
return $this;
}
/**
* Dismiss a JavaScript dialog.
*
* @return $this
*/
public function dismissDialog()
{
$this->driver->switchTo()->alert()->dismiss();
return $this;
}
}

View File

@@ -0,0 +1,19 @@
<?php
namespace Laravel\Dusk\Concerns;
trait InteractsWithJavascript
{
/**
* Execute JavaScript within the browser.
*
* @param string|array $scripts
* @return array
*/
public function script($scripts)
{
return collect((array) $scripts)->map(function ($script) {
return $this->driver->executeScript($script);
})->all();
}
}

View File

@@ -0,0 +1,21 @@
<?php
namespace Laravel\Dusk\Concerns;
trait InteractsWithMouse
{
/**
* Move the mouse over the given selector.
*
* @param string $selector
* @return $this
*/
public function mouseover($selector)
{
$element = $this->resolver->findOrFail($selector);
$this->driver->getMouse()->mouseMove($element->getCoordinates());
return $this;
}
}

View File

@@ -0,0 +1,676 @@
<?php
namespace Laravel\Dusk\Concerns;
use Illuminate\Support\Str;
use PHPUnit\Framework\Assert as PHPUnit;
use Facebook\WebDriver\Exception\NoSuchElementException;
trait MakesAssertions
{
/**
* Assert that the page title is the given value.
*
* @param string $title
* @return $this
*/
public function assertTitle($title)
{
PHPUnit::assertEquals($title, $this->driver->getTitle());
return $this;
}
/**
* Assert that the page title contains the given value.
*
* @param string $title
* @return $this
*/
public function assertTitleContains($title)
{
PHPUnit::assertTrue(
Str::contains($this->driver->getTitle(), $title)
);
return $this;
}
/**
* Assert that the current URL path matches the given pattern.
*
* @param string $path
* @return $this
*/
public function assertPathIs($path)
{
$pattern = preg_quote($path, '/');
$pattern = str_replace('\*', '.*', $pattern);
PHPUnit::assertRegExp('/^'.$pattern.'/u', parse_url(
$this->driver->getCurrentURL()
)['path']);
return $this;
}
/**
* Assert that the current URL path begins with given path.
*
* @param string $path
* @return $this
*/
public function assertPathBeginsWith($path)
{
PHPUnit::assertStringStartsWith($path, parse_url(
$this->driver->getCurrentURL()
)['path']);
return $this;
}
/**
* Assert that the current URL path does not match the given path.
*
* @param string $path
* @return $this
*/
public function assertPathIsNot($path)
{
PHPUnit::assertNotEquals($path, parse_url(
$this->driver->getCurrentURL()
)['path']);
return $this;
}
/**
* Assert that the current URL path matches the given route.
*
* @param string $route
* @param array $parameters
* @return $this
*/
public function assertRouteIs($route, $parameters = [])
{
return $this->assertPathIs(route($route, $parameters, false));
}
/**
* Assert that a query string parameter is present and has a given value.
*
* @param string $name
* @param string $value
* @return $this
*/
public function assertQueryStringHas($name, $value = null)
{
$output = $this->assertHasQueryStringParameter($name);
if (is_null($value)) {
return $this;
}
PHPUnit::assertEquals(
$value, $output[$name],
"Query string parameter [{$name}] had value [{$output[$name]}], but expected [{$value}]."
);
return $this;
}
/**
* Assert that the given query string parameter is missing.
*
* @param string $name
* @return $this
*/
public function assertQueryStringMissing($name)
{
$parsedUrl = parse_url($this->driver->getCurrentURL());
if (! array_key_exists('query', $parsedUrl)) {
PHPUnit::assertTrue(true);
return $this;
}
parse_str($parsedUrl['query'], $output);
PHPUnit::assertArrayNotHasKey(
$name, $output,
"Found unexpected query string parameter [{$name}] in [".$this->driver->getCurrentURL()."]."
);
return $this;
}
/**
* Assert that the given query string parameter is present.
*
* @param string $name
* @return $this
*/
protected function assertHasQueryStringParameter($name)
{
$parsedUrl = parse_url($this->driver->getCurrentURL());
PHPUnit::assertArrayHasKey(
'query', $parsedUrl,
"Did not see expected query string in [".$this->driver->getCurrentURL()."]."
);
parse_str($parsedUrl['query'], $output);
PHPUnit::assertArrayHasKey(
$name, $output,
"Did not see expected query string parameter [{$name}] in [".$this->driver->getCurrentURL()."]."
);
return $output;
}
/**
* Assert that the given cookie is present.
*
* @param string $name
* @return $this
*/
public function assertHasCookie($name)
{
PHPUnit::assertTrue(
! is_null($this->cookie($name)),
"Did not find expected cookie [{$name}]."
);
return $this;
}
/**
* Assert that an encrypted cookie has a given value.
*
* @param string $name
* @param string $value
* @param bool $decrypt
* @return $this
*/
public function assertCookieValue($name, $value, $decrypt = true)
{
$actual = $decrypt ? $this->cookie($name) : $this->plainCookie($name);
PHPUnit::assertEquals(
$value, $actual,
"Cookie [{$name}] had value [{$actual}], but expected [{$value}]."
);
return $this;
}
/**
* Assert that a cookie has a given value.
*
* @param string $name
* @param string $value
* @return $this
*/
public function assertPlainCookieValue($name, $value)
{
return $this->assertCookieValue($name, $value, false);
}
/**
* Assert that the given text appears on the page.
*
* @param string $text
* @return $this
*/
public function assertSee($text)
{
return $this->assertSeeIn('', $text);
}
/**
* Assert that the given text does not appear on the page.
*
* @param string $text
* @return $this
*/
public function assertDontSee($text)
{
return $this->assertDontSeeIn('', $text);
}
/**
* Assert that the given text appears within the given selector.
*
* @param string $selector
* @param string $text
* @return $this
*/
public function assertSeeIn($selector, $text)
{
$fullSelector = $this->resolver->format($selector);
$element = $this->resolver->findOrFail($selector);
PHPUnit::assertTrue(
Str::contains($element->getText(), $text),
"Did not see expected text [{$text}] within element [{$fullSelector}]."
);
return $this;
}
/**
* Assert that the given text does not appear within the given selector.
*
* @param string $selector
* @param string $text
* @return $this
*/
public function assertDontSeeIn($selector, $text)
{
$fullSelector = $this->resolver->format($selector);
$element = $this->resolver->findOrFail($selector);
PHPUnit::assertFalse(
Str::contains($element->getText(), $text),
"Saw unexpected text [{$text}] within element [{$fullSelector}]."
);
return $this;
}
/**
* Assert that the given source code is present on the page.
*
* @param string $code
* @return $this
*/
public function assertSourceHas($code)
{
PHPUnit::assertContains(
$code, $this->driver->getPageSource(),
"Did not find expected source code [{$code}]"
);
return $this;
}
/**
* Assert that the given source code is not present on the page.
*
* @param string $code
* @return $this
*/
public function assertSourceMissing($code)
{
PHPUnit::assertNotContains(
$code, $this->driver->getPageSource(),
"Found unexpected source code [{$code}]"
);
return $this;
}
/**
* Assert that the given link is visible.
*
* @param string $link
* @return $this
*/
public function assertSeeLink($link)
{
if ($this->resolver->prefix) {
$message = "Did not see expected link [{$link}] within [{$this->resolver->prefix}].";
} else {
$message = "Did not see expected link [{$link}].";
}
PHPUnit::assertTrue(
$this->seeLink($link),
$message
);
return $this;
}
/**
* Assert that the given link is not visible.
*
* @param string $link
* @return $this
*/
public function assertDontSeeLink($link)
{
if ($this->resolver->prefix) {
$message = "Saw unexpected link [{$link}] within [{$this->resolver->prefix}].";
} else {
$message = "Saw unexpected expected link [{$link}].";
}
PHPUnit::assertFalse(
$this->seeLink($link),
$message
);
return $this;
}
/**
* Determine if the given link is visible.
*
* @param string $link
* @return bool
*/
public function seeLink($link)
{
$this->ensurejQueryIsAvailable();
$selector = addslashes(trim($this->resolver->format("a:contains('{$link}')")));
$script = <<<JS
var link = jQuery.find("{$selector}");
return link.length > 0 && jQuery(link).is(':visible');
JS;
return $this->driver->executeScript($script);
}
/**
* Assert that the given input or text area contains the given value.
*
* @param string $field
* @param string $value
* @return $this
*/
public function assertInputValue($field, $value)
{
PHPUnit::assertEquals($value, $this->inputValue($field));
return $this;
}
/**
* Assert that the given input or text area does not contain the given value.
*
* @param string $field
* @param string $value
* @return $this
*/
public function assertInputValueIsNot($field, $value)
{
PHPUnit::assertNotEquals($value, $this->inputValue($field));
return $this;
}
/**
* Get the value of the given input or text area field.
*
* @param string $field
* @return string
*/
public function inputValue($field)
{
$element = $this->resolver->resolveForTyping($field);
return in_array($element->getTagName(), ['input', 'textarea'])
? $element->getAttribute('value')
: $element->getText();
}
/**
* Assert that the given checkbox field is checked.
*
* @param string $field
* @param string $value
* @return $this
*/
public function assertChecked($field, $value = null)
{
$element = $this->resolver->resolveForChecking($field, $value);
PHPUnit::assertTrue(
$element->isSelected(),
"Expected checkbox [{$field}] to be checked, but it wasn't."
);
return $this;
}
/**
* Assert that the given checkbox field is not checked.
*
* @param string $field
* @param string $value
* @return $this
*/
public function assertNotChecked($field, $value = null)
{
$element = $this->resolver->resolveForChecking($field, $value);
PHPUnit::assertFalse(
$element->isSelected(),
"Checkbox [{$field}] was unexpectedly checked."
);
return $this;
}
/**
* Assert that the given radio field is selected.
*
* @param string $field
* @param string $value
* @return $this
*/
function assertRadioSelected($field, $value)
{
$element = $this->resolver->resolveForRadioSelection($field, $value);
PHPUnit::assertTrue(
$element->isSelected(),
"Expected radio [{$field}] to be selected, but it wasn't."
);
return $this;
}
/**
* Assert that the given radio field is not selected.
*
* @param string $field
* @param string $value
* @return $this
*/
public function assertRadioNotSelected($field, $value = null)
{
$element = $this->resolver->resolveForRadioSelection($field, $value);
PHPUnit::assertFalse(
$element->isSelected(),
"Radio [{$field}] was unexpectedly selected."
);
return $this;
}
/**
* Assert that the given select field has the given value selected.
*
* @param string $field
* @param string $value
* @return $this
*/
public function assertSelected($field, $value)
{
PHPUnit::assertTrue(
$this->selected($field, $value),
"Expected value [{$value}] to be selected for [{$field}], but it wasn't."
);
return $this;
}
/**
* Assert that the given select field does not have the given value selected.
*
* @param string $field
* @param string $value
* @return $this
*/
public function assertNotSelected($field, $value)
{
PHPUnit::assertFalse(
$this->selected($field, $value),
"Unexpected value [{$value}] selected for [{$field}]."
);
return $this;
}
/**
* Assert that the given array of values are available to be selected.
*
* @param string $field
* @param array $values
* @return $this
*/
public function assertSelectHasOptions($field, array $values)
{
PHPUnit::assertCount(
count($values),
$this->resolver->resolveSelectOptions($field, $values),
"Expected options [".implode(',', $values)."] for selection field [{$field}] to be available."
);
return $this;
}
/**
* Assert that the given array of values are not available to be selected.
*
* @param string $field
* @param array $values
* @return $this
*/
public function assertSelectMissingOptions($field, array $values)
{
PHPUnit::assertCount(
0, $this->resolver->resolveSelectOptions($field, $values),
"Unexpected options [".implode(',', $values)."] for selection field [{$field}]."
);
return $this;
}
/**
* Assert that the given value is available to be selected on the given field.
*
* @param string $field
* @param string $value
* @return $this
*/
public function assertSelectHasOption($field, $value)
{
return $this->assertSelectHasOptions($field, [$value]);
}
/**
* Assert that the given value is not available to be selected on the given field.
*
* @param string $field
* @param string $value
* @return $this
*/
public function assertSelectMissingOption($field, $value)
{
return $this->assertSelectMissingOptions($field, [$value]);
}
/**
* Determine if the given value is selected for the given select field.
*
* @param string $field
* @param string $value
* @return bool
*/
public function selected($field, $value)
{
$element = $this->resolver->resolveForSelection($field);
return (string) $element->getAttribute('value') === (string) $value;
}
/**
* Assert that the element at the given selector has the given value.
*
* @param string $selector
* @param string $value
* @return $this
*/
public function assertValue($selector, $value)
{
$actual = $this->resolver->findOrFail($selector)->getAttribute('value');
PHPUnit::assertEquals($value, $actual);
return $this;
}
/**
* Assert that the element with the given selector is visible.
*
* @param string $selector
* @return $this
*/
public function assertVisible($selector)
{
$fullSelector = $this->resolver->format($selector);
PHPUnit::assertTrue(
$this->resolver->findOrFail($selector)->isDisplayed(),
"Element [{$fullSelector}] is not visible."
);
return $this;
}
/**
* Assert that the element with the given selector is not on the page.
*
* @param string $selector
* @return $this
*/
public function assertMissing($selector)
{
$fullSelector = $this->resolver->format($selector);
try {
$missing = ! $this->resolver->findOrFail($selector)->isDisplayed();
} catch (NoSuchElementException $e) {
$missing = true;
}
PHPUnit::assertTrue($missing, "Saw unexpected element [{$fullSelector}].");
return $this;
}
/**
* Assert that a JavaScript dialog with given message has been opened.
*
* @param string $message
* @return $this
*/
public function assertDialogOpened($message)
{
PHPUnit::assertEquals(
$message, $this->driver->switchTo()->alert()->getText()
);
return $this;
}
}

View File

@@ -0,0 +1,179 @@
<?php
namespace Laravel\Dusk\Concerns;
use Closure;
use Exception;
use Carbon\Carbon;
use Illuminate\Support\Str;
use Facebook\WebDriver\Exception\TimeOutException;
use Facebook\WebDriver\Exception\NoSuchElementException;
trait WaitsForElements
{
/**
* Execute the given callback in a scoped browser once the selector is available.
*
* @param string $selector
* @param Closure $callback
* @param int $seconds
* @return $this
*/
public function whenAvailable($selector, Closure $callback, $seconds = 5)
{
return $this->waitFor($selector, $seconds)->with($selector, $callback);
}
/**
* Wait for the given selector to be visible.
*
* @param string $selector
* @param int $seconds
* @return $this
*/
public function waitFor($selector, $seconds = 5)
{
return $this->waitUsing($seconds, 100, function () use ($selector) {
return $this->resolver->findOrFail($selector)->isDisplayed();
}, "Waited {$seconds} seconds for selector [{$selector}].");
}
/**
* Wait for the given selector to be removed.
*
* @param string $selector
* @param int $seconds
* @return $this
*/
public function waitUntilMissing($selector, $seconds = 5)
{
return $this->waitUsing($seconds, 100, function () use ($selector) {
try {
$missing = ! $this->resolver->findOrFail($selector)->isDisplayed();
} catch (NoSuchElementException $e) {
$missing = true;
}
return $missing;
}, "Waited {$seconds} seconds for removal of selector [{$selector}].");
}
/**
* Wait for the given text to be visible.
*
* @param string $text
* @param int $seconds
* @return $this
*/
public function waitForText($text, $seconds = 5)
{
return $this->waitUsing($seconds, 100, function () use ($text) {
return Str::contains($this->resolver->findOrFail('')->getText(), $text);
}, "Waited {$seconds} seconds for text [{$text}].");
}
/**
* Wait for the given link to be visible.
*
* @param string $link
* @param int $seconds
* @return $this
*/
public function waitForLink($link, $seconds = 5)
{
return $this->waitUsing($seconds, 100, function () use ($link) {
return $this->seeLink($link);
});
}
/**
* Wait for the given location.
*
* @param string $path
* @param int $seconds
* @return $this
*/
public function waitForLocation($path, $seconds = 5)
{
return $this->waitUntil("window.location.pathname == '{$path}'", $seconds);
}
/**
* Wait until the given script returns true.
*
* @param string $script
* @param int $seconds
* @return $this
*/
public function waitUntil($script, $seconds = 5)
{
if (! Str::startsWith($script, 'return ')) {
$script = 'return '.$script;
}
if (! Str::endsWith($script, ';')) {
$script = $script.';';
}
return $this->waitUsing($seconds, 100, function () use ($script) {
return $this->driver->executeScript($script);
});
}
/**
* Wait for the current page to reload.
*
* @param Closure $callback
* @param int $seconds
* @return $this
*/
public function waitForReload($callback = null, $seconds = 5)
{
$token = str_random();
$this->driver->executeScript("window['{$token}'] = {};");
if ($callback) {
$callback($this);
}
return $this->waitUsing($seconds, 100, function () use ($token) {
return $this->driver->executeScript("return typeof window['{$token}'] === 'undefined';");
});
}
/**
* Wait for the given callback to be true.
*
* @param int $seconds
* @param int $interval
* @param Closure $callback
* @param string|null $message
* @return $this
* @throws TimeOutException
*/
public function waitUsing($seconds, $interval, Closure $callback, $message = null)
{
$this->pause($interval);
$started = Carbon::now();
while (true) {
try {
if ($callback()) {
break;
}
} catch (Exception $e) {
//
}
if ($started->lt(Carbon::now()->subSeconds($seconds))) {
throw new TimeOutException($message ?: "Waited {$seconds} seconds for callback.");
}
$this->pause($interval);
}
return $this;
}
}

View File

@@ -0,0 +1,230 @@
<?php
namespace Laravel\Dusk\Console;
use Dotenv\Dotenv;
use Illuminate\Console\Command;
use Symfony\Component\Finder\Finder;
use Symfony\Component\Process\ProcessBuilder;
use Symfony\Component\Process\Exception\RuntimeException;
class DuskCommand extends Command
{
/**
* The name and signature of the console command.
*
* @var string
*/
protected $signature = 'dusk';
/**
* The console command description.
*
* @var string
*/
protected $description = 'Run the Dusk tests for the application';
/**
* Indicates if the project has its own PHPUnit configuration.
*
* @var boolean
*/
protected $hasPhpUnitConfiguration = false;
/**
* Create a new command instance.
*
* @return void
*/
public function __construct()
{
parent::__construct();
$this->ignoreValidationErrors();
}
/**
* Execute the console command.
*
* @return mixed
*/
public function handle()
{
$this->purgeScreenshots();
$this->purgeConsoleLogs();
$options = array_slice($_SERVER['argv'], 2);
return $this->withDuskEnvironment(function () use ($options) {
$process = (new ProcessBuilder())
->setTimeout(null)
->setPrefix($this->binary())
->setArguments($this->phpunitArguments($options))
->getProcess();
try {
$process->setTty(true);
} catch (RuntimeException $e) {
$this->output->writeln('Warning: '.$e->getMessage());
}
return $process->run(function ($type, $line) {
$this->output->write($line);
});
});
}
/**
* Get the PHP binary to execute.
*
* @return array
*/
protected function binary()
{
return [PHP_BINARY, 'vendor/phpunit/phpunit/phpunit'];
}
/**
* Get the array of arguments for running PHPUnit.
*
* @return array
*/
protected function phpunitArguments($options)
{
return array_merge(['-c', base_path('phpunit.dusk.xml')], $options);
}
/**
* Purge the failure screenshots
*
* @return void
*/
protected function purgeScreenshots()
{
$files = Finder::create()->files()
->in(base_path('tests/Browser/screenshots'))
->name('failure-*');
foreach ($files as $file) {
@unlink($file->getRealPath());
}
}
/**
* Purge the console logs.
*
* @return void
*/
protected function purgeConsoleLogs()
{
$files = Finder::create()->files()
->in(base_path('tests/Browser/console'))
->name('*.log');
foreach ($files as $file) {
@unlink($file->getRealPath());
}
}
/**
* Run the given callback with the Dusk configuration files.
*
* @param \Closure $callback
* @return mixed
*/
protected function withDuskEnvironment($callback)
{
if (file_exists(base_path($this->duskFile()))) {
if (file_get_contents(base_path('.env')) !== file_get_contents(base_path($this->duskFile()))) {
$this->backupEnvironment();
}
$this->refreshEnvironment();
}
$this->writeConfiguration();
return tap($callback(), function () {
$this->removeConfiguration();
if (file_exists(base_path($this->duskFile())) && file_exists(base_path('.env.backup'))) {
$this->restoreEnvironment();
}
});
}
/**
* Backup the current environment file.
*
* @return void
*/
protected function backupEnvironment()
{
copy(base_path('.env'), base_path('.env.backup'));
copy(base_path($this->duskFile()), base_path('.env'));
}
/**
* Restore the backed-up environment file.
*
* @return void
*/
protected function restoreEnvironment()
{
copy(base_path('.env.backup'), base_path('.env'));
unlink(base_path('.env.backup'));
}
/**
* Refresh the current environment variables.
*
* @return void
*/
protected function refreshEnvironment()
{
(new Dotenv(base_path()))->overload();
}
/**
* Write the Dusk PHPUnit configuration.
*
* @return void
*/
protected function writeConfiguration()
{
if (! file_exists($file = base_path('phpunit.dusk.xml'))) {
copy(realpath(__DIR__.'/../../stubs/phpunit.xml'), $file);
} else {
$this->hasPhpUnitConfiguration = true;
}
}
/**
* Remove the Dusk PHPUnit configuration.
*
* @return void
*/
protected function removeConfiguration()
{
if (! $this->hasPhpUnitConfiguration) {
unlink(base_path('phpunit.dusk.xml'));
}
}
/**
* Get the name of the Dusk file for the environment.
*
* @return string
*/
protected function duskFile()
{
if (file_exists(base_path($file = '.env.dusk.'.$this->laravel->environment()))) {
return $file;
}
return '.env.dusk';
}
}

View File

@@ -0,0 +1,95 @@
<?php
namespace Laravel\Dusk\Console;
use Illuminate\Console\Command;
class InstallCommand extends Command
{
/**
* The name and signature of the console command.
*
* @var string
*/
protected $signature = 'dusk:install';
/**
* The console command description.
*
* @var string
*/
protected $description = 'Install Dusk into the application';
/**
* Create a new command instance.
*
* @return void
*/
public function __construct()
{
parent::__construct();
}
/**
* Execute the console command.
*
* @return mixed
*/
public function handle()
{
if (! is_dir(base_path('tests/Browser/Pages'))) {
mkdir(base_path('tests/Browser/Pages'), 0755, true);
}
if (! is_dir(base_path('tests/Browser/screenshots'))) {
$this->createScreenshotsDirectory();
}
if (! is_dir(base_path('tests/Browser/console'))) {
$this->createConsoleDirectory();
}
$subs = [
'ExampleTest.stub' => base_path('tests/Browser/ExampleTest.php'),
'HomePage.stub' => base_path('tests/Browser/Pages/HomePage.php'),
'DuskTestCase.stub' => base_path('tests/DuskTestCase.php'),
'Page.stub' => base_path('tests/Browser/Pages/Page.php'),
];
foreach ($subs as $stub => $file) {
if (! is_file($file)) {
copy(__DIR__.'/../../stubs/'.$stub, $file);
}
}
$this->info('Dusk scaffolding installed successfully.');
}
/**
* Create the screenshots directory.
*
* @return void
*/
protected function createScreenshotsDirectory()
{
mkdir(base_path('tests/Browser/screenshots'), 0755, true);
file_put_contents(base_path('tests/Browser/screenshots/.gitignore'), '*
!.gitignore
');
}
/**
* Create the console directory.
*
* @return void
*/
protected function createConsoleDirectory()
{
mkdir(base_path('tests/Browser/console'), 0755, true);
file_put_contents(base_path('tests/Browser/console/.gitignore'), '*
!.gitignore
');
}
}

View File

@@ -0,0 +1,73 @@
<?php
namespace Laravel\Dusk\Console;
use Illuminate\Console\GeneratorCommand;
class MakeCommand extends GeneratorCommand
{
/**
* The console command name.
*
* @var string
*/
protected $signature = 'dusk:make {name : The name of the class}';
/**
* The console command description.
*
* @var string
*/
protected $description = 'Create a new Dusk test class';
/**
* The type of class being generated.
*
* @var string
*/
protected $type = 'Test';
/**
* Get the stub file for the generator.
*
* @return string
*/
protected function getStub()
{
return __DIR__.'/stubs/test.stub';
}
/**
* Get the destination class path.
*
* @param string $name
* @return string
*/
protected function getPath($name)
{
$name = str_replace_first($this->rootNamespace(), '', $name);
return $this->laravel->basePath().'/tests'.str_replace('\\', '/', $name).'.php';
}
/**
* Get the default namespace for the class.
*
* @param string $rootNamespace
* @return string
*/
protected function getDefaultNamespace($rootNamespace)
{
return $rootNamespace.'\Browser';
}
/**
* Get the root namespace for the class.
*
* @return string
*/
protected function rootNamespace()
{
return 'Tests';
}
}

View File

@@ -0,0 +1,73 @@
<?php
namespace Laravel\Dusk\Console;
use Illuminate\Console\GeneratorCommand;
class PageCommand extends GeneratorCommand
{
/**
* The console command name.
*
* @var string
*/
protected $signature = 'dusk:page {name : The name of the class}';
/**
* The console command description.
*
* @var string
*/
protected $description = 'Create a new Dusk page class';
/**
* The type of class being generated.
*
* @var string
*/
protected $type = 'Page';
/**
* Get the stub file for the generator.
*
* @return string
*/
protected function getStub()
{
return __DIR__.'/stubs/page.stub';
}
/**
* Get the destination class path.
*
* @param string $name
* @return string
*/
protected function getPath($name)
{
$name = str_replace_first($this->rootNamespace(), '', $name);
return $this->laravel->basePath().'/tests'.str_replace('\\', '/', $name).'.php';
}
/**
* Get the default namespace for the class.
*
* @param string $rootNamespace
* @return string
*/
protected function getDefaultNamespace($rootNamespace)
{
return $rootNamespace.'\Browser\Pages';
}
/**
* Get the root namespace for the class.
*
* @return string
*/
protected function rootNamespace()
{
return 'Tests';
}
}

View File

@@ -0,0 +1,42 @@
<?php
namespace DummyNamespace;
use Laravel\Dusk\Browser;
use Laravel\Dusk\Page as BasePage;
class DummyClass extends BasePage
{
/**
* Get the URL for the page.
*
* @return string
*/
public function url()
{
return '/';
}
/**
* Assert that the browser is on the page.
*
* @param Browser $browser
* @return void
*/
public function assert(Browser $browser)
{
$browser->assertPathIs($this->url());
}
/**
* Get the element shortcuts for the page.
*
* @return array
*/
public function elements()
{
return [
'@element' => '#selector',
];
}
}

View File

@@ -0,0 +1,23 @@
<?php
namespace DummyNamespace;
use Tests\DuskTestCase;
use Laravel\Dusk\Browser;
use Illuminate\Foundation\Testing\DatabaseMigrations;
class DummyClass extends DuskTestCase
{
/**
* A Dusk test example.
*
* @return void
*/
public function testExample()
{
$this->browse(function (Browser $browser) {
$browser->visit('/')
->assertSee('Laravel');
});
}
}

44
vendor/laravel/dusk/src/Dusk.php vendored Normal file
View File

@@ -0,0 +1,44 @@
<?php
namespace Laravel\Dusk;
use InvalidArgumentException;
class Dusk
{
/**
* Register the Dusk service provider.
*
* @param array $options
* @return void
*/
public static function register(array $options = [])
{
if (static::duskEnvironment($options)) {
app()->register(DuskServiceProvider::class);
}
}
/**
* Determine if Dusk may run in this environment.
*
* @param array $options
* @return bool
*/
protected static function duskEnvironment($options)
{
if (! isset($options['environments'])) {
return false;
}
if (is_string($options['environments'])) {
$options['environments'] = [$options['environments']];
}
if (! is_array($options['environments'])) {
throw new InvalidArgumentException("Dusk environments must be listed as an array.");
}
return app()->environment(...$options['environments']);
}
}

View File

@@ -0,0 +1,55 @@
<?php
namespace Laravel\Dusk;
use Exception;
use Illuminate\Support\Facades\Route;
use Illuminate\Support\ServiceProvider;
class DuskServiceProvider extends ServiceProvider
{
/**
* Bootstrap any package services.
*
* @return void
*/
public function boot()
{
Route::get('/_dusk/login/{userId}/{guard?}', [
'middleware' => 'web',
'uses' => 'Laravel\Dusk\Http\Controllers\UserController@login',
]);
Route::get('/_dusk/logout/{guard?}', [
'middleware' => 'web',
'uses' => 'Laravel\Dusk\Http\Controllers\UserController@logout',
]);
Route::get('/_dusk/user/{guard?}', [
'middleware' => 'web',
'uses' => 'Laravel\Dusk\Http\Controllers\UserController@user',
]);
}
/**
* Register any package services.
*
* @return void
* @throws Exception
*/
public function register()
{
if ($this->app->environment('production')) {
throw new Exception('It is unsafe to run Dusk in production.');
}
if ($this->app->runningInConsole()) {
$this->commands([
Console\InstallCommand::class,
Console\DuskCommand::class,
Console\MakeCommand::class,
Console\PageCommand::class,
]);
}
}
}

View File

@@ -0,0 +1,377 @@
<?php
namespace Laravel\Dusk;
use Exception;
use Illuminate\Support\Str;
use InvalidArgumentException;
use Facebook\WebDriver\WebDriverBy;
use Illuminate\Support\Traits\Macroable;
class ElementResolver
{
use Macroable;
/**
* The remote web driver instance.
*
* @var \Facebook\WebDriver\Remote\RemoteWebDriver
*/
public $driver;
/**
* The selector prefix for the resolver.
*
* @var string
*/
public $prefix;
/**
* Set the elements the resolver should use as shortcuts.
*
* @var array
*/
public $elements = [];
/**
* The button finding methods.
*
* @var array
*/
protected $buttonFinders = [
'findById',
'findButtonBySelector',
'findButtonByName',
'findButtonByValue',
'findButtonByText'
];
/**
* Create a new element resolver instance.
*
* @param \Facebook\WebDriver\Remote\RemoteWebDriver $driver
* @param string $prefix
* @return void
*/
public function __construct($driver, $prefix = 'body')
{
$this->driver = $driver;
$this->prefix = trim($prefix);
}
/**
* Set the page elements the resolver should use as shortcuts.
*
* @param array $elements
* @return $this
*/
public function pageElements(array $elements)
{
$this->elements = $elements;
return $this;
}
/**
* Resolve the element for a given input "field".
*
* @param string $field
* @return \Facebook\WebDriver\Remote\RemoteWebElement
*/
public function resolveForTyping($field)
{
if (! is_null($element = $this->findById($field))) {
return $element;
}
return $this->firstOrFail([
$field, "input[name='{$field}']", "textarea[name='{$field}']"
]);
}
/**
* Resolve the element for a given select "field".
*
* @param string $field
* @return \Facebook\WebDriver\Remote\RemoteWebElement
*/
public function resolveForSelection($field)
{
if (! is_null($element = $this->findById($field))) {
return $element;
}
return $this->firstOrFail([
$field, "select[name='{$field}']"
]);
}
/**
* Resolve all the options with the given value on the select field.
*
* @param string $field
* @param array $values
* @return array
*/
public function resolveSelectOptions($field, array $values)
{
$options = $this->resolveForSelection($field)
->findElements(WebDriverBy::tagName('option'));
if (empty($options)) {
return [];
}
return array_filter($options, function($option) use ($values) {
return in_array($option->getAttribute('value'), $values);
});
}
/**
* Resolve the element for a given radio "field" / value.
*
* @param string $field
* @param string $value
* @return \Facebook\WebDriver\Remote\RemoteWebElement
*/
public function resolveForRadioSelection($field, $value = null)
{
if (! is_null($element = $this->findById($field))) {
return $element;
}
if (is_null($value)) {
throw new InvalidArgumentException(
"No value was provided for radio button [{$field}]."
);
}
return $this->firstOrFail([
$field, "input[type=radio][name='{$field}'][value='{$value}']"
]);
}
/**
* Resolve the element for a given checkbox "field".
*
* @param string $field
* @param string $value
* @return \Facebook\WebDriver\Remote\RemoteWebElement
*/
public function resolveForChecking($field, $value = null)
{
if (! is_null($element = $this->findById($field))) {
return $element;
}
$selector = "input[type=checkbox][name='{$field}']";
if (! is_null($value)) {
$selector .= "[value='{$value}']";
}
return $this->firstOrFail([
$field, $selector
]);
}
/**
* Resolve the element for a given file "field".
*
* @param string $field
* @return \Facebook\WebDriver\Remote\RemoteWebElement
*/
public function resolveForAttachment($field)
{
if (! is_null($element = $this->findById($field))) {
return $element;
}
return $this->firstOrFail([
$field, "input[type=file][name='{$field}']"
]);
}
/**
* Resolve the element for a given button.
*
* @param string $button
* @return \Facebook\WebDriver\Remote\RemoteWebElement
*/
public function resolveForButtonPress($button)
{
foreach ($this->buttonFinders as $method) {
if (! is_null($element = $this->{$method}($button))) {
return $element;
}
}
throw new InvalidArgumentException(
"Unable to locate button [{$button}]."
);
}
/**
* Resolve the element for a given button by selector.
*
* @param string $button
* @return \Facebook\WebDriver\Remote\RemoteWebElement|null
*/
protected function findButtonBySelector($button)
{
if (! is_null($element = $this->find($button))) {
return $element;
}
}
/**
* Resolve the element for a given button by name.
*
* @param string $button
* @return \Facebook\WebDriver\Remote\RemoteWebElement|null
*/
protected function findButtonByName($button)
{
if (! is_null($element = $this->find("input[type=submit][name='{$button}']")) ||
! is_null($element = $this->find("input[type=button][value='{$button}']")) ||
! is_null($element = $this->find("button[name='{$button}']"))) {
return $element;
}
}
/**
* Resolve the element for a given button by value.
*
* @param string $button
* @return \Facebook\WebDriver\Remote\RemoteWebElement|null
*/
protected function findButtonByValue($button)
{
foreach ($this->all("input[type=submit]") as $element) {
if ($element->getAttribute('value') === $button) {
return $element;
}
}
}
/**
* Resolve the element for a given button by text.
*
* @param string $button
* @return \Facebook\WebDriver\Remote\RemoteWebElement|null
*/
protected function findButtonByText($button)
{
foreach ($this->all('button') as $element) {
if (Str::contains($element->getText(), $button)) {
return $element;
}
}
}
/**
* Attempt to find the selector by ID.
*
* @param string $selector
* @return \Facebook\WebDriver\Remote\RemoteWebElement|null
*/
protected function findById($selector)
{
if (preg_match('/^#[\w\-:]+$/', $selector)) {
return $this->driver->findElement(WebDriverBy::id(substr($selector, 1)));
}
}
/**
* Find an element by the given selector or return null.
*
* @param string $selector
* @return \Facebook\WebDriver\Remote\RemoteWebElement|null
*/
public function find($selector)
{
try {
return $this->findOrFail($selector);
} catch (Exception $e) {
//
}
}
/**
* Get the first element matching the given selectors.
*
* @param array $selectors
* @return \Facebook\WebDriver\Remote\RemoteWebElement
*/
public function firstOrFail($selectors)
{
foreach ((array) $selectors as $selector) {
try {
return $this->findOrFail($selector);
} catch (Exception $e) {
//
}
}
throw $e;
}
/**
* Find an element by the given selector or throw an exception.
*
* @param string $selector
* @return \Facebook\WebDriver\Remote\RemoteWebElement
*/
public function findOrFail($selector)
{
if (! is_null($element = $this->findById($selector))) {
return $element;
}
return $this->driver->findElement(
WebDriverBy::cssSelector($this->format($selector))
);
}
/**
* Find the elements by the given selector or return an empty array.
*
* @param string $selector
* @return array
*/
public function all($selector)
{
try {
return $this->driver->findElements(
WebDriverBy::cssSelector($this->format($selector))
);
} catch (Exception $e) {
//
}
return [];
}
/**
* Format the given selector with the current prefix.
*
* @param string $selector
* @return string
*/
public function format($selector)
{
$sortedElements = collect($this->elements)->sortByDesc(function($element, $key){
return strlen($key);
})->toArray();
$selector = str_replace(
array_keys($sortedElements), array_values($sortedElements), $originalSelector = $selector
);
if (starts_with($selector, '@') && $selector === $originalSelector) {
$selector = '[dusk="'.explode('@', $selector)[1].'"]';
}
return trim($this->prefix.' '.$selector);
}
}

View File

@@ -0,0 +1,74 @@
<?php
namespace Laravel\Dusk\Http\Controllers;
use Illuminate\Support\Facades\Auth;
class UserController
{
/**
* Retrieve the authenticated user identifier and class name.
*
* @param string|null $guard
* @return array
*/
public function user($guard = null)
{
$user = Auth::guard($guard)->user();
if (! $user) {
return [];
}
return [
'id' => $user->getAuthIdentifier(),
'className' => get_class($user),
];
}
/**
* Login using the given user ID / email.
*
* @param string $userId
* @param string $guard
* @return Response
*/
public function login($userId, $guard = null)
{
$model = $this->modelForGuard(
$guard = $guard ?: config('auth.defaults.guard')
);
if (str_contains($userId, '@')) {
$user = (new $model)->where('email', $userId)->first();
} else {
$user = (new $model)->find($userId);
}
Auth::guard($guard)->login($user);
}
/**
* Log the user out of the application.
*
* @param string $guard
* @return Response
*/
public function logout($guard = null)
{
Auth::guard($guard ?: config('auth.defaults.guard'))->logout();
}
/**
* Get the model for the given guard.
*
* @param string $guard
* @return string
*/
protected function modelForGuard($guard)
{
$provider = config("auth.guards.{$guard}.provider");
return config("auth.providers.{$provider}.model");
}
}

44
vendor/laravel/dusk/src/Page.php vendored Normal file
View File

@@ -0,0 +1,44 @@
<?php
namespace Laravel\Dusk;
abstract class Page
{
/**
* Get the URL for the page.
*
* @return string
*/
abstract public function url();
/**
* Assert that the browser is on the page.
*
* @param \Laravel\Dusk\Browser $browser
* @return void
*/
public function assert(Browser $browser)
{
//
}
/**
* Get the element shortcuts for the page.
*
* @return array
*/
public function elements()
{
return [];
}
/**
* Get the global element shortcuts for the site.
*
* @return array
*/
public static function siteElements()
{
return [];
}
}

View File

@@ -0,0 +1,118 @@
<?php
namespace Laravel\Dusk;
use RuntimeException;
use Symfony\Component\Process\Process;
use Symfony\Component\Process\ProcessBuilder;
trait SupportsChrome
{
/**
* The path to the custom Chromedriver binary.
*
* @var string|null
*/
protected static $chromeDriver;
/**
* The Chromedriver process instance.
*
* @var \Symfony\Component\Process\Process
*/
protected static $chromeProcess;
/**
* Start the Chromedriver process.
*
* @throws \RuntimeException if the driver file path doesn't exist.
*
* @return void
*/
public static function startChromeDriver()
{
static::$chromeProcess = static::buildChromeProcess();
static::$chromeProcess->start();
static::afterClass(function () {
static::stopChromeDriver();
});
}
/**
* Stop the Chromedriver process.
*
* @return void
*/
public static function stopChromeDriver()
{
if (static::$chromeProcess) {
static::$chromeProcess->stop();
}
}
/**
* Build the process to run the Chromedriver.
*
* @throws \RuntimeException if the driver file path doesn't exist.
*
* @return \Symfony\Component\Process\Process
*/
protected static function buildChromeProcess()
{
$driver = static::$chromeDriver
?: realpath(__DIR__.'/../bin/chromedriver-'.static::driverSuffix());
if (realpath($driver) === false) {
throw new RuntimeException("Invalid path to Chromedriver [{$driver}].");
}
return (new ProcessBuilder())
->setPrefix(realpath($driver))
->getProcess()
->setEnv(static::chromeEnvironment());
}
/**
* Set the path to the custom Chromedriver.
*
* @param string $path
* @return void
*/
public static function useChromedriver($path)
{
static::$chromeDriver = $path;
}
/**
* Get the Chromedriver environment variables.
*
* @return array
*/
protected static function chromeEnvironment()
{
if (PHP_OS === 'Darwin' || PHP_OS === 'WINNT') {
return [];
}
return ['DISPLAY' => ':0'];
}
/**
* Get the suffix for the Chromedriver binary.
*
* @return string
*/
protected static function driverSuffix()
{
switch (PHP_OS) {
case 'Darwin':
return 'mac';
case 'WINNT':
return 'win.exe';
default:
return 'linux';
}
}
}

245
vendor/laravel/dusk/src/TestCase.php vendored Normal file
View File

@@ -0,0 +1,245 @@
<?php
namespace Laravel\Dusk;
use Closure;
use Exception;
use Throwable;
use ReflectionFunction;
use Illuminate\Support\Collection;
use Facebook\WebDriver\Remote\RemoteWebDriver;
use Facebook\WebDriver\Remote\DesiredCapabilities;
use Illuminate\Foundation\Testing\TestCase as FoundationTestCase;
abstract class TestCase extends FoundationTestCase
{
use SupportsChrome;
/**
* All of the active browser instances.
*
* @var array
*/
protected static $browsers = [];
/**
* The callbacks that should be run on class tear down.
*
* @var array
*/
protected static $afterClassCallbacks = [];
/**
* Register the base URL with Dusk.
*
* @return void
*/
protected function setUp()
{
parent::setUp();
Browser::$baseUrl = $this->baseUrl();
Browser::$storeScreenshotsAt = base_path('tests/Browser/screenshots');
Browser::$storeConsoleLogAt = base_path('tests/Browser/console');
Browser::$userResolver = function () {
return $this->user();
};
}
/**
* Tear down the Dusk test case class.
*
* @afterClass
* @return void
*/
public static function tearDownDuskClass()
{
static::closeAll();
foreach (static::$afterClassCallbacks as $callback) {
$callback();
}
}
/**
* Register an "after class" tear down callback.
*
* @param \Closure $callback
* @return void
*/
public static function afterClass(Closure $callback)
{
static::$afterClassCallbacks[] = $callback;
}
/**
* Create a new browser instance.
*
* @param \Closure $callback
* @return \Laravel\Dusk\Browser|void
* @throws \Exception
* @throws \Throwable
*/
public function browse(Closure $callback)
{
$browsers = $this->createBrowsersFor($callback);
try {
$callback(...$browsers->all());
} catch (Exception $e) {
$this->captureFailuresFor($browsers);
throw $e;
} catch (Throwable $e) {
$this->captureFailuresFor($browsers);
throw $e;
} finally {
$this->storeConsoleLogsFor($browsers);
static::$browsers = $this->closeAllButPrimary($browsers);
}
}
/**
* Create the browser instances needed for the given callback.
*
* @param \Closure $callback
* @return array
*/
protected function createBrowsersFor(Closure $callback)
{
if (count(static::$browsers) === 0) {
static::$browsers = collect([$this->newBrowser($this->createWebDriver())]);
}
$additional = $this->browsersNeededFor($callback) - 1;
for ($i = 0; $i < $additional; $i++) {
static::$browsers->push($this->newBrowser($this->createWebDriver()));
}
return static::$browsers;
}
/**
* Create a new Browser instance.
*
* @param \Facebook\WebDriver\Remote\RemoteWebDriver $driver
* @return \Laravel\Dusk\Browser
*/
protected function newBrowser($driver)
{
return new Browser($driver);
}
/**
* Get the number of browsers needed for a given callback.
*
* @param \Closure $callback
* @return int
*/
protected function browsersNeededFor(Closure $callback)
{
return (new ReflectionFunction($callback))->getNumberOfParameters();
}
/**
* Capture failure screenshots for each browser.
*
* @param \Illuminate\Support\Collection $browsers
* @return void
*/
protected function captureFailuresFor($browsers)
{
$browsers->each(function ($browser, $key) {
$browser->screenshot('failure-'.$this->getName().'-'.$key);
});
}
/**
* Store the console output for the given browsers.
*
* @param \Illuminate\Support\Collection $browsers
* @return void
*/
protected function storeConsoleLogsFor($browsers)
{
$browsers->each(function ($browser, $key) {
$browser->storeConsoleLog($this->getName().'-'.$key);
});
}
/**
* Close all of the browsers except the primary (first) one.
*
* @param \Illuminate\Support\Collection $browsers
* @return \Illuminate\Support\Collection
*/
protected function closeAllButPrimary($browsers)
{
$browsers->slice(1)->each->quit();
return $browsers->take(1);
}
/**
* Close all of the active browsers.
*
* @return void
*/
public static function closeAll()
{
Collection::make(static::$browsers)->each->quit();
static::$browsers = collect();
}
/**
* Create the remote web driver instance.
*
* @return \Facebook\WebDriver\Remote\RemoteWebDriver
*/
protected function createWebDriver()
{
return retry(5, function () {
return $this->driver();
}, 50);
}
/**
* Create the RemoteWebDriver instance.
*
* @return \Facebook\WebDriver\Remote\RemoteWebDriver
*/
protected function driver()
{
return RemoteWebDriver::create(
'http://localhost:9515', DesiredCapabilities::chrome()
);
}
/**
* Determine the application's base URL.
*
* @var string
*/
protected function baseUrl()
{
return config('app.url');
}
/**
* Get a callback that returns the default user to authenticate.
*
* @return \Closure
* @throws \Exception
*/
protected function user()
{
throw new Exception("User resolver has not been set.");
}
}