Laravel dusk with browserstack to run tests on multiple devices and browsers - laravel

I am using this ("browserstack/browserstack-local": "^1.1") package to run dusk tests on BrowserStack. Now the requirement is to run tests on multiple and different devices with different browsers. Currently, I am following this approach to run tests.
private function browserStackCaps($local_identifier)
{
return [
'project' => config('app.name'),
'browserstack.local' => 'true',
'browser' => env('BROWSER'),
'device' => env('DEVICE'),
'acceptSslCert' => true,
'resolution' => '1920x1080'
];
}
The drawback of this approach is I have to change the device name and browser name in the .env file every time I need to run tests on a different device/browser. Is there any way I can run tests on the provided array? The array that contains devices and browser information.

I know this is old, but I found this page while searching for a solution. I ended up building one myself that would probably meet your use-case. The biggest hurdle that I had was $this->browse() in a normal Dusk test was using a single instance of Laravel\Dusk\Browser and the new capabilities were not being pulled in. This implementation adds a function called performTest to the DuskTestCase.php file. This function loops through a set of capabilities and instantiates a new instance of Laravel\Dusk\Browser for each test. This function works similarly to the existing browse function in Laravel Dusk. You call performTest by passing it a callable that accepts a single parameter which is an instance of Laravel\Dusk\Browser
Dusk Test Case
<?php
namespace Tests;
use Laravel\Dusk\TestCase as BaseTestCase;
use Facebook\WebDriver\Remote\RemoteWebDriver;
use Facebook\WebDriver\Remote\DesiredCapabilities;
abstract class DuskTestCase extends BaseTestCase
{
use CreatesApplication;
protected array $capabilities;
private const BROWSERS = [
'ios_14_iphone_xs_safari' => [
"os_version" => "14",
"device" => "iPhone XS",
"real_mobile" => "true",
"browserstack.local" => "true",
'acceptSslCerts' => 'true'
],
'mac_osx_catalina_safari' => [
"os" => "OS X",
"os_version" => "Catalina",
"browser" => "Safari",
"browser_version" => "13.0",
"browserstack.local" => "true",
"browserstack.selenium_version" => "3.14.0",
"resolution" => "1920x1080",
'acceptSslCerts' => 'true',
]
];
/**
* Create the RemoteWebDriver instance.
*
* #return \Facebook\WebDriver\Remote\RemoteWebDriver
*/
protected function driver()
{
$browserStackConnectionUrl = config('browserstack.connection_url');
return RemoteWebDriver::create(
$browserStackConnectionUrl, $this->capabilities
);
}
protected function performTest(Callable $test){
foreach(self::BROWSERS as $browserName => $capabilitySet){
try {
$this->capabilities = $capabilitySet;
$browser = $this->newBrowser($this->driver());
$test($browser);
$browser->quit();
fprintf(STDOUT, "\e[0;32m√ {$browserName}\r\n");
}
catch(\Exception $exception){
fprintf(STDOUT, "\e[0;31mX {$browserName}\r\n");
throw $exception;
}
}
}
}
Example Test
<?php
namespace Tests\Browser;
use Tests\DuskTestCase;
use Laravel\Dusk\Browser;
class ExampleTest extends DuskTestCase
{
public function testExample()
{
$this->performTest(function(Browser $browser){
$browser->visit('/')
->assertDontSee('Foobar');
});
}
}
config/browserstack.php
<?php
return [
'connection_url' => env('BROWSERSTACK_CONNECTION_URL')
];

you can implement this at your end. Fetch the list of Browsers and Devices you want to execute your tests on using the REST API and use the same.
REST API to be used:
curl -u "username:password"
https://api.browserstack.com/automate/browsers.json
Read more on this here:
https://www.browserstack.com/docs/automate/api-reference/selenium/browser#get-browser-list

Related

Catch external requests in Laravel Telescope

Is it possible to catch external requests in Laravel Telescope. I'm new to telescope and I've done my research but I couldn't find any blog/article that mentioned this except this but it didn't work for me
I've Installed telescope on my app according to the documentation, Iv'e created a new watcher called GuzzleRequestWatcher and registered it under config/telescope.php, I've also created a test route that sends an http::post message to this. Telescope is catching my API request and recording it under requests as shown in the screenshot but I need it to see the URL the request is hitting not only the route for example rather than showing in Path '/api/v1/guzzle-test' I need it to show the URL I'm requesting 'http://httpbin.org/anything'.screenshot
<?php
declare(strict_types=1);
namespace App\Telescope\Watchers;
use Closure;
use GuzzleHttp\Client;
use GuzzleHttp\TransferStats;
use Illuminate\Foundation\Application;
use Illuminate\Support\Facades\Log;
use Laravel\Telescope\IncomingEntry;
use Laravel\Telescope\Telescope;
use Laravel\Telescope\Watchers\FetchesStackTrace;
use Laravel\Telescope\Watchers\Watcher;
final class GuzzleRequestWatcher extends Watcher
{
use FetchesStackTrace;
public function register($app)
{
$app->bind(Client::class, $this->buildClient($app));
}
private function buildClient(Application $app): Closure
{
return static function (Application $app): Client {
$config = $app['config']['guzzle'] ?? [];
if (Telescope::isRecording()) {
$config['on_stats'] = function (TransferStats $stats) {
$caller = $this->getCallerFromStackTrace();
Telescope::recordQuery(
IncomingEntry::make([
'connection' => 'guzzle',
'bindings' => [],
'sql' => (string) $stats->getEffectiveUri(),
'time' => number_format(
$stats->getTransferTime() * 1000,
2,
''
),
'slow' => $stats->getTransferTime() > 1,
'file' => $caller['file'],
'line' => $caller['line'],
'hash' => md5((string) $stats->getEffectiveUri()),
])
);
};
}
return new Client(
$config
);
};
}
}

PHP Unit Test Closure functions

I was performing unit test in my application, and some of the functions include closure functions like below:
<?php
namespace JobProgress\Transformers;
use League\Fractal\TransformerAbstract;
class CustomersTransformer extends TransformerAbstract {
public function includesCreatedBy($customer) {
$user = $customer->createdBy;
if($user) {
return $this->item($user, function($user){
\Log::info('unit test');
return [
'id' => (int)$user->id,
'first_name' => $user->first_name,
'last_name' => $user->last_name,
'full_name' => $user->full_name,
'full_name_mobile' => $user->full_name_mobile,
'company_name' => $user->company_name,
];
});
}
}
}
Note : I have extended TransformerAbstract class of Fractal and using the item function as $this->item from the TransformerAbstract class.
Here is how i am executing my test:
public function testIncludeDeletedBy() {
$customer = factory(Customer::class)->create();
$include = $this->transformer->includesCreatedBy($customer);
$object = $include->getData()->first();
$this->assertInstanceOf(User::class, $object);
}
My unit test is not executing the code that i have written in closure function.
Like as i have mentioned in above code, i have added some logs but my unit test is not executing that portion of the code.
Can anyone please help me
This might be related to the implementation detail of the $this->item() method. You may expect the closure to be executed while as we can see, the closure only needs to be parsed and then passed to the method as a parameter.
The code shows nothing that it is actually executed and therefore you should not expect this to be as it is an implementation detail not under your test.
You could de-anonymize the closure and call it directly to be able to test it as a unit.
So you have one test for the if-clause in the existing method and one test which allows you to run (not only parse) the code you currently have as an anonymous function / closure.
This is sometimes called a test-point and can bring you up speed. The smaller the units you have are, the easier it is to test them.
<?php
namespace JobProgress\Transformers;
use League\Fractal\TransformerAbstract;
class CustomersTransformer extends TransformerAbstract {
public function includesCreatedBy($customer) {
$user = $customer->createdBy;
if($user) {
return $this->item($user, $this->transformImplementation(...));
}
}
public function transformImplementation(object $user) {
\Log::info('unit test');
return [
'id' => (int)$user->id,
'first_name' => $user->first_name,
'last_name' => $user->last_name,
'full_name' => $user->full_name,
'full_name_mobile' => $user->full_name_mobile,
'company_name' => $user->company_name,
];
}
}
For the three dots (...), this is PHP 8.1 syntax, compare PHP RFC: First-class callable syntax for alternative PHP syntax before 8.1 for callables, e.g.:
Closure::fromCallable([$this, 'transformImplementation'])

PHPUnit test can't find factory

I have a unit test that uses a JobFactory and ClientFactory.
<?php
namespace Tests\Unit;
use PHPUnit\Framework\TestCase;
class JobTest extends TestCase
{
/** #test */
function can_display_job_details()
{
$job = create('App\Job', [
'job-number' => 9999,
'site' => 'Homeville',
'client_id' => function(){
return create('App\Client', [
'name' => 'ACME'
])->id;
},
]);
$details = $job->job_details;
$this->assertEquals('EPS-9999-Homeville-RES', $details);
}
}
When I run the test I get this error
InvalidArgumentException : Unable to locate factory with name [default] [App\Job].
I use the factory App\Job (JobFactory) in my feature test with no issues.
I use PhpStorm's built in PHPUnit testing function.
I run create and make through a little test helper
<?php
function create($class, $attributes = [], $times = null)
{
return factory($class, $times)->create($attributes);
}
function make($class, $attributes = [], $times = null)
{
return factory($class, $times)->make($attributes);
}
Not sure what I'm doing wrong but I can't seem to get to the bottom of it.
Any help greatly appreciated.
You are extending the wrong TestCase class.
Change:
use PHPUnit\Framework\TestCase;
to:
use Tests\TestCase;
Here you can find an example.

How to mock internal calls with mockery

I try to mock a method of my service with Mockery lib. It works if I call that method from the test's context. But if I call it from another method (for example, it calls from another tested method) - it returns original data from implementation, but not from mock. What I'm doing wrong?
The example is below.
I added contract's because of my real implementation uses it. I don't think the problem is related to interfaces.
app/Contracts/TransactionsServiceContract.php
namespace App\Contracts;
interface TransactionsServiceContract
{
public function getAllRequests(): array;
public function getRequests(array $necessaryFields): array;
}
app/Services/TransactionsService.php
namespace App\Services;
use App\Contracts\TransactionsServiceContract;
class TransactionsService implements TransactionsServiceContract
{
public function getAllRequests(): array
{
return [
'foo' => [
'metric' => 'foo',
],
'bar' => [
'metric' => 'bar',
],
'another' => [
'metric' => [
// Some fields
],
],
];
}
public function getRequests(array $necessaryFields): array
{
// dd($this->getAllRequests()); // -> for the test context it returns original value (above's one)
return collect($this->getAllRequests())->only($necessaryFields)
->map(function (array $metric) {
return $metric['formula'];
})
->toArray();
}
}
tests/Feature/TransactionsServiceTest.php
namespace Tests\Feature;
use App\Contracts\TransactionsServiceContract;
use Tests\TestCase;
class TransactionsServiceTest extends TestCase
{
/** #var TransactionsServiceContract */
private $_transactionsService;
public function setUp()
{
parent::setUp();
$requests = [
'test1' => [
'metric' => 'test 1',
],
'test2' => [
'metric' => 'test 2',
],
];
$this->_transactionsService = \Mockery::mock(app()->make(TransactionsServiceContract::class))->makePartial();
$this->_transactionsService->shouldReceive('getAllRequests')->andReturn($requests);
}
public function testInternalCall()
{
$directCall = $this->_transactionsService->getAllRequests(); // returns array "requests" from the setUp method
dump($directCall);
$internalCall = $this->_transactionsService->getRequests(['test1']);
dd($internalCall); // if we call getAllRequests into getRequests, but not from test's context, we get original array from real implementation, but not test's mock
}
}
Versions of libs/frameworks:
Laravel: v5.7.19
PHPUnit: 7.5.1
Mockery: 1.2.0
Thanks for attention. Happy new year! :)
When you call \Mockery::mock(app()->make(TransactionsServiceContract::class))->makePartial(); in your setUp method, you're not really replacing the implementation existing in the app container. Laravel's container provides you with the bind method, to do that (the documentation for that). Besides you wouldn't replace an interface with a mock, as interfaces don't do anything per definition.
So in fact you would do something like:
app()->bind('\App\TransactionsService', $mockedTransactionService);
Note this will only work if your code gets an instance of the TransactionService by injection or resolving, not by calling new TransactionService.

How do I configure a custom form element with zend expressive

In my ZF2 Application, I had several custom form Elements with an injected database Adapter. I put the configuration in the module.php file with a method, like this:
public function getFormElementConfig()
{
return array(
'factories' => [
'dksCodeElementSelect' => function($container)
{
$db = $container->get(AdapterInterface::class);
$elemnt = new Form\Element\Select\DksCodeElementSelect();
$elemnt->setDb($db);
return $elemnt;
},
)
}
How can I configure custom form elements within a zend-expressive application?
Form class calls elements via 'FormElementManger'. and FormElementManager reads 'form_elements' key from config. It's basicly a container (service manager) so you have to configure it same as container (factories, invokables, aliases etc). Also, you have to register Zend/Form module to container. I didn't try it but has to be ths way;
(ps: it's too late here, if it doesn't work let me know so i can put a working example.)
class MyModule\ConfigProvider {
public function __invoke()
{
return [
'dependencies' => $this->getDependencies(),
'templates' => $this->getTemplates(),
'form_elements => [
/* this is where you will put configuration */
'factories' => [
MyElement::class => MyElementFactory::class,
]
]
];
}
}

Resources