I am using a symfony 4.4 with the form bundle and want to make some functional tests.
My form has multiple steps and I want to perform a complete form until the success message. But with csrf_protection:true I can't even get to page 2. If I disable it for the test environment I can get to page 2. If I dump my session at page 2, I can see, that it is empty. Here is an example of my test:
$client = static::createClient();
$client->request('GET', '/test');
$crawler = $client->submitForm('Next', ['name' => 'Max Mustermann']); // => lands on step2
$crawler = $client->submitForm('Next', ['email' => 'asdf#ase.de']); // => lands back on step 1 with emtpy form. So session is empty
Does anyone have an idea what is wrong here?
This page says, that is have to work, but it doesn't.
https://symfony.com/doc/4.4/components/http_foundation/session_testing.html#functional-testing
I suggest you to retrieve the form from the client's response then submit the form with the data, as example:
// Get the crawler
$crawler = $client->request('GET', '/test');
// Get the form
$form = $crawler->filter('form')->form();
// Fill the form. NB: double check your form structure/fields name
$form['name'] = 'Max Mustermann';
$form['email'] = 'asdf#ase.de';
// Submit the form.
$crawler = $this->client->submit($form);
$this->assertEquals(200, $this->client->getResponse()->getStatusCode());
Related
I added some functionality where if you edit an item that somebody edited in the meantime, you go to an "edit confirm" page where you can select which changes you want to overwrite.
How it's done:
if ($request->isMethod('GET')) {
$session->set('overwriteDate', $language->getUpdatedAt()?->format('Y-m-d H:i:s'));
$session->set('language_referer', $request->headers->get('referer'));
}
$form = $this->createForm(LanguageFormType::class, $language);
$form->handleRequest($request);
if ($form->isSubmitted() && $form->isValid()) {
var_dump($language->getUpdatedAt());
var_dump($session->get('overwriteDate'));
if ($session->get('overwriteDate') !== $language->getUpdatedAt()?->format('Y-m-d H:i:s')) {
$session->set('overwriteItem', $language);
return $this->redirectToRoute('languages_edit_confirm', ['id' => $language->getId()]);
} else {
$baseEntityService->save($language);
return $this->redirect($session->get('language_referer'));
}
}
return $this->render('admin/language/edit.html.twig', [
'language' => $language,
'form' => $form->createView()
]);
If the overwriteDate in the session doesn't match the updatedAt from the object, it will redirect to the edit confirm page. This all works fine in the browser (tested it manually a lot, never had an issue).
However, now I try to write a functional test for this, and it's inconsistent. This is the begin of the test:
$crawler1 = $this->client->request('GET', '/admin/languages');
$crawler1 = $this->filterTable($crawler1, $originalEnglishName);
$crawler1 = $this->client->click($crawler1->filter('a.edit-language')->link());
$crawler2 = $this->client->request('GET', '/admin/languages');
$crawler2 = $this->filterTable($crawler2, $originalEnglishName);
$crawler2 = $this->client->click($crawler2->filter('a.edit-language')->link());
$form = $crawler1->selectButton('saveLanguage')->form();
$form['language_form[abbreviation]'] = $firstEditAbbreviation;
$form['language_form[englishName]'] = $firstEditEnglishName;
$form['language_form[name]'] = $firstEditName;
$form['language_form[flag]'] = $firstEditFlag;
$form2 = $crawler2->selectButton('saveLanguage')->form();
$form2['language_form[abbreviation]'] = $secondEditAbbreviation;
$form2['language_form[englishName]'] = $secondEditEnglishName;
$form2['language_form[name]'] = $secondEditName;
$form2['language_form[flag]'] = $secondEditFlag;
$this->client->submit($form);
$this->client->submit($form2);
$crawler2 = $this->client->followRedirect();
$tbody = $crawler2->filter('table#difference-table tbody')->first();
After this I try to assert some stuff from $tbody however sometimes the test works fine but sometimes it gives errors because it acts like a normal edit and redirects to the homepage and the data from $tbody I search on doesn't exist.
Edit:
I tried changing the session stuff to putting it in the form as hidden input, but this gives the same issues.
You'll need to preserve the session cookie between then requests in your test(s).
The Symfony HTTPClient does not do this by default.
The HTTP client provided by this component is stateless but handling cookies requires a stateful storage (because responses can update cookies and they must be used for subsequent requests). That's why this component doesn't handle cookies automatically.
You can either handle cookies yourself using the Cookie HTTP header or use the BrowserKit component which provides this feature and integrates seamlessly with the HttpClient component.
Example usage:
// Request using the client
$crawler = $client->request('GET', '/');
// Get the cookie Jar
$cookieJar = $client->getCookieJar();
// Get the history
$history = $client->getHistory();
// Get a cookie by name
$sessionCookie = $cookieJar->get('PHPSESSID');
// create cookies and add to cookie jar
$cookie = new Cookie('PHPSESSID', 'XYZ', strtotime('+1 day'));
$cookieJar = new CookieJar();
$cookieJar->set($cookie);
// create a client and set the cookies
$client = new Client([], null, $cookieJar);
I try to download content of the web page with web scraping but on of the main problems is I can not bypass redirect of websites. for example when I try login to the website and submit the login form. I see waiting page and just waiting page.
but in browser after waiting page I redirect to profile page
I downloaded goutte and created my script but in submit form I have problem because when I submit wrongdoer password or username I will see incorrect password but when I enter correct username and password I will see waiting image to redirect
First Edit
according to the update response my code is
<?php
require_once 'vendor/autoload.php';
use Goutte\Client;
$client = new Client();
$url = 'https://egghead.io/users/sign_in';
$username = 'xxxx';
$password = 'xxxx';
$crawler = $client->request('GET', $url, [
'allow_redirects' => true
]);
$form = $crawler->selectButton('Sign In')->form();
$crawler = $client->submit($form, array('user[email]' => $username, 'user[password]' => $password));
$crawler->filter('body')->each(function ($node){
print $node->html();
});
Goutte will automatically follow redirects unless you tell it not to. You can customize the redirect behavior using the allow_redirects request option.
Set to true to enable normal redirects with a maximum number of 5
redirects. This is the default setting.
Set to false to disable redirects.
Pass an associative array containing the 'max' key to specify the
maximum number of redirects and optionally provide a 'strict' key
value to specify whether or not to use strict RFC compliant redirects
(meaning redirect POST requests with POST requests vs. doing what
most browsers do which is redirect POST requests with GET requests).
ref:
http://docs.guzzlephp.org/en/latest/quickstart.html#redirects
Update:
$crawler = $client->request('GET', 'http://egghead.io', [
'allow_redirects' => true
]);
$crawler = $client->click($crawler->selectLink('Sign in')->link());
$form = $crawler->selectButton('Sign in')->form();
$crawler = $client->submit($form, array('login' => 'fabpot', 'password' => 'xxxxxx'));
$crawler->filter('.flash-error')->each(function ($node) {
print $node->text()."\n";
});
how can i get additional $_GET Parameters in Yii2 even when prettyurl is enabled?
I need to read some feedback from the redirect Paypal-Link but i cant change the Link-Format on Paypal-Side to fit my Yii2 implementation:
http://example.com/controller/action?success=boolean&token=xyz
Thanks for your help!
you can use it
http://www.yiiframework.com/doc-2.0/guide-runtime-requests.html
for e.g
if you need to use $_GET['success'] or $_GET['token']
you must use it:
$request = Yii::$app->request;
$get = $request->get();
$success = $request->get('success');
$token= $request->get('token');
I figured out a way:
$url = parse_url(Yii::$app->request->url);
parse_str($url['query'], $array);
$success = $array['success'];
$token = $array['token'];
but it still doesnt seem like the right Yii2-ish way to solve it.
http://www.yiiframework.com/doc-2.0/yii-web-urlmanager.html this will help You. you can specify GET POST method for any controller,
[
'dashboard' => 'site/index',
'POST <controller:\w+>s' => '<controller>/create',
'<controller:\w+>s' => '<controller>/index',
'PUT <controller:\w+>/<id:\d+>' => '<controller>/update',
'DELETE <controller:\w+>/<id:\d+>' => '<controller>/delete',
'<controller:\w+>/<id:\d+>' => '<controller>/view',];
for example
'POST <controller:\w+>/<success:\w+>/<token:\w+>' => '<controller>/update',
Use the Request class.
http://www.yiiframework.com/doc-2.0/yii-web-request.html
print_r(Yii::$app->request->get()); returns all get variables in an array. It's like doing print_r($_GET); in straight php.
If you want a specific $_GET variable you access it as follows:
Yii::$app->request->get('varName');
In your case it would be:
$success = Yii::$app->request->get('success');
$token = Yii::$app->request->get('token');
Here is my successful returnUrl from paypal which yii2 handles beautifully with prettyurl enabled in the UrlManager.
http://multi2.myhost/subscription/subscription/success?token=EC-8GE539098H175763M
I have created a subscription module and a controller class called SubscriptionController and actions called actionSuccess and actionCancel.
The Paypal redirect only passes one parameter. The token. No need for two parameters. Your success and cancel returnurl's should be something like:
controller/action or subscription/success/
controller/action or subscription/cancel/
Your success returnUrl:
SubscriptionController/actionSuccess($token)
public actionSuccess($token)
{
}
and your cancel returnUrl:
SubscriptionController/actionCancel($token)
public actionCancel($token)
{
}
Using this method there is no need for a second parameter to handle the success and cancel variable since the separate Controller Actions solve this problem. Incorporate the 'success' in the name of the action which satisfies PrettyUrl Management.
You will have to modify both merchant preference returnUrls in the following code.
$merchantPreferences = new MerchantPreferences();
$merchantPreferences->setReturnUrl($model->merchant_preference_returnurl)
->setCancelUrl($model->merchant_preference_cancelurl)
There is therefore no need for:
$request = Yii::$app->request;
$get = $request->get();
$token = $request->get('token');
at the beginning of the action.
I have a form with two fields. After submitting it, there's a redirection to the same controller that displayed that form.
The method is as follows:
public function chartsAction($path, Request $request)
{
// display form
if(isset($_POST))
{
// handle form submission
}
}
When it's not a POST request, the URL is charts/.
When it's a POST request, the URL is for example charts/dateFrom/2013-08/dateTo/2014-02, so that's the $path argument of the method, and two variables depending on the form.
Now I want to test this. The problem is that the form redirection gives me the same, charts/ website. It just doesn't add $path parameter to the route. What's going on? My test method:
public function testChartsAction()
{
$crawler = $this->client->request('GET', '/charts', [], [], $this->credintials);
$form = $crawler->selectButton('dateChooser[Choose]')->form([
'dateChooser[dateFrom]' => '2014-08',
'dateChooser[dateTo]' => '2014-12',
]);
$this->client->submit($form);
$crawler = $this->client->followRedirect();
$this->assertTrue($this->client->getResponse()->isRedirect('/charts/dateFrom/2014-08/dateTo/2014-12'));
$this->assertTrue($this->client->getResponse()->isSuccessful());
}
First assert gives me false.
I think you should skip "isRedirect" assert. In the form handler, you can set a success flash message, that will be rendered in the response page:
$client = $this->makeClient(true);
$client->followRedirects();
$crawler = $client->request('GET', $url);
$form = $crawler->filter('#formID')->form();
$form->setValues(array(
"formsample[firstname]" => "test firstname",
"formsample[lastname]" => "test lastname"
));
$client->setServerParameter("HTTP_X-Requested-With" , "XMLHttpRequest");
$client->submit($form);
$response = $client->getResponse();
$this->assertContains('Your data has been saved!', $response->getContent());
I am using the php guzzle Client to grab the website, and then process it with the symfony 2.1 crawler
I am trying to access a form....for example this test form here
http://de.selfhtml.org/javascript/objekte/anzeige/forms_method.htm
$url = 'http://de.selfhtml.org/javascript/objekte/anzeige/forms_method.htm';
$client = new Client($url);
$request = $client->get();
$request->getCurlOptions()->set(CURLOPT_SSL_VERIFYHOST, false);
$request->getCurlOptions()->set(CURLOPT_SSL_VERIFYPEER, false);
$response = $request->send();
$body = $response->getBody(true);
$crawler = new Crawler($body);
$filter = $crawler->selectButton('submit')->form();
var_dump($filter);die();
But i get the exception:
The current node list is empty.
So i am kind of lost, on how to access the form
Try using Goutte, It is a screen scraping and web crawling library build on top of the tools that you are already using (Guzzle, Symfony2 Crawler). See the GitHub repo for more info.
Your code would look like this using Goutte
<?php
use Goutte\Client;
$url = 'http://de.selfhtml.org/javascript/objekte/anzeige/forms_method.htm';
$client = new Client();
$crawler = $client->request('GET', $url);
$form = $crawler->selectButton('submit')->form();
$crawler = $client->submit($form, array(
'username' => 'myuser', // assuming you are submitting a login form
'password' => 'P#S5'
));
var_dump($crawler->count());
echo $crawler->html();
echo $crawler->text();
If you really need to setup the CURL options you can do it this way:
<?php
$url = 'http://de.selfhtml.org/javascript/objekte/anzeige/forms_method.htm';
$client = new Client();
$guzzle = $client->getClient();
$guzzle->setConfig(
array(
'curl.CURLOPT_SSL_VERIFYHOST' => false,
'curl.CURLOPT_SSL_VERIFYPEER' => false,
));
$client->setClient($guzzle);
// ...
UPDATE:
When using the DomCrawler I often times get that same error. Most of the time is because I'm not selecting the correct element in the page, or because it doesn't exist. Try instead of using:
$crawler->selectButton('submit')->form();
do the following:
$form = $crawler->filter('#signin_button')->form();
Where you are using the filter method to get the element by id if it has one '#signin_button' or you could also get it by class '.signin_button'.
The filter method requires The CssSelector Component.
Also debug your form by printing out the HTML (echo $crawler->html();) and ensuring that you are actually on the right page.