I am using Watir-Webdriver along with the PageObject gem to automate tests.
I have a PageObject defined like this:
class Page
include PageObject
div(:loading_indicator, :class => 'loading_indicator')
div(:display_detail, :id => 'log_module_detail')
# large number of links like this:
link(:input1) { display_detail_element.link_element(:id => 'detailed_input1') }
link(:input2) { display_detail_element.link_element(:id => 'detailed_input2') }
# input3, input4, etc..
def wait_on_loading_indicator
loading_indicator_element.when_not_present # wait till loading indicator is gone
end
end
When I press input1 the loading_indicator becomes present and I want to wait for it to disappear before continuing.
Of course I could just define a method like this:
def click_input1
input1
wait_on_loading_indicator
end
But then I would have to define a method like that for every link...
Ideally I would want something like this:
link(:input1) { display_detail_element.link_element(:id => 'detailed_input1'); wait_on_loading_indicator }
But that doesn't work. Is there some easy/clean way to do this?
One approach would be to create an accessor that defines the normal link methods and override the click method to also call the wait_on_loading_indicator method.
The page object would be:
class MyPage
include PageObject
def self.detailed_input(name, identifier={:index => 0}, &block)
link(name, identifier, &block)
define_method(name) do
send("#{name}_element").click
wait_on_loading_indicator
end
end
div(:loading_indicator, :class => 'loading_indicator')
div(:display_detail, :id => 'log_module_detail')
detailed_input(:input1) { display_detail_element.link_element(:id => 'detailed_input1') }
detailed_input(:input2) { display_detail_element.link_element(:id => 'detailed_input2') }
def wait_on_loading_indicator
loading_indicator_element.when_not_present
end
end
Notice that the detailed_input class method:
1. Calls the standard link accessor method to setup the standard link methods.
2. Redefines the method that clicks the link to also call the wait_on_loading_indicator method.
Links that need to wait for the indicator would use the custom accessor method rather than standard link method. In other words, they are declared as:
detailed_input(:input1) { display_detail_element.link_element(:id => 'detailed_input1') }
detailed_input(:input2) { display_detail_element.link_element(:id => 'detailed_input2') }
You can see this work in the page:
<html>
<head>
<title>wait test</title>
<script type="text/javascript" charset="utf-8">
function loading() {
var e = document.getElementById("loading")
e.style.display = "block";
setTimeout(function() {
e.parentNode.removeChild(e);
}, 2000);
}
</script>
</head>
<body>
<div id="loading" class="loading_indicator" style="display:none;">loading</div>
<div id="log_module_detail">
detailed_input1
</div>
</body>
</html>
When the following code is run against the page object:
page = MyPage.new(browser)
puts page.loading_indicator_element.visible?
#=> false
page.input1
puts page.loading_indicator_element.visible?
#=> false (this would return `true` without the wait method)
Related
I have the invisible reCAPTCHA set up, but it doesn't seem to want to call my callback function. My form looks like:
<form id='ContactAgentForm' name='ContactAgentForm' class='custom-form-widget-form standard_form' action='contact_agent' listing_id=1233445>
...
<div class='field captcha-field recaptcha_field' >
<div id='g-recaptcha-div' class="g-recaptcha" ></div>
</div>
...
<div class="field button-field">
<button class="button button-primary"><span>Send</span></button>
<a class="button button-cancel btn-close" href="#cancel"><span>Cancel</span></a>
</div>
</form>
In the javascript, I want to handle the fact that there might be multiple forms on the page, so I create a list of all the forms. For each form, I attach/render the reCAPTCHA logic, attaching my callback with the form passed as a parameter:
<script>
var $form_list = jQuery("form.custom-form-widget-form");
var onFormPageSubmit = function(token, $form ) {
console.log("Got here! ", token );
var field = $form.find('.g-recaptcha-response')[0];
field.value = token;
$form[0].submit();
};
var onloadCallback = function() {
$form_list.each( function() {
var $form = jQuery(this);
var $recaptcha = $form.find( ".g-recaptcha" );
if ( $recaptcha.length )
{
var recaptchaId = grecaptcha.render($recaptcha[0], {
'callback': function (token) { onFormPageSubmit(token, $form); },
'sitekey': "{$captcha_config.invisible_captcha_site_key}",
'size': 'invisible',
'badge': 'inline'
});
$form.data("recaptchaid", recaptchaId);
}
});
};
</script>
And just below that, I load the recaptcha/api.js file:
<script src="https://www.google.com/recaptcha/api.js?render=explicit&onload=onloadCallback"></script>
With some judicial 'console.log' statements, we get through all of the code EXCEPT for the callback (onFormPageSubmit). The "protected by reCAPTCHA" logo is there, but it seems that the form is just submitted, ignoring the reCAPTCHA call altogether.
All help appreciated.
Somewhere along the line, the validation function for the form was lost (it's in another file). The validation function was attached to the button, and it executed something like this:
$submit_button.on( 'click', function( event ) {
event.preventDefault();
// get the recaptchaid from form data
var $recaptcha_id = $form.data( "recaptchaid" );
if ( $recaptcha_id != undefined )
{
grecaptcha.execute($recaptcha_id);
}
} );
The "grecaptcha.execute" is the important thing - this is what triggers the actual reCAPTCHA call.
I want to embed a contact form in multiple places on my website.
I developed a contact form in a contact() function within my MessagesController.php:
// MessagesController.php
public function contact()
{
$this->set('title', 'Contact');
$message = $this->Messages->newEntity();
... // shortened for brevity
$this->set(compact('message'));
$this->set('_serialize', ['message']);
}
I loaded the CSRF component in the initialize() function of the AppController.php:
// AppController.php
public function initialize()
{
parent::initialize();
$this->loadComponent('Csrf');
... // shortened for brevity
}
The form is rendered with a contact.ctp and it works fine.
I followed CakePHP's cookbook which suggests using requestAction() within an element, then echoing the element where I want it:
// contact_form.ctp
<?php
echo $this->requestAction(
['controller' => 'Messages', 'action' => 'contact']
);
?>
And:
// home.ctp
<?= $this->element('contact_form'); ?>
The problem is that the form is rendered fine, but the CSRF hidden field is missing. It should be automatically added to the form since the CSRF component is called in the AppController.php.
I guess either using an element with a requestAction() isn't the solution for this particular case, or I am doing something wrong.
Any ideas? Thanks in advance for the input!
Request parameters need to be passed manually
requestAction() uses a new \Cake\Network\Request instance, and it doesn't pass the _Token and _csrf parameters to it, so that's why things break.
While you could pass them yourself via the $extra argument, like
$this->requestAction(
['controller' => 'Messages', 'action' => 'contact'],
[
'_Token' => $this->request->param('_Token'),
'_csrf' => $this->request->param('_csrf')
]
);
Use a cell instead
I would suggest using a cell instead, which is way more lightweight than requesting an action, also it operates in the current request and thus will work with the CSRF component out of the box.
You'd pretty much just need to copy your controller action code (as far as the code is concerned that you are showing), and add a loadModel() call to load the Messages table, something like
src/View/Cell/ContactFormCell.php
namespace App\View\Cell;
use Cake\View\Cell;
class ContactFormCell extends Cell
{
public function display()
{
$this->loadModel('Messages');
$this->set('title', 'Contact');
$message = $this->Messages->newEntity();
// ... shortened for brevity
$this->set(compact('message'));
$this->set('_serialize', ['message']);
}
}
Create the form in the corresponding cell template
src/Template/Cell/ContactForm/display.ctp
<?php
echo $this->Form->create(
/* ... */,
// The URL needs to be set explicitly, as the form is being
// created in the context of the current request
['url' => ['controller' => 'Messages', 'action' => 'contact']]
);
// ...
And then wherever you want to place the form, just use <?= $this->cell('ContactForm') ?>.
See also
API > \Cake\Routing\RequestActionTrait::requestAction()
Cookbook > Views > Cells
So I have form (typical articles and comments example) for comments at bottom of page. If validation fails I display validation errors.
Thats my comments controller code:
class CommentsController < ApplicationController
before_action :authenticate_admin!, only: [:destroy]
expose(:article)
expose(:comment, attributes: :comment_params)
expose(:reply) { Reply.new }
def create
comment.article = article
if verify_recaptcha(model: comment, message: t('captcha_verification_error')) && comment.save
flash[:comment_notice] = t('comment_created_successfully')
redirect_to article_path(article) + '#comments'
else
flash[:comment_errors] = comment.errors.full_messages
render 'articles/show'
end
end
def destroy
comment.destroy
redirect_to article_path(article)
end
private
def comment_params
params.require(:comment).permit(:author, :content)
end
end
Here is form:
= simple_form_for(comment, url: article_comments_path(article)) do |f|
- if flash[:comment_errors]
.alert.alert-danger
strong= pluralize(flash[:comment_errors].count, 'error') + ' prohibited this article from being saved:'
- flash[:comment_errors].each do |msg|
ul
li= msg
fieldset class='form-group'
= f.label t('author')
= f.text_field :author, class: 'form-control', placeholder: t('who_are_you')
fieldset class='form-group'
= f.label t('content')
= f.text_area :content, class: 'form-control', rows: 6, placeholder: t('what_do_you_want_to_say')
fieldset class='form-group'
= recaptcha_tags
fieldset class='form-group'
= f.submit t('create_comment'), class: 'btn btn-primary'
For forms I'm using simple-form. Also I'm using decent exposure and slim
I want my page to scroll down to form after validation fails (So user don't have to scroll manually). Is there simple way to achieve that?
AFAIK I can't pass anchor to render (That would solve problem). Any ideas?
So I solved it with this javascript placed in comment form:
javascript:
if (document.getElementById("comment_errors")) {
location.hash = '#new_comment';
}
When element with id 'comment_errors' (My validation errors div) exists it jumps to it.
I'm just a beginner in laravel framework. i created a simple form and controller methods . i think by default the form method is post. but now i need to make it as get method and also wants to pass the selected inputs to controller and shows that parameters in the url also. currently i did like below but failed.
index.blade.php
<?php echo Form::open(array('url' => 'home/find','method' => 'get')); ?>
<select class="location" id="location" name='location'>
<?php
$id1 = /* get user db details based on locations*/
$location_id_drop_down='';
foreach($idl as $lrow)
{
$city_name=Location::where('location_id','=',$lrow['loc_id'])->first();
if($city_name==array()) continue;
if(Input::old('location')==$lrow['loc_id'])
{
$location_id_drop_down.="<option value='" . $city_name['location_name'] . "' selected='selected'>" .$city_name['location_name'] . "</option>";
}
else
{
$location_id_drop_down.="<option value='" . $city_name['location_name'] . "'>" . $city_name['location_name']. "</option>";
}
}
echo $location_id_drop_down;
?>
</select>
<input type="submit" name="search" id="search_submit" class="search_submit" value="Search" />
{{ Form::close() }}
HomeController.php
public function anyFind($s='',$d='',$l='') {
if(Input::get('search'))
{
$location=Input::get('location');
}
else
{
if($l!='~')
$location=$l;
}
/* queries to get the user details and images based on selected location and list them*/
}
Routes.php
Route::get('/find/{location}','HomeController#anySearch');
But this shows the url as mysite.com/home/find?location=Test&search=Search
I need mysite.com/home/find/location
is there any mistake in my code?
Edit
As a part of experiment i tried this method. i gave a redirect like below at the end of my controller function anyfind()
return Redirect::to('/home/find/'.$location);
But this redirects me but did anyone knows how to load the search.search_new.blade.php with this custom url??
Having a field from the form in your url (not query string) is simply not possible with Laravel. What you can do though, is just use javascript for that.
First lets put a placeholder in the action url
<?php echo Form::open(array('url' => 'home/find/%location%','method' => 'get')); ?>
jQuery
$('form').on('submit', function(){ // you maybe need to be a bit more precise with the selector here
var location = $('#location').val();
$(this).prop('action', $(this).prop('action').replace('%location%', location));
});
Vanilla Javascript (if you can't / don't want to use jQuery)
document.getElementsByTagName('form')[0].onsubmit = function(e){
var location = document.getElementById('location').value;
e.target.setAttribute('action', e.target.getAttribute('action').replace('%location%', location));
};
By the way: the code in your question has still some weird stuff in there. I'm just assuming this has happened because of copy paste etc. So if it still doesn't work, make sure you post the correct code
Insert at the end of your method anyFind() inside the HomeController.php where you set the view for the page:
if (Input::get('location') != '') {
return Redirect::route('home.findByLocation',Input::get('location'));
}
return View::make('home', compact('location));
Also in your routes.php add the following code:
Route::get('home/find/{location?}', array('as'=>'home.findByLocation', 'uses'=>'HomeController#anyFind'));
http://jsfiddle.net/p8RNJ/3/
When I press "submit" button, fooController in controller is equal to 'A' so barController gets assigned value 'A', which is confirmed in UI:
bar in controller: A
After I press "set value" button, fooController is updated from directive through binding with value 'B', which is confirmed in UI:
foo in controller: B
However after I press "submit" button, fooController in controller still has old value of 'A' so $scope.barController once again gets assigned a value of 'A':
bar in controller: A
How can I ensure that on second submit fooController has correct value of 'B' and barController gets assigned a value of 'B' as well?
HTML:
<div ng-app="app" ng-controller="Controller">
<script type="text/ng-template" id="directive-template.html">
foo in directive: {{fooDirective}}<br/>
<button ng-click="setValue()">set value</button>
</script>
<form data-ng-submit="getModel()">
<div ng-controller="Controller">
<button type="submit">submit</button><br/>
foo in controller: {{fooController}}<br/>
bar in controller: {{barController}}<br/>
<my-customer model="fooController"></my-customer>
</div>
</form>
</div>
And the JS:
angular.module('app', [])
.controller('Controller', ['$scope', function($scope) {
$scope.fooController = 'A';
$scope.getModel = function() {
$scope.barController = $scope.fooController;
}
}])
.directive('myCustomer', function() {
return {
restrict: 'E',
scope: {
fooDirective: '=model'
},
controller: function ($scope) {
$scope.setValue = function() {
$scope.fooDirective = 'B';
};
},
templateUrl: 'directive-template.html'
};
});
Don't use primitive data types (number, strings, booleans) because the inheritance doesn't work both ways, it only works from parent $scope to chid $scope. If you want it to work both ways user objects or arrays (that's what the angular community actually recommends).
$scope.barController = "aaaa";
should be:
$scope.modelState.barController = "aaa";
This way if you change it from the child scope it will be updated in the parent one as well.
you need to wrap your directive change in $scope.apply
$scope.apply(function(){
$scope.setValue = function() {
$scope.fooDirective = 'B';
};
}