Behat mink test multiple pages - behat

I'm new to behat.
what I want to do is test a bunch of pages if they exist or not.
this is my example:
Scenario: Page "contact"
Given I am on "/contact"
Then I should see "contact"
in the footer you see a link called contact
so if there is some php error the it quits and I don't see the footer so behat fails.
but can I select multiple names like this:
Given I am on [/, /contact, /about-me] etc

You have many options but I'm just giving you two for now so for more, you can do your own research:
This is what many people would do:
Feature file:
Scenario: Checking pages and their content
Given I am on "/"
Then I should see "welcome home"
When I am on "/contact"
Then I should see "welcome to contact page"
When I am on "/about-me"
Then I should see "welcome to about me page"
When I am on "/whatever"
Then I should see "welcome to whatever page"
......
......
This is another option which verifies physical existence of the files:
Feature file:
Scenario: Checking pages and but not their content
Given I am on "/"
Then I should see "welcome home"
And the files below must exist in my project folder:
| file |
| /path/to/my/project/files/contact.tml |
| /path/to/my/project/files/about-me.tml |
| /path/to/my/project/files/whatever.tml |
In your FeatureContext file:
class FeatureContext extends MinkContext
{
/**
* #When /^the files below must exist in my project folder:$/
*/
public function theFilesBelowMustExistInMyProjectFoder(TableNode $table)
{
foreach ($table->getHash() as $file) {
if (file_exists($file) !== true) {
throw new Exception(sprintf('File "%s" not found', $file));
}
}
}
}

Related

Unable to execute login feature on chrome browser using Behat + Drupal 8

Just using Behat with drupal 8 and facing problem with login behat script to run. Below are the my code :
behat.yml
default:
suites:
default:
contexts:
- FeatureContext
- Drupal\DrupalExtension\Context\DrupalContext
- Drupal\DrupalExtension\Context\MinkContext
- Drupal\DrupalExtension\Context\MessageContext
- Drupal\DrupalExtension\Context\DrushContext
extensions:
Behat\MinkExtension:
goutte: ~
base_url: http://localhost:8080/drupal-dev/web
javascript_session: selenium2
browser_name: 'chrome'
selenium2: ~
Drupal\DrupalExtension:
blackbox: ~
api_driver: drupal
drupal:
drupal_root: web/
region_map:
navigation: ".navbar-header"
navigation_collapsible: "#navbar-collapse"
header: ".region-header"
highlighted: ".highlighted"
help: ".region-help"
content: ".region-content"
sidebar_first: ".region-sidebar-first"
sidebar_second: ".region-sidebar-second"
footer: ".footer"
login.feature
#javascript
Feature: login check
Scenario: user login is working
Given I am on "/user"
When I fill in "john" for "edit-name"
And I fill in "john for "edit-pass"
And I press "Log in"
Then I should see "Add content"
While i execute the vendor\bin\behat then it will open the chrome browser and redirect to user login page but failed from second step. See below error message :
#javascript
Feature: login check
Scenario: user login is working # features\bootstrap\login.feature:4
Given I am on "/user" # Drupal\DrupalExtension\Context\MinkContext::visit()
When I fill in "john" for "edit-name" # Drupal\DrupalExtension\Context\MinkContext::fillField()
Form field with id|name|label|value|placeholder "edit-name" not found. (Behat\Mink\Exception\ElementNotFoundException)
And I fill in "john" for "edit-pass" # Drupal\DrupalExtension\Context\MinkContext::fillField()
And I press "Log in" # Drupal\DrupalExtension\Context\MinkContext::pressButton()
Then I should see "Add content" # Drupal\DrupalExtension\Context\MinkContext::assertPageContainsText()
--- Failed scenarios:
features\bootstrap\login.feature:4
1 scenario (1 failed)
5 steps (1 passed, 1 failed, 3 skipped)
0m15.30s (17.82Mb)
And same feature file working fine and all steps passed if i replaced from #javasript to #api.
And same feature file working fine and all steps passed if i replaced from #javasript to #api.
A common problem with Selenium is that the automated browser takes more time to render the page than the headless browser (in your case #api, #goutte in Symfony), so Behat returns an error when it tries to find elements in a DOM that is not yet rendered.
The solution is to add a wait() before trying to fill the field. You can even query the driver type in your context, and trigger the wait only if the type is Selenium.
You need to create your own Context file to add your methods.
Create "MyFeatureContext" (or whatever), make it inherit the MinkContext class, add it to the contexts in your behat.yml file (instead of the MinkContext since you inherit from it anyway), and add the following methods :
public function wait(int $ms)
{
if ($this->getDriver() instanceof Selenium2Driver) {
return $this->getSession()->wait($ms);
}
}
/**
* Fills in form field with specified id|name|label|value
* Example: When I fill in "username" with: "bwayne"
* Example: And I fill in "bwayne" for "username"
*
* #When /^(?:|I )fill in "(?P<field>(?:[^"]|\\")*)" with "(?P<value>(?:[^"]|\\")*)"$/
* #When /^(?:|I )fill in "(?P<field>(?:[^"]|\\")*)" with:$/
* #When /^(?:|I )fill in "(?P<value>(?:[^"]|\\")*)" for "(?P<field>(?:[^"]|\\")*)"$/
*/
public function fillField($field, $value)
{
$this->wait(1000); # 1000ms or 1sec
parent::fillField($field, $value);
}
Note that the annotations are copied from MinkContext::fillField().
Thanks Olivier! Just resolved the issue via changing selenium server version from 3.1 to 2.53

Groovy URL getText() returns a PasswordAuthentication instance

I am trying to download the content of a password-protected Gerrit URL in a Jenkins pipeline Groovy script. HTTPBuilder is not accessible so I am using the URL class with Authenticator:
// To avoid pipline bailing out since data PasswordAuthentication is non-serializable
#NonCPS
def getToString(data) {
data.toString()
}
def fetchCommit(host, project, version) {
withCredentials([usernamePassword(credentialsId: 'my-credentials',
usernameVariable: 'user',
passwordVariable: 'PASSWORD')]) {
proj = java.net.URLEncoder.encode(project, 'UTF-8')
echo "Setting default authentication"
Authenticator.default = {
new PasswordAuthentication(env.user, env.PASSWORD as char[])
} as Authenticator
echo "https://${host}/a/projects/${proj}/commits/${version}"
url = "https://${host}/a/projects/${proj}/commits/${version}".toURL()
result = getToString(url.getText())
echo "${result}"
}
}
The result is a PasswordAuthentication instance, and not the expected data:
[Pipeline] echo
java.net.PasswordAuthentication#3938b0f1
I have been wrestling with this for a while. I have tried different ways to setup the authentication and reading the data, but those mostly end up with an exception. Using eachLine() on the url does not enter the closure at all. The job also exits far to quickly, giving the impression it not even tries to make a connection.
Refs:
https://kousenit.org/2012/06/07/password-authentication-using-groovy/

Symfony 4 login form with security and database users

I was a total noob on Symfony about a week ago and I thought I should just dive in Symfony 4. After a week of trying to solve the basic login problem, I believe the documentation is still missing some parts.
Now I've found a solution and I will share it along with some tips on what you might be doing wrong. First part of the answer is a list of suggestions, while the second part is the creation of a project with working login from scratch (supposing you already have composer installed and using a server like apache).
Part 1: Suggestions
403 Forbidden
Check the access_control: key in security.yaml. The order of the rules has impact, since no more than one rule will match each time. Keep most specific rules on top.
login_check
Make sure the form action sends you to the login_check path, or whatever you changed it to in security.yaml.
Also check that you have declared a route for the login_check path either in a controller or in routes.yaml.
input name
Symfony forms tend to encapsulate input names in an array, while it only expects them to be named _username and _password (you can change that in security.yaml) to count it as a login attempt. So inspect the inputs to make sure the name attributes are correct.
Part 2: Full Symfony 4 Login
Project Setup
Let's start by creating the project. Open cmd/terminal and go to the folder you want to contain the project folder.
cd .../MyProjects
composer create-project symfony/website-skeleton my-project
cd my-project
Now you have created a Symfony 4 website template in .../MyProjects/my-project and the cmd/terminal is in that path and will execute the rest of the commands properly.
Check in your .../MyProjects/my-project/public folder for a .htaccess file. If it exists you are fine, else run the following command.
composer require symfony/apache-pack
You can now find your site by visiting my-project.dev/public. If you want to remove this public path, you should do so using the .htaccess file, not moving the index.php.
Project Settings
1) Edit the DATABASE_URL key inside the .env file to correspond to your database settings.
2) Edit the config/packages/security.yaml file, so it looks like this:
security:
encoders:
App\Entity\User:
algorithm: bcrypt
providers:
user:
entity:
class: App\Entity\User
property: username
firewalls:
dev:
pattern: ^/(_(profiler|wdt)|css|images|js)/
security: false
main:
anonymous: true
provider: user
form_login:
#login_path: login
#check_path: login_check
default_target_path: homepage
#username_parameter: _username
#password_parameter: _password
logout:
#path: /logout
#target: /
access_control:
- { path: ^/login, roles: IS_AUTHENTICATED_ANONYMOUSLY }
- { path: ^/, roles: ROLE_USER }
- { path: ^/admin, roles: ROLE_ADMIN }
Some explanation:
App\Entity\User is the User entity you 'll create in a while to handle the login.
The user provider is just a name that needs to have a match in providers and firewalls.
The logout key must be declared if you want to allow the user to... well, logout.
Values in #comment reveal the default value we'll be using later on and act as a reference of what you are more likely to change.
User Entity
A user must have a role, but could have more. So let's build a UserRole Entity first for a ManyToMany relationship.
php bin/console make:entity userRole
All entities start with an id property. Add a role too.
php bin/console make:entity user
User needs the username, password and roles properties, but you can add more.
Let's edit the src/Entity/User.php file:
Add the UserInterface interface to your User class.
use Symfony\Component\Security\Core\User\UserInterface;
class User implements UserInterface
Edit the generated getRoles(), to make it return string array.
public function getRoles(): array
{
$roles = $this->roles->toArray();
foreach($roles as $k => $v) {
$roles[$k] = $v->getRole();
}
return $roles;
}
getSalt() and eraseCredentials() are functions to implement the UserInterface interface.
public function getSalt()
{
return null;
}
public function eraseCredentials()
{
}
Using the bcrypt algorithm (as we set in security.yaml) we don't need a salt. It generates automatically one. No, you don't store this salt anywhere and yes, it will produce different hash for the same password every time. But yes, it will work somehow (magic...).
If you need a different algorithm, that uses salt, you need to add a salt property on the User entity.
Homepage
For testing purposes we will create a homepage
php bin/console make:controller homepage
Edit the generated src/Controller/HomepageController.php file to change the root to /
#Route("/", name="homepage")
Login Controller
php bin/console make:controller login
Edit the generated src/Controller/LoginController.php file to make it like this:
<?php
namespace App\Controller;
use Symfony\Component\Routing\Annotation\Route;
use Symfony\Bundle\FrameworkBundle\Controller\Controller;
use Symfony\Component\Security\Http\Authentication\AuthenticationUtils;
use App\Form\LoginType;
class LoginController extends Controller
{
/**
* #Route("/login", name="login")
*/
public function index(AuthenticationUtils $authenticationUtils)
{
$error = $authenticationUtils->getLastAuthenticationError();
$lastUsername = $authenticationUtils->getLastUsername();
$form = $this->createForm(LoginType::class);
return $this->render('login/index.html.twig', [
'last_username' => $lastUsername,
'error' => $error,
'form' => $form->createView(),
]);
}
/**
* #Route("/logout", name="logout")
*/
public function logout() {}
/**
* #Route("/login_check", name="login_check")
*/
public function login_check() {}
}
Login Form
php bin/console make:form login
You don't have to associate it to the User entity.
Edit the generated src/Form/LoginType.php file to add this:
use Symfony\Component\Form\Extension\Core\Type\PasswordType;
use Symfony\Component\Form\Extension\Core\Type\SubmitType;
replace this:
$builder
->add('_username')
->add('_password', PasswordType::class)
->add('login', SubmitType::class, ['label' => 'Login'])
;
and add this function, to prevent Symfony from changing the input names you requested above by enclosing them in login[...]
public function getBlockPrefix() {}
Login Template
Edit the templates/login/index.html.twig file to add this code in the {% block body %} ... {% endblock %}:
{% if error %}
<div>{{ error.messageKey|trans(error.messageData, 'security') }}</div>
{% endif %}
{{ form_start(form, {'action': path('login_check'), 'method': 'POST'}) }}
{{ form_widget(form) }}
{{ form_end(form) }}
Database Generation
php bin/console doctrine:migrations:generate
php bin/console doctrine:migrations:migrate
This should have generated your database, according to your User and UserRole entities.
Generate Password
The following command will provide you with a hashed password you can directly insert into the database. The password will be hashed with the algorithm specified in security.yaml.
php bin/console security:encode-password my-password
Hope this helps!
Thank you very much, the documentation lacks some important things, but this works very good. For others, check the order of the access-control entries, because one misplaced entry may block the whole process.
access_control:
- { path: ^/login, roles: IS_AUTHENTICATED_ANONYMOUSLY }
- { path: ^/, roles: IS_AUTHENTICATED_ANONYMOUSLY }
- { path: ^/admin, roles: ROLE_ADMIN }
This was working, but this not
access_control:
- { path: ^/login, roles: IS_AUTHENTICATED_ANONYMOUSLY }
- { path: ^/admin, roles: ROLE_ADMIN }
- { path: ^/, roles: IS_AUTHENTICATED_ANONYMOUSLY }

Ember-auth signin test fails with json

I am having some issues with testing my signin/signout and related features of my app. The app works, but the test fail.
For testing, I use a QUnit with testem (I also tried teaspoon)
test "after signin, should redirect user back to previous page", ->
visit '/library'
fillIn '.signin-email', 'example#example.com'
fillIn '.signin-password', 'examplepass'
click '.signin-btn'
andThen ->
equal(testing().path(), 'library', "Should redirect back to library (was #{testing().path()})")
After running the test, I get a failure:
(screenshot here )
Authentication: visiting restricted page as non authenticated user: after signin, should redirect user back to previous page (2, 0, 2)Rerun544 ms
{user_id: 2, auth_token: wVveiyDLuXBXu69pQ2XQwg}
Source:
at Test.QUnitAdapter.Test.Adapter.extend.exception (http://localhost:7357/public/assets/application-aad0a1b2c887cc25124c361787446e83.js:50149:5)
at superWrapper [as exception] (http://localhost:7357/public/assets/application-aad0a1b2c887cc25124c361787446e83.js:13374:16)
at Object.onerror (http://localhost:7357/public/assets/application-aad0a1b2c887cc25124c361787446e83.js:50009:22)
at onerror (http://localhost:7357/public/assets/application-aad0a1b2c887cc25124c361787446e83.js:20453:16)
at EventTarget.trigger (http://localhost:7357/public/assets/application-aad0a1b2c887cc25124c361787446e83.js:20286:22)
at null.<anonymous> (http://localhost:7357/public/assets/application-aad0a1b2c887cc25124c361787446e83.js:20439:14)
at EventTarget.trigger (http://localhost:7357/public/assets/application-aad0a1b2c887cc25124c361787446e83.js:20286:22)
at http://localhost:7357/public/assets/application-aad0a1b2c887cc25124c361787446e83.js:20588:17
Should redirect back to library (was signin)
Expected:
"library"
Result:
"signin"
Diff:
"library" "signin"
Source:
at http://localhost:7357/public/assets/spec/javascripts/integration/authentication_pages_spec.js.js:22:14
at andThen (http://localhost:7357/public/assets/application-aad0a1b2c887cc25124c361787446e83.js:50258:20)
at http://localhost:7357/public/assets/application-aad0a1b2c887cc25124c361787446e83.js:49817:21
at isolate (http://localhost:7357/public/assets/application-aad0a1b2c887cc25124c361787446e83.js:49989:14)
at http://localhost:7357/public/assets/application-aad0a1b2c887cc25124c361787446e83.js:49972:12
at invokeCallback (http://localhost:7357/public/assets/application-aad0a1b2c887cc25124c361787446e83.js:20463:19)
at null.<anonymous> (http://localhost:7357/public/assets/application-aad0a1b2c887cc25124c361787446e83.js:20513:11)
Also, auth.coffee:
App.Auth = Em.Auth.extend
request: 'jquery'
response: 'json'
strategy: 'token'
session: 'cookie'
modules: [
'emberData',
'authRedirectable',
'actionRedirectable'
]
signInEndPoint: '/signin'
signOutEndPoint: '/signout'
tokenKey: 'auth_token'
tokenIdKey: 'user_id'
tokenLocation: 'param'
emberData:
userModel: 'user' # create user model on login
authRedirectable:
route: 'signin'
actionRedirectable:
signInRoute: 'library'
# signInSmart: true
# signInBlacklist: ['signin']
signOutRoute: 'index'
I am unable to find the source of the error, so maybe it is something to do with ember-auth. Any ideas would be very appreciated.
Update 1 [Jan 4th]:
I've written an additional test, which passes only halfway. The test is simpler than the previous in that it does not check a redirect, but only checks that the user name appears in the UI after signin.
test "after signin, TEST", ->
visit '/library'
fillIn '.signin-email', 'user#example.com'
fillIn '.signin-password', 'foobargaz'
click '.signin-btn'
andThen ->
ok exists('.menu-signout'), "signout button exists"
The assertions passes, but I get an additional error reporting the returned JSON as seen in this screenshot. The screenshot basically shows:
[Fail] {user_id: 2, auth_token: wVveiyDLuXBXu69pQ2XQwg}
[Pass] signout button exists
Additionally, I've also run the tests by mocking the ajax requests with mockjax, but with the same failure.
Third, I should note that I had to patch "ember-auth-request-jquery.js" to make it work with ember testing as suggested here
I'm pretty sure you're failing to wait on the first visit to happen, so here's how I read it (I'm no CS person)
You're telling Ember to go to library
Before being sure it's finished navigating you're trying to fill in 2 fields and click a button (all of which probably doesn't exist)
then you check to see if it's library, but while waiting after you thought you clicked, really the page finishes rendering the login page from the visit
Here's what js2coffe says it'd kind of look like (my main point is the then after the visit).
test "after signin, should redirect user back to previous page", ->
visit("/library").then ->
fillIn ".signin-email", "example#example.com"
fillIn ".signin-password", "examplepass"
click(".signin-btn").then ->
equal testing().path(), "library", "Should redirect back to library (was " + (testing().path()) + ")"
Update 1/4: Documentation changed on me
Now we move to educated guess time. Looking through the Ember-auth code it might not be creating any timers/promises that Ember is aware of, in affect Ember thinks it's finished the signin process immediately. So the click promise is resolved immediately and you run your test immediately (andThen waits on the global testing promise to resolve). To test the theory you can do some terrible timeout and see if it does indeed redirect after some time
test "after signin, should redirect user back to previous page", ->
visit "/library"
fillIn ".signin-email", "example#example.com"
fillIn ".signin-password", "examplepass"
click ".signin-btn"
stop()
Ember.run.later this, (->
start()
equal testing().path(), "library", "Should redirect back to library (was " + (testing().path()) + ")"
), 5000
It turns out my coffeescript was not the best in the world.
The module function in QUnit should NOT compile to:
module('Authentication: visiting restricted page as non authenticated user', function() {
return setup(function() {
return Em.run(App, App.advanceReadiness);
});
});
but to:
module('Authentication: visiting restricted page as non authenticated user', {
setup: function() {
Ember.run(App, App.advanceReadiness);
},
// This is also new
teardown: function() {
App.reset();
}
});
Additionally, in my spec_helper.coffee file I had something like this:
QUnit.testStart(function() {
// FIXME: this below made it fail every time
// Ember.run(function() {
// return App.reset();
// });
Ember.testing = true;
});
QUnit.testDone(function() {
Ember.testing = false;
});
QUnit.done(function() {
return Ember.run(function() {
return App.reset();
});
});
which seems to have caused some issues, so I just deleted it and the tests now pass.

Symfony and uploadify

I want to use uploadify with Symfony 1.4, but so far I couldn't.
Uploadify loads correctly, I choose my files, it says that the files were successfully uploaded, but the are nowhere.
(I'm doing this on localhost)
Is there anybody who met this problem before?
Thanks, Tom
$file = $request->getParameter('file');
$filename = sha1($file->getOriginalName()).$file->getExtension($file->getOriginalExtension());
$file->save(sfConfig::get('sf_upload_dir').'/'.$filename);
in my project session stored in cookies so I found solution by create extra session storage class
class MySessionStorage extends sfSessionStorage
{
public function initialize($options = null)
{
$request = sfContext::getInstance()->getRequest();
// work-around for uploadify
if ($request->getParameter('uploadify') == "onUpload")
{
$sessionName = $options["session_name"];
if($value = $request->getParameter($sessionName))
{
session_name($sessionName);
session_id($value);
}
}
parent::initialize($options);
}
}
then changed factories.yml to
all:
storage:
class: MySessionStorage
and then "uploader" param will like this
uploader : '<?php echo url_for("attachments/upload?uploadify=onUpload&" . session_name() . "=" . session_id(), true)?>',
I can only guess that it's because you're trying to upload while logged into a system, but flash does not inherit session data from the browser, this means you will always be denied permission to whatever function you are trying to access since symfony thinks you're not logged in.
So you need to manually set variables in order for flash to use the same login session as the browser:
jQuery Code (needs to be in a php file, will not work in a js file):
$('#file_upload').uploadify({
.... config here
'scriptData': { '<?php echo session_name() ?>': '<?php echo session_id() ?>' }
});