I'm using laravel 5.6 and Dusk for running some tests.
I'm always taking my screenshot like this
...
use Facebook\WebDriver\WebDriverDimension;
...
class LoginTest extends DuskTestCase
{
public function testLogin()
{
$user = User::first();
$this->browse(function ($browser) use ( $user ) {
$test = $browser->visit( new Login)
->resize(1920,1080)
...
->driver->takeScreenshot(base_path('tests/Browser/screenshots/testLogin.png'));
});
}
}
But as my tests will be more and more used, I don't want to continue to write everytime ->resize(X,Y) and base_path('bla/blab/bla').
I wanted to define the size and path for every tests that will be written.
I guess I should define some function in tests/DesukTestCase.php but I'm not even aware of how I can retrieve the driver and so on.
Have you got some guidance or documentation about this? Because I can't find anything...
In my DuskTestCase file I have the below in my driver() function.
protected function driver()
{
$options = (new ChromeOptions())->addArguments([
'--disable-gpu',
'--headless',
]);
$driver = RemoteWebDriver::create(
'http://selenium:4444/wd/hub',
DesiredCapabilities::chrome()->setCapability(
ChromeOptions::CAPABILITY,
$options
)
);
$size = new WebDriverDimension(1280, 2000);
$driver->manage()->window()->setSize($size);
return $driver;
}
You should just be able to configure it with the right dimensions you need.
You only need to add '--window-size=1920,1080' in $options. This will apply a 1920x1080 screen resolution to all your Dusk tests. Feel free to adjust to whatever window size you want.
So your DuskTestCase.php file should look like this:
protected function driver()
{
$options = (new ChromeOptions())->addArguments([
'--disable-gpu',
'--headless',
'--window-size=1920,1080',
]);
$driver = RemoteWebDriver::create(
'http://selenium:4444/wd/hub',
DesiredCapabilities::chrome()->setCapability(
ChromeOptions::CAPABILITY,
$options
)
);
}
Regarding the path issue, you can set it with Browser::$storeScreenshotsAt in setUp method of your test case class.
protected function setUp()
{
parent::setUp();
Browser::$storeScreenshotsAt = '/path/to/your/screenshots';
}
Default location of Browser::$storeScreenshotsAt is set in setUp method of the grand parent test case class.
So, make sure that you set Browser::$storeScreenshotsAt after calling parent::setUp(), otherwise it will be overwritten by the default.
Related
I am trying to remove some fields before they are validated.
Trying to attempt this inside prepareForValidation()
use Illuminate\Foundation\Http\FormRequest;
class VideoRequest extends ApiRequest
{
// ..code..
protected function prepareForValidation()
{
$this->merge([
'public' => $this->toBoolean($this->public),
'notify' => $this->toBoolean($this->notify),
]);
$video_id = $this->route('video_id');
if($this->isMethod('put') && Video::salesStarted($video_id)){
Log::info('removing sales');
// attempt 1
$this->except(['sales_start', 'tickets', 'price']);
// attempt 2
$this->request->remove('sales_start');
// attempt 3
$this->offsetUnset('sales_start');
}
if($this->isMethod('put') && Video::streamStarted($video_id)){
Log::info('removing streams');
// attempt 1
$this->except(['stream_start', 'stream_count', 'archive']);
// attempt 2
$this->request->remove('sales_start');
// attempt 3
$this->offsetUnset('sales_start');
}
$thumb = $this->uploadThumbnail($video_id);
if($thumb !== null){
$this->merge($thumb);
}
}
// ..code..
}
I made sure the code was entering inside the if statement, however the fields are not being removed.
Running $this->request->remove() and $this->except() have no effect.
If I add safe() it throws Call to a member function safe() on null.
I also tried to use unset() but nothing seems to work.
The rules for the dates are like so:
'sales_start' => 'sometimes|required|date|after:now|before:stream_start',
'stream_start' => 'sometimes|required|date|after:now',
but the $request->validated() returns the errors although it shouldn't be validating the deleted fields.
"sales_start": [
"The sales start must be a date after now."
],
"stream_start": [
"The stream start must be a date after now."
]
Why are the fields not being deleted?
Edit
As requested I added some code.
This is what ApiRequest looks like:
use Illuminate\Http\Exceptions\HttpResponseException;
use Illuminate\Foundation\Http\FormRequest;
use Illuminate\Contracts\Validation\Validator;
abstract class ApiRequest extends FormRequest
{
protected function failedValidation(Validator $validator): void
{
$response['data'] = [];
$response['api_status'] = 'ng';
$response['status_message'] = 'failed validation';
$response['errors'] = $validator->errors()->toArray();
throw new HttpResponseException(
response()->json( $response, 422 )
);
}
protected function toBoolean($booleable)
{
return filter_var($booleable, FILTER_VALIDATE_BOOLEAN, FILTER_NULL_ON_FAILURE);
}
}
And the request is called from the controller like so:
public function update(VideoRequest $request, $video_id)
{
... some code ...
$validated = $request->validated();
... some code ...
}
so $this refers to the VideoRequest that extends FormRequest.
Can't find anything about deleting. But acording to Laravel docs you pick what keys you need from a request as follows:
$request->only(['username', 'password']);
// plug everything you need into the array.
$request->except(['username', 'password']);
//plug everything you don't need into the array
The latter is probably most useful to you.
Example:
Say I have the following keys: ['username', 'password', 'randomVal'];
$request->only(['username', 'password']);
// Output:
['username', 'password']
$request->except(['username', 'password']);
// Output:
['randomVal']
To remove (unset) a key from a Request before it goes to the Controller you can use offsetUnset()
inside your request:
protected function prepareForValidation()
{
$this->offsetUnset('sales_start');//same goes for the other key to remove...
}
This is a bit of an ugly answer.
Instead of modifying the request before the validation, I tried adding exclude when getting rules().
So something along these lines:
public function rules() {
$ex = $this->isMethod('put') && Video::salesStarted($video_id) ? 'exclude|' : '';
return [
'sales_start' => $ex.'sometimes|required|other_stuff',
];
}
Note that the validation 'exclude' only works if added first.
So this won't work:
'sometimes|required|other_stuff|exclude' //exclude is called last
I am still unable to find out why remove(), exclude(), offsetUnset() were not working, so this is not the right answer, but I hope it helps if someone is having the same issue.
Edit
Setting this as correct answer as I was unable to find an alternative solution/fix.
I am trying to test my View Composers. Whenever I pass an object to the $view->with('string', $object), my test fails. This is when I do the test like this:
$view
->shouldReceive('with')
->with('favorites', $this->user->favorites(Ad::class)->get())
->once();
I'm pretty sure this is due to strict checking. So I looked around and saw this issue. However, I can't seem to get it working. The closure return true, but the test fails:
Mockery\Exception\InvalidCountException : Method with('favorites',
< Closure===true >) from Mockery_3_Illuminate_View_View should be called
exactly 1 times but called 0 times.
Here is my current test
public function it_passes_favorites_to_the_view()
{
$this->setUpUser(); // basically sets $this->user to a User object
Auth::shouldReceive('user')
->once()
->andReturn($this->user);
$composer = new FavoritesComposer();
$view = Mockery::spy(View::class);
$view
->shouldReceive('with')
->with('favorites', Mockery::on(function($arg) {
$this->assertEquals($this->user->favorites(Ad::class)->get(), $arg);
}))
->once();
$composer->compose($view);
}
FavoritesComposer class:
public function compose(View $view)
{
$user = Auth::user();
$favorites = $user
? $user->favorites(Ad::class)->get()
: collect([]);
$view->with('favorites', $favorites);
}
How do I test object like this?
I fixed the issue by replacing $view->with('favorites', $favorites); with $view->with(['favorites' => $favorites]); and then testing it like this:
$view
->shouldReceive('with')
->with(['favorites' => $this->user->favorites(Ad::class)->get()])
->once();
So, essentially using only one parameter in the with()-method is what fixed it for me.
I am writing some tests and I want to see whether Dusk correctly fills in the input fields but Dusk doesn't show the browser while running the tests, is there any way to force it to do that?
Disable the headless mode in tests\DuskTestCase.php file driver() function:
$options = (new ChromeOptions)->addArguments([
//'--disable-gpu',
//'--headless'
]);
Updated (2021):
You can disable headless with 2 methods:
Method 1: Add this to your .env
DUSK_HEADLESS_DISABLED=true
Method 2: Add this to your special test case if you don't need to show the browser for all tests
protected function hasHeadlessDisabled(): bool
{
return true;
}
Btw, I don't know why these are not mentioned in the documentation. I found the above methods myself from DuskTestCase.php.
Answer for Laravel 8 & UP
You can use php artisan dusk --browse to force showing the browser.
Near the top of your tests/DuskTestCase.php file, add:
use Facebook\WebDriver\Chrome\ChromeOptions;
In that same file, replace the entire driver() function with:
/**
* Create the RemoteWebDriver instance.
*
* #return \Facebook\WebDriver\Remote\RemoteWebDriver
*/
protected function driver() {
$options = (new ChromeOptions)->addArguments([
//'--disable-gpu',
//'--headless'//https://stackoverflow.com/q/49938673/470749
]);
return RemoteWebDriver::create(
'http://localhost:9515', DesiredCapabilities::chrome()->setCapability(
ChromeOptions::CAPABILITY, $options
)
);
}
Starting on a new build with Laravel Spark 6 (Laravel 5.6) and decided to give TDD a try.
First test was lovely, I created a unit test to make sure that users can create an team.
(Pseudo code):
class AddNewTeamTest extends TestCase
{
/** #test */
public function admin_can_create_new_team()
{
// Create a user account
$data = [
// Information for tea,
];
$response = $this->withHeaders([
'X-Requested-With' => 'XMLHttpRequest',
])
->actingAs($user)
->json('POST', '/api/teams', $data);
$response
->assertStatus(201);
}
}
Using this in TDD style was a nice process, but now I want to be able to write a test for adding a member to that team.
It seems backwards that in this new test, I would run all of the code in my first test. Is there anyway around this? For the new test I would need a user and team already created before I could test adding a user to that team..
Any links or advice welcome!
You can use function setUp() and build your enviroment inside it.
So your class should looks like that:
class AddNewTeamTest extends TestCase
{
protected function setUp()
{
// Create a user account
// Create your enviroment, etc.
$this->actingAs($user)
}
/** #test */
public function admin_can_create_new_team()
{
$data = [
// Information for tea,
];
$response = $this->withHeaders([
'X-Requested-With' => 'XMLHttpRequest',
])
->json('POST', '/api/teams', $data);
$response
->assertStatus(201);
}
public function testAnother()
{
\\your next test
}
}
If you need a team in next few cases, that should be added in setUp().
Also, you can make your next test needed ypur previous one. In that case you can return something in admin_can_create_new_team() and take as parameter in testAnother()
More info:
https://phpunit.de/manual/current/en/writing-tests-for-phpunit.html#writing-tests-for-phpunit.test-dependencies
I added this in routes.php, expected it will check the authentication session for the page, however it is not working.
Route::resource('ticket', 'TicketController', array('before' => 'auth') );
Then I go to the controller, work in another way. It's work.
class TicketController extends BaseController {
public function __construct()
{
$this->beforeFilter('auth');
}
May I know where can get more documentation regarding the Route::resource(), what type of argument it able to accept?
OK... I found the answer.
in
\vendor\laravel\framework\src\Illuminate\Routing\Router.php
public function resource($resource, $controller, array $options = array())
{
// If the resource name contains a slash, we will assume the developer wishes to
// register these resource routes with a prefix so we will set that up out of
// the box so they don't have to mess with it. Otherwise, we will continue.
if (str_contains($resource, '/'))
{
$this->prefixedResource($resource, $controller, $options);
return;
}
// We need to extract the base resource from the resource name. Nested resources
// are supported in the framework, but we need to know what name to use for a
// place-holder on the route wildcards, which should be the base resources.
$base = $this->getBaseResource($resource);
$defaults = $this->resourceDefaults;
foreach ($this->getResourceMethods($defaults, $options) as $method)
{
$this->{'addResource'.ucfirst($method)}($resource, $base, $controller);
}
}
protected function getResourceMethods($defaults, $options)
{
if (isset($options['only']))
{
return array_intersect($defaults, $options['only']);
}
elseif (isset($options['except']))
{
return array_diff($defaults, $options['except']);
}
return $defaults;
}
as you can see, it only accept only and except arguement only.
If you want to archive the same result in route.php, it can be done as below
Route::group(array('before'=>'auth'), function() {
Route::resource('ticket', 'TicketController');
});