How to override env variables in Laravel Dusk - laravel

Unfortunately config(['key' => 'newValue']) doesn't work in a Dusk setup (for overriding a config value), presumably because it would change the config of the system running the test rather than the experience of the headless browser that gets opened to execute the flow.
And sometimes I can see no way around needing to temporarily change an env value for a certain Dusk test.
E.g. temporarily set QUEUE_DRIVER=sync when usually it is 'dusk-connection', but in one particular test, I need to check for values in the 'jobs' tables in the DB.
Before upgrading to Laravel >=5.8 (and therefore newer versions of DotEnv), I was able to use this function called within a Dusk test before $this->browse(...:
/**
* Overrides any .env variables for Dusk tests. https://laracasts.com/discuss/channels/testing/how-to-change-env-variable-config-in-dusk-test
* The changes exist only for that one test because of tearDown.
* Remember that you need to be using `php artisan dusk` instead of `phpunit`.
* https://stackoverflow.com/questions/54407784/laravel-dusk-how-to-change-config-values-before-each-test-for-the-browser#comment103224655_54407784
*
* #param array $variables
*/
protected function overrideDuskEnv($variables = []) {
$path = self::DOT_ENV;
if (file_exists($path)) {
$contentToPrepend = '';
foreach ($variables as $key => $value) {// Convert all new parameters to expected format
$contentToPrepend .= $key . '="' . $value . '"' . PHP_EOL;
}
$originalFileContents = $this->envContents;
$comment = '# ==============================================' . PHP_EOL . '# VARIABLES ABOVE THIS LINE are from "' . __FUNCTION__ . '" function in DuskTestCase ( https://laracasts.com/discuss/channels/testing/how-to-change-env-variable-config-in-dusk-test )' . PHP_EOL;
file_put_contents($path, $contentToPrepend . $comment . $originalFileContents); //"If they are appended, it doesn't seem to take priority."
} else {
throw new \Exception('Could not find env file to override!');
}
}
I was able to call it like this: $this->overrideDuskEnv(['QUEUE_DRIVER' => 'sync']);
But in more recent Laravel versions, environment variables are immutable (see "Read-Only env Helper").
How can I achieve my goal, where Dusk uses .env.dusk.local for most tests but then for certain tests may differ slightly?

Finally after struggling with this problem for 10+ hours, I have a solution.
/**
* #param array $variables
*/
protected function overrideDuskEnv($variables = []) {
$path = self::DOT_ENV;
if (file_exists($path)) {
$contentToAppend = '';
foreach ($variables as $key => $value) {// Convert all new parameters to expected format
$contentToAppend .= $key . '="' . $value . '"' . PHP_EOL;
}
$originalFileContents = $this->envContents;
$comment = '# ==============================================' . PHP_EOL . '# VARIABLES BELOW THIS LINE are from "' . __FUNCTION__ . '" function in DuskTestCase ( https://laracasts.com/discuss/channels/testing/how-to-change-env-variable-config-in-dusk-test )' . PHP_EOL;
$this->baseCommand->consoleOutput('Appending to ' . $path . ': ' . $contentToAppend);
file_put_contents($path, $originalFileContents . $comment . $contentToAppend); //It used to be the case that "If they are appended [rather than prepended], it doesn't seem to take priority", but after the DotEnv upgrade in Laravel 5.8, it seems prepending doesn't work and appending does.
} else {
throw new \Exception('Could not find env file to override!');
}
}
Then in my setUp() function in my Dusk test class, I call:
$this->overrideDuskEnv([
'SIGNUP_FORM_POST_PATH' => \App\Helpers\Routes::SIGNUP,
'QUEUE_DRIVER' => \App\Helpers\ConfigConstants::DUSK_CONNECTION
]);
Then in each test function after the closing of $this->browse(function (Browser $browser)... and before assertions, I call:
config(['queue.default' => \App\Helpers\ConfigConstants::DUSK_CONNECTION]); //this does not affect the headless browser but IS probably necessary here so that assertQueued knows to pull from the queue that the headless browser was using.
The tricky part to understand with Dusk is that the environment variables (and therefore the config arrays) of the console process running the tests differ from those that get used by the headless browser (simulating what a real user would experience).
By the way, I had been so hopeful about approaches like this one, but they turned out to be complete wastes of time because DuskCommand is already calling overload to make the env variables mutable.

See here for a documented approach to overriding config([]) during a dusk test:
https://gist.github.com/wrabit/e01df16858505c395b7b0d271112a023

You can also use a seperate env for all the dusk tests.
It is also mentioned in the laravel docs here https://laravel.com/docs/8.x/dusk#environment-handling

This is a bit easy but if you put any wrong entry then it will burst.
public function store(Request $request) {
foreach ($request->all() as $key => $value) {
$_ENV[$key] = $value;
}
$x = '';
unset($_ENV['_token']);
foreach ($_ENV as $key => $value) {
$x .= $key . "=" . $value . "\n";
}
base_path('.env');
file_put_contents(base_path('.env'), $x);
return redirect()->back();
}
Using
<form class="grid gap-2" action="{{ route('admin.enviroment.store') }}" method="post">
<div>
<x-label for="GOOGLE_TAG" :value="__('GOOGLE_TAG')" />
<x-input id='GOOGLE_TAG' name="GOOGLE_TAG" :value="__($_ENV['GOOGLE_TAG'])" class="w-full rounded-md dark:bg-gray-700" type="text" required />
</div>
#csrf
<x-button class="ml-auto dark:bg-blue-900/90">
{{ __('Update GOOGLE TAG') }}
</x-button>
</form>

Related

Find a Session by ID Substring in Laravel 8

Im trying to find a session by PART of its ID in laravel.
I only have the first part of the id, and I need to find if the session has a key/value associated with it.
I have tried various forms of the below code. Its fairly simple but not sure if possible in laravel.
Note
Im not sure if this helps or not, but the laravel system is using file based sessions, not DB based sessions.
$value = 'do i have this value';
// Session::all()->whereLike('id','aVhN8u' . '%')->get();
foreach( Session::all()->where('id')->startsWith('aVhN8u') as $session)
{
if($session->has('key', $value)
{
// Do something interesting
}
}
Something like this should work.
use Illuminate\Support\Str;
$value = 'my cool value';
$prefix = 'aVhN8u';
$stored = session()->all();
$filtered = collect($stored)->filter(function ($session, $key) use ($prefix, $value) {
return Str::startsWith($key, $prefix) && $session == $value;
})->all();

How to test config file changes?

I wrote function which changes values in config/app.php using file_put_content.
public function updateConfig($path, $key, $value)
{
$content = file_get_contents($path);
$pattern = "/'" . $key . "' => '[0-9]+'/";
$replacement = "'" . $key . "' => '" . $value . "'";
$content = preg_replace($pattern, $replacement, $content);
file_put_contents($path, $content);
}
$path = base_path() . '/config/app.php';
$key = 'version_number';
$value = '10';
$service->updateConfig($path, $key, $value);
$this->assertEquals(config('app.version_number'), $value);
How can I test it with changing config file?
I presume you want to change the config then use it.
There is a way you can achieve this without editing the config file. Note that, in order to speed up Laravel, configurations are cached and you need to run php artisan clear:cache or manually delete cache files.
If you want to change config on the fly. Change it as shown before using it.
Config::set('app.version_number', 10);

Check if a view exists and do an #include in Laravel Blade

With Laravel Blade, is there an elegant way to check if a view exists before doing an #include?
For example I'm currently doing this:
#if(View::exists('some-view'))
#include('some-view')
#endif
Which gets quite cumbersome when 'some-view' is a long string with variables inside.
Ideally I'm looking for something like this:
#includeifexists('some-view')
Or to make #include just output an empty string if the view doesn't exist.
As an aside, I would also like to provide a set of views and the first one that exists is used, e.g.:
#includefirstthatexists(['first-view', 'second-view', 'third-view'])
And if none exist an empty string is output.
How would I go about doing this? Would I need to extend BladeCompiler or is there another way?
Had a similar issue. Turns out that from Laravel 5.3 there is an #includeIf blade directive for this purpose.
Simply do #includeIf('some-view')
I created this blade directive for including the first view that exists, and returns nothing if none of the views exist:
Blade::directive('includeFirstIfExists', function ($expression) {
// Strip parentheses
if (Str::startsWith($expression, '(')) {
$expression = substr($expression, 1, -1);
}
// Generate the string of code to render in the blade
$code = "<?php ";
$code .= "foreach ( {$expression} as \$view ) { ";
$code .= "if ( view()->exists(\$view) ) { ";
$code .= "echo view()->make(\$view, \Illuminate\Support\Arr::except(get_defined_vars(), array('__data', '__path')))->render(); ";
$code .= "break; } } ";
$code .= "echo ''; ";
$code .= "?>";
return $code;
});

Laravel 5 link_to_* with icon/html

Anyone figured out how to put HTML inside $title parameter of link_to_* helper functions?
I've been googling around and found some topics about it directly on laravel.io but there are only suggestions:
make your own helper (tried but in the end I use link_to_* or App::make('html')->linkRoute(...); example helpers.
generate links inside views and use URL facade
Use HTML facade to encode links in views (I think this is not secure, do not know why just feels like it is wrong).
To make myself clear I am asking how to make 1. suggestion work? (I tried helpers but result is the same when using L5 helper link_to_route('some.route', 'This is title with icon <i class="icon"></i>'); and helper that I made (code below). Where $icon = '<i class="some-icon"></i>';
if ( ! function_exists('link_to_route_icon'))
{
function link_to_route_icon($name, $title = null, $icon = null, $parameters = array(), $attributes = array())
{
return app('html')->linkRoute($name, $title . $icon, $parameters, $attributes);
}
}
both (my own helper or L5 helper) output the same result:
visible in browser: name<i class="fa-sort-alpha-asc"></i>
html code: name<i class="fa-sort-alpha-asc"></i>
Solution
if ( ! function_exists('link_to_route_icon'))
{
function link_to_route_icon($name, $title = null, $icon = null, $parameters = array(), $attributes = array())
{
$url = route($name, $parameters);
return '<a href="' . $url . '"' . app('html')->attributes($attributes) . '>' . htmlentities($title) . ' ' . $icon . '</a>';
}
}
Please note when using L5 and you want to use your own helper.php, you have to load it in composer.json
"psr-4": {
"App\\": "app/"
},
"files": [
"app/Helpers.php"
]
The problem is that you are using the linkRoute method which eventually calls htmlentities on the passed title string. You have to build the HTML yourself to make it work. Here's a generic example (feel free to optimize it more for the purpose of generating icon links)
function link_to_route_html($name, $html, $parameters = array(), $attributes = array())
{
$url = route($name, $parameters);
return '<a href="'.$url.'"'.app('html')->attributes($attributes).'>'.$html.'</a>';
}
And then you call it like this:
link_to_route_html('some.route', 'This is title with icon <i class="icon"></i>');

dynamic route in codeigniter don't work

i have problem in codeigniter routes.
i want change url from:http://example.com/test/news_details?id=17 to http://example.com/test/funy-url-maker or http://example.com/funy-url-maker
i can make $route dynamicly but it don't work.
my code:
$route[rawurlencode("'" . str_replace(' ', '-', $string)) . "'"] = "test/news_details?id=" . $id;
i print route and all route's will be add perfectly, but when i want to open http://example.com/test/funy-url-maker i will be redirect 404 page :(
any idea?
note:
when i use this as static it work perfectly.like:
$route[rawurlencode('funyNews')] = "test/b_news";
ok after all the questions and answers, I think youre looking for something like this:
foreach ($result as $row) {
$string = rawurlencode(str_replace(' ', '-', strtolower($row->subject)));
$route[$string] = "test/news_details/$row->id";
}
and your controller:
function news_details($id){
var_dump($id);
}

Resources