Laravel Dusk how to show the browser while executing tests - laravel

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
)
);
}

Related

Unable to retrieve the file_size for file when using Storage::fake for testing

I just upgraded my app from Laravel v8 to v9. Now I'm having an issue with the following code throwing this error:
League\Flysystem\UnableToRetrieveMetadata : Unable to retrieve the file_size for file at location: test_file.xlsx.
My controller:
<?php
namespace App\Http\Controllers\Reports;
use App\Http\Controllers\Controller;
use Illuminate\Support\Facades\Storage;
class ReportDownloadController extends Controller
{
/**
* Handle the incoming request.
*
* #param string $path
* #return \Symfony\Component\HttpFoundation\StreamedResponse
*/
public function __invoke(string $path): \Symfony\Component\HttpFoundation\StreamedResponse
{
return Storage::disk('temp')->download($path, request()->query('filename'));
}
}
The relevant test:
/** #test */
public function it_returns_a_file_download_with_the_provided_filename_presented_to_the_user()
{
$this->withoutExceptionHandling();
Storage::fake('temp');
$fakeFile = UploadedFile::fake()->create('test_file.xlsx');
Storage::disk('temp')->put('test_file.xlsx', $fakeFile);
$response = $this->actingAs($this->user)
->getJson(route('reports.download', ['path' => 'test_file.xlsx', 'filename' => 'Appropriate.xlsx']));
$response->assertDownload('Appropriate.xlsx');
}
I know Flysystem was updated to v3, but I can't seem to figure out how to resolve this issue.
I even created an empty Laravel 9 app to test with the following code and still get the same error, so I don't think it is my app.
public function test_that_file_size_can_be_retrieved()
{
Storage::fake('local');
$fakeFile = UploadedFile::fake()->create('test_file.xlsx', 10);
Storage::disk('local')->put('test_file.xlsx', $fakeFile);
$this->assertEquals(10, (Storage::disk('local')->size('test_file.xlsx') / 1024));
}
What am is missing?
I've had the same problem when switching from Laravel 8 to Laravel 9.
The error Unable to retrieve the file_size was actually thrown because it couldn't find the file at all at the path provided.
In my case, it turned out that the path that was provided to the download function started with a /, which wasn't cause for problem with Flysystem 2 but apparently is with Flysystem 3.
Given the tests you're showing it doesn't look like it's the issue here but maybe this will help anyway.
So it turns out my test was broken all along. I should have been using $fakeFile->hashName() and I had the filename where I should have had '/' for the path in my Storage::put().
Here are the corrected tests that pass as expected:
/** #test */
public function it_returns_a_file_download_with_the_provided_filename_presented_to_the_user()
{
$this->withoutExceptionHandling();
Storage::fake('temp');
$fakeFile = UploadedFile::fake()->create('test_file.xlsx');
Storage::disk('temp')->put('/', $fakeFile);
$response = $this->actingAs($this->user)
->getJson(route('reports.download', ['path' => $fakeFile->hashName(), 'filename' => 'Appropriate.xlsx']));
$response->assertDownload('Appropriate.xlsx');
}
and
public function test_that_file_size_can_be_retrieved()
{
Storage::fake('local');
$fakeFile = UploadedFile::fake()->create('test_file.xlsx');
Storage::disk('local')->put('/', $fakeFile);
$this->assertEquals(0, Storage::disk('local')->size($fakeFile->hashName()));
}
Thanks, #yellaw. Your comment about it not being able to find the file at the path provided helped me realize I had done something stupid.

Laravel Dusk Test Multi Threading

I have a series of Laravel Dusk tests as shown below ,,
when I run php laravel dusk
each of them work correctly but one test after another
how can I run more than one test in the same time each test in separate browser ??
and how to control the order of the tests to run ?
here is my DuskTestCase options :
protected function driver()
{
$options = (new ChromeOptions)->addArguments([
'--disable-gpu',
'--window-size=1920,1080',
]);
return RemoteWebDriver::create(
'http://localhost:9515',
DesiredCapabilities::chrome()->setCapability(
ChromeOptions::CAPABILITY,
$options
),
90000,
90000
);
}

Unit Testing with Model::Find Fails in Laravel

I am creating unit tests for my rather large Laravel application.
This test passes all assertions. It creates a new database entry for the advertising status model.
/** #test **/
public function create_a_new_advertising_status_test()
{
$status = AdvertisingStatus::create(['name' => 'Test']);
$this->assertNotNull($status);
$this->assertCount(1, AdvertisingStatus::all());
$this->assertEquals($status->name, $this->advertisingStatus['name']);
}
This test fails the NotNull assertion as it says $status is null, which means that the Find method is failing.
/** #test **/
public function edit_an_existing_advertising_status_entry()
{
$status = AdvertisingStatus::create(['name' => 'Test']);
// $status = AdvertisingStatus::first(); // This successfully finds the database entry
$status = AdvertisingStatus::find(1); // This fails to find the database entry
$status->name = 'Edit';
$status->save();
$this->assertNotNull($status);
$this->assertCount(1, AdvertisingStatus::all());
$this->assertEquals($status->name, "Edit");
}
It appears like the find function and subsequently the where function takes too long to locate the entry, so the test renders the $status variable null.
Aside from using the Model::first() function, does anyone have any idea how to overcome this?
I'm wondering if it's because my application is extremely large and takes a long time to run because I'm using RefreshDatabase
You have to create a factory first, then you will use it like this:
$status = factory(AdvertisingStatus::class)->create(['name' => 'Test']);

Laravel Dusk screenshot

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.

How to cancel queued job in Laravel or Redis

How can I browse all the pending jobs within my Redis queue so that I could cancel the Mailable that has a certain emailAddress-sendTime pair?
I'm using Laravel 5.5 and have a Mailable that I'm using successfully as follows:
$sendTime = Carbon::now()->addHours(3);
Mail::to($emailAddress)
->bcc([config('mail.supportTeam.address'), config('mail.main.address')])
->later($sendTime, new MyCustomMailable($subject, $dataForMailView));
When this code runs, a job gets added to my Redis queue.
I've already read the Laravel docs but remain confused.
How can I cancel a Mailable (prevent it from sending)?
I'd love to code a webpage within my Laravel app that makes this easy for me.
Or maybe there are tools that already make this easy (maybe FastoRedis?)? In that case, instructions about how to achieve this goal that way would also be really helpful. Thanks!
Update:
I've tried browsing the Redis queue using FastoRedis, but I can't figure out how to delete a Mailable, such as the red arrow points to here:
UPDATE:
Look at the comprehensive answer I provided below.
Make it easier.
Don't send an email with the later option. You must dispatch a Job with the later option, and this job will be responsible to send the email.
Inside this job, before send the email, check the emailAddress-sendTime pair. If is correct, send the email, if not, return true and the email won't send and the job will finish.
Comprehensive Answer:
I now use my own custom DispatchableWithControl trait instead of the Dispatchable trait.
I call it like this:
$executeAt = Carbon::now()->addDays(7)->addHours(2)->addMinutes(17);
SomeJobThatWillSendAnEmailOrDoWhatever::dispatch($contactId, $executeAt);
namespace App\Jobs;
use App\Models\Tag;
use Carbon\Carbon;
use Exception;
use Illuminate\Bus\Queueable;
use Illuminate\Queue\SerializesModels;
use Illuminate\Queue\InteractsWithQueue;
use Illuminate\Contracts\Queue\ShouldQueue;
use Log;
class SomeJobThatWillSendAnEmailOrDoWhatever implements ShouldQueue {
use DispatchableWithControl,
InteractsWithQueue,
Queueable,
SerializesModels;
protected $contactId;
protected $executeAt;
/**
*
* #param string $contactId
* #param Carbon $executeAt
* #return void
*/
public function __construct($contactId, $executeAt) {
$this->contactId = $contactId;
$this->executeAt = $executeAt;
}
/**
* Execute the job.
*
* #return void
*/
public function handle() {
if ($this->checkWhetherShouldExecute($this->contactId, $this->executeAt)) {
//do stuff here
}
}
/**
* The job failed to process.
*
* #param Exception $exception
* #return void
*/
public function failed(Exception $exception) {
// Send user notification of failure, etc...
Log::error(static::class . ' failed: ' . $exception);
}
}
namespace App\Jobs;
use App\Models\Automation;
use Carbon\Carbon;
use Illuminate\Foundation\Bus\PendingDispatch;
use Log;
trait DispatchableWithControl {
use \Illuminate\Foundation\Bus\Dispatchable {//https://stackoverflow.com/questions/40299080/is-there-a-way-to-extend-trait-in-php
\Illuminate\Foundation\Bus\Dispatchable::dispatch as parentDispatch;
}
/**
* Dispatch the job with the given arguments.
*
* #return \Illuminate\Foundation\Bus\PendingDispatch
*/
public static function dispatch() {
$args = func_get_args();
if (count($args) < 2) {
$args[] = Carbon::now(TT::UTC); //if $executeAt wasn't provided, use 'now' (no delay)
}
list($contactId, $executeAt) = $args;
$newAutomationArray = [
'contact_id' => $contactId,
'job_class_name' => static::class,
'execute_at' => $executeAt->format(TT::MYSQL_DATETIME_FORMAT)
];
Log::debug(json_encode($newAutomationArray));
Automation::create($newAutomationArray);
$pendingDispatch = new PendingDispatch(new static(...$args));
return $pendingDispatch->delay($executeAt);
}
/**
* #param int $contactId
* #param Carbon $executeAt
* #return boolean
*/
public function checkWhetherShouldExecute($contactId, $executeAt) {
$conditionsToMatch = [
'contact_id' => $contactId,
'job_class_name' => static::class,
'execute_at' => $executeAt->format(TT::MYSQL_DATETIME_FORMAT)
];
Log::debug('checkWhetherShouldExecute ' . json_encode($conditionsToMatch));
$automation = Automation::where($conditionsToMatch)->first();
if ($automation) {
$automation->delete();
Log::debug('checkWhetherShouldExecute = true, so soft-deleted record.');
return true;
} else {
return false;
}
}
}
So, now I can look in my 'automations' table to see pending jobs, and I can delete (or soft-delete) any of those records if I want to prevent the job from executing.
Delete job by id.
$job = (new \App\Jobs\SendSms('test'))->delay(5);
$id = app(Dispatcher::class)->dispatch($job);
$res = \Illuminate\Support\Facades\Redis::connection()->zscan('queues:test_queue:delayed', 0, ['match' => '*' . $id . '*']);
$key = array_keys($res[1])[0];
\Illuminate\Support\Facades\Redis::connection()->zrem('queues:test_queue:delayed', $key);
Maybe instead of canceling it you can actually remove it from the Redis, from what Ive read from official docs about forget command on Redis and from Laravel official doc interacting with redis you can basically call any Redis command from the interface, if you could call the forget command and actually pass node_id which in this case I think it's that number you have in your image DEL 1517797158 I think you could achieve the "cancel".
hope this helps
$connection = null;
$default = 'default';
//For the delayed jobs
var_dump( \Queue::getRedis()->connection($connection)->zrange('queues:'.$default.':delayed' ,0, -1) );
//For the reserved jobs
var_dump( \Queue::getRedis()->connection($connection)->zrange('queues:'.$default.':reserved' ,0, -1) );
$connection is the Redis connection name which is null by default, and The $queue is the name of the queue / tube which is 'default' by default!
source : https://stackoverflow.com/a/42182586/6109499
One approach may be to have your job check to see if you've set a specific address/time to be canceled (deleted from queue). Setup a database table or cache a value forever with the address/time in an array. Then in your job's handle method check if anything has been marked for removal and compare it to the mailable's address/time it is processing:
public function handle()
{
if (Cache::has('items_to_remove')) {
$items = Cache::get('items_to_remove');
$removed = null;
foreach ($items as $item) {
if ($this->mail->to === $item['to'] && $this->mail->sendTime === $item['sendTime']) {
$removed = $item;
$this->delete();
break;
}
}
if (!is_null($removed)) {
$diff = array_diff($items, $removed);
Cache::set(['items_to_remove' => $diff]);
}
}
}
I highly recommend checking out the https://laravel.com/docs/master/redis (I run dev/master) but it shows you where they are headed. Most of it works flawlessly now.
Under laravel 8.65 you can just set various status's depending.
protected function listenForEvents()
{
$this->laravel['events']->listen(JobProcessing::class, function ($event) {
$this->writeOutput($event->job, 'starting');
});
$this->laravel['events']->listen(JobProcessed::class, function ($event) {
$this->writeOutput($event->job, 'success');
});
$this->laravel['events']->listen(JobFailed::class, function ($event) {
$this->writeOutput($event->job, 'failed');
$this->logFailedJob($event);
});
}
You can even do $this->canceled;
I highly recommend Muhammads Queues in action PDF. Trust me well worth the money if your using. queues for very important things.... especially with redis . At first TBH I was turned off a bit cause hes a Laravel employee and I thought he should just post things that are helpful but he goes into specific use cases that they do with forge and other items he does plus dives deep into the guts of how queue workers work whether its horizon or whatever. Total eyeopener for me.
Removing all queued jobs:
Redis::command('flushdb');
Using redis-cli I ran this command:
KEYS *queue*
on the Redis instance holding queued jobs,
then deleted whatever keys showed up in the response
DEL queues:default queues:default:reserved
Delete the job from the queue.
$this->delete();

Resources