setting iframe ID via fancybox2 to allow webdriver switchTo()->frame(id) - php

I'm using fancybox2 to create iframes but I can't see a way of setting the ID of the iframe that gets created, which is preventing me from using php-webdriver and selenium to test the contents of the iframe.
Simplified version of the code:
iframe
<script>
$(document).ready(function() {
$(".various").fancybox()
});
</script>
Which works, but using Chrome's inspector, the iframe was (this time) generated with an ID of
fancybox-frame1443817733402, which appears to be random. This means when I try to use php-webdriver to switch to this frame (having clicked the link to create the iframe), I can't predict the frame's ID to pass in:
$frame_id = 'fancybox-frame1443817733402'; // can't predict this in advance
$driver->switchTo()->frame($frame_id);
The iframe is always generated with a class of fancybox-iframe but calls to
$iframe = $driver->findElement(WebDriverBy::class("fancybox-iframe"))
return nothing.
I've also tried using fancybox2's afterLoad callback to try explicitly setting the iframe's ID before trying to switch to the frame by this ID, but that also fails (I think because current is an object, not an element?)
$(".various").fancybox({
afterLoad: function(current, previous) {
//console.log(current);
current.attr('id', 'rob');
}});
Is there a way of explicitly setting the iframe's ID so that I can switch to it via selenium/webdriver? Or is there a simpler way of doing this?

I don't know about setting the frame id here, but you can switch to a frame via xpath (such as //frame):
protected WebElement gotoIframeByXpath(final String iframeXpath) {
if (driver.findElements(By.xpath(iframeXpath)).size() > 0) { // find elements so an exception isn't thrown if not found
WebElement contentFrame = driver.findElement(By.xpath(iframeXpath));
driver.switchTo().frame(contentFrame);
return contentFrame;
} else {
System.out.println("Unable to find " + iframeXpath);
}
return null;
}

For anyone interested, following #EGHM's answer above, this is how I did it.
// this also works & is a little simpler
//$iframes = $driver->findElements(WebDriverBy::tagName('iframe'));
$iframes = $driver->findElements(WebDriverBy::xPath('//*[starts-with(#id,"fancybox-frame")]'));
$id = $iframes[0]->getAttribute('id');
$driver->switchTo()->frame($id);
echo $driver->getPageSource();

Related

PHP WebDriver cant select iframe for login

I am making script that will login to "https://asia.nikkei.com/". The script opens the login window which is in a iframe.
But then iam not able to select <inputs> for email and password. And when I try to switchTo iframe with id "piano-id-AciFE", I'm getting "no such frame" error using this code:
$driver->switchTo()->frame("piano-id-AciFE");
I tried another solution with:
$username = $driver->switchTo()->activeElement();
after the login window open, but this solution doesnt work either.
Is there any way to select login input in this case?
The value of the id attribute is partially static (i.e. piano-id-) and partially dynamic (i.e. AciFE). When I access the website I find the value of the id attribute as piano-id-IE9ZK
In such cases to switch to the desired iframe you can refer to the preceeding(sibling) WebElement and you can use the following locator strategy:
Using xpath and following:
$element = $driver->findElement(WebDriverBy::xpath("//button[#aria-label='Close']//following::iframe[1]"));
$driver->switchTo()->frame(element);
Using xpath and following-sibling:
$element = $driver->findElement(WebDriverBy::xpath("//button[#aria-label='Close']//following-sibling::iframe[1]"));
$driver->switchTo()->frame(element);
You can find a relevant detailed discussion in WebDriver - How to locate iframe without id

Dragula - passing class on dragula drop event

So i have an object thats draggable to multiple columns and I need to make it so that when it is dropped a variable in that object gets updated depending on what column that is, as another object manipulates that variable for display. i have been unable to get the (ondrop) event to work from the tag, and the dragula event listener's value doesnt pass me any information that would allow me to get the object. Is there anyway to force the event listener to pass the object instead of the html tags? or is there some method im missing?
I think you can update data on drop. I've done it like this:
var drake = dragula({...});
function updateMyObject(elementId, listId) {
// update the object here, for example:
if (listId === 'firstList') {
// use the element id to find the item in your object and update it
myDataObject.filter(function(x) {
return x.id === elementId;
})[0].propertyToUpdate = listId;
}
}
drake.on('drop', function(el, target, source, sibling) {
var elementId = el.id;
updateMyObject(el.id, target.id);
});
This pen may help. I'm mixing Dragula with Angular.js for the data modelling. The event needs to update the data model on drop.
http://codepen.io/chris22smith/pen/37459a002cbe6b6cd37aa5e927698fba
The only solution I have found (short from using a different drag'n'drop module) is to save the order when the user closes the page or goes to something else. Or since the drop event is catch-able, but does not seem to be able to give a class object, you can still detect when there was a change and have it update everything, however that is not the best work around as it is far more taxing on system resources than updating one variable.
Pass the order # as an attribute in your element so it's accessible in your drake.on function. With the oder# and the ID you should be able to figure out what to do with it and make the right updates on the backend.

how to check if bookmarks exist in html document

I've just designed my first form in HTML and a PHP page to display the results. In the form the user inputs some codes in response to some questions, a bit like a multiple choice, so for example, these are "ABC". The PHP page displays the code to the user as a link, which when clicked will go to a bookmark (a link within the same page) with the ID #ABC. This was achieved with simple manipulation of the PHP variable as follows:
<?php
$code = "ABC"
$part1 = '<a href="mywebpage.php#';
$part2 = '">Go to this code</a>';
$string = $part1.$code.$part2;
echo $string;
?>
(i.e. Link in the page says "go to this code" and when clicked will go to section with bookmark ABC)
This all works fine, but I simply need to know if there is a way of error trapping so that if a bookmark does not exist for the code entered, a message can be displayed to the user instead? Can this be done using the PHP variable, or do I need to use JavaScript? One work around may be to search the web page for the ID "#ABC'. Is it possible to do this? Another option would be to store an array of valid codes on the server then query this before setting the bookmark, but I want to keep it as simple as possible. Any help appreciated, thanks.
What you call a "bookmark" we call a hash. And when you say "go to a bookmark" you mean a hash change. Hash changes do not make an additional request to the server, it is all handled on the client-side, therefore this must be done with JavaScript and not PHP.
So let's just do some simple JavaScript on hash change window.onhashchange that will search for an element with that ID and if it's not found alert something.
window.onhashchange = function(){
if(!document.getElementById(location.hash){
alert("not found");
}
}

Can I send raw keyboard input using Mink and Selenium2?

I am using Behat and Mink with the Selenium2 driver, and I'm trying to type directly into a form field (simulating raw keyboard input), instead of using the fillField() function.
This is what I'm trying:
$element = $this->getSession()->getPage()->find('css', '#questionName');
$element->focus();
$element->keyPress('a');
// also tried this, with no success
// $element->keyDown('a');
// $element->keyUp('a');
There is an <input type="text" id="questionName"> element on the page. It correctly receives the focus, but does not respond to any of the simulated keyboard input.
Is it possible to simulate raw keyboard input like this?
What am I doing wrong?
There seems to be a lot of posts complaining about keyPress not working as intended and some drivers don't support it at all. e.g.:
Goutte - Keyboard manipulations are not supported by Behat\Mink\Driver\GoutteDriver
The Selenium driver in particular uses a custom js library to run it's commands, however it doesn't seem to work. I've tried using both the $this->getSession()->getDriver()->keyPress() and the $element->getPress() without luck.
https://github.com/Behat/MinkSelenium2Driver/blob/master/src/Behat/Mink/Driver/Selenium2Driver.php#L815
https://github.com/Behat/MinkSelenium2Driver/blob/master/src/Behat/Mink/Driver/Selenium2/syn.js
What is interesting is that there are no unit tests for the keyPress event in the Selenium2 code base yet (so I assume it's currently in development).
So, for the moment, an adequate solution is to use the javascript emulation of key events from Is it possible to simulate key press events programmatically? (see this for an alternative if you're not using jQuery) and Behat Mink's evaluateScript function.
If you're using straight PHPUnit to test:
$key = 'a';
$script = "jQuery.event.trigger({ type : 'keypress', which : '" . $key . "' });";
$this->getSession()->evaluateScript($script);
Or if you're using Cucumber, add this to your FeatureContext.php file you can add this function:
/**
* #Given /^(?:|I ) manually press "([^"]*)"$/
*/
public function manuallyPress($key)
{
$script = "jQuery.event.trigger({ type : 'keypress', which : '" . $key . "' });";
$this->getSession()->evaluateScript($script);
}
And use it in your feature file like this:
Given I manually press "a"
As for using the javascript as the solution, some of the drivers use javascript to perform the required keyPress. E.g.:
https://github.com/Behat/MinkZombieDriver/blob/master/src/Behat/Mink/Driver/ZombieDriver.php#L819
I'm using Mink with Zombie.js and as it does not catching keyboard events natively, I both listen to focusout and keyup jQuery events.
$('form[name="order"]').find('input[id$="quantity"],input[id$="price"]').bind('keyup focusout', function(){
// [...] update order price
});
I has solved the problem for me but I didn't try it with Selenium2.
The easiest answer I have found is to trigger the key event in javascript and write a specific behat step to send the js to the browser and trigger it.
We have been using YUI so we use the YUI event simulate but jquery or native js handles it. The concept is what matters. It's the best solution I've found until the native behat support is there.
hope this helps.
public function press_key_in_the_ousupsub_editor($keys, $fieldlocator) {
// NodeElement.keyPress simply doesn't work.
if (!$this->running_javascript()) {
throw new coding_exception('Selecting text requires javascript.');
}
// We delegate to behat_form_field class, it will
// guess the type properly.
$field = behat_field_manager::get_form_field_from_label($fieldlocator, $this);
if (!method_exists($field, 'get_value')) {
throw new coding_exception('Field does not support the get_value function.');
}
$editorid = $this->find_field($fieldlocator)->getAttribute('id');
// Get query values for the range.
$js = '
function TriggerKeyPressBehat() {
// http://www.wfimc.org/public/js/yui/3.4.1/docs/event/simulate.html
YUI().use(\'node-event-simulate\', function(Y) {
var id = "'.$editorid.'";
var node = Y.one("#" + id + "editable");
node.focus();
var keyEvent = "keypress";
if (Y.UA.webkit || Y.UA.ie) {
keyEvent = "keydown";
}
// Key code (up arrow) for the keyboard shortcut which triggers this button:
var keys = ['.$keys.'];
for(var i=0; i<keys.length;i++) {
node.simulate(keyEvent, { charCode: keys[i] });
}
});
}
TriggerKeyPressBehat();';
$this->getSession()->executeScript($js);
}

using jquery + <audio> w/ php/mysql to loop playback times

I've got a page with an iframe. The iframe is using jquery.scrollTo to detect the audio playback time and keep the contents, lyrics for example, linked to the right point in the audio.
Each line of text is being pulled from a mysql table. What I'm trying to do is to allow the user to click on a line in the <iframe> and have the audio in the parent page play at that point. Here's the code that grabs the times, which exists after the jquery that handles the other audio functions:
<?php
include 'mysqlt.php';
$entries=mysql_query("SELECT * FROM `database`.`table`") or die(mysql_error());
while ($ent = mysql_fetch_object($entries)) {
$segment = $ent->index;
$starttime = $ent->time;
$nextline = $ent->time2;
$length = ($nextline - $starttime); ?>
That same information is also duplicated in the iframe source page along with the individual lines of lyrics. The jquery that uses this info follows:
$("#iframe_id").contents().find("#button_in_iframe<?php echo $segment;?>").bind("click", function(){
audioplayer.currentTime = <?php echo $time;?>;
audioplayer.play();
}
<?php } ?>
This second part loops, echoed once for each each potential line in the iframe that can be clicked. #button_in_iframe<?php echo $segment;?> ends up as something like #button_in_iframe23 for the 23rd line.
I've got jquery loaded up in both the parent and the iframe. As far as I can tell, the syntax is what it should be. If I remove this bit of code, everything works normally. if it's in there, the basic audio functions on the normal controls I have set stop working. So this is not only not working, but it's causing everything else associated with the audio tag's controls to stop working as well. I know that any small error in the audio controls' jquery is enough to make it stop working, but in this case I'm not sure where the problem is.
Thanks in advance for any insights anyone might be able to offer.
update:
I've got it so it no longer crashes the audio tag. Now it's more an issue of the iframe's item not triggering an event. Here's the code now:
$('#iframe_id').contents().find("button.loop2").bind("click", function() {
alert ("test");
});
I'm not getting any console errors except for a Google Maps imbedded in the page in another location. When i click the <button> with class loop2 in the iframe, all of which exist, nothing's happening. That seems to be the root of the problem as it was earlier.
I figured out how to do it. I've set the loop on the parent to read as follows:
play<?php echo $segment; ?> = function(){
sfx.currentTime = sounds.getCueById(id).startTime;
sfx.play();
};
Then in the iframe's page, I've told each button to call parent.play<?php echo $segment;?>();
I'm not sure why finding the tags in the iframe didn't work, but no matter. This get's the job done.
Thanks, shanabus, for all the suggestions.
Would it work to use media cues?
Example:
var sfx = new Audio('sfx.wav');
var sounds = a.addTextTrack('metadata');
// add sounds we care about
sounds.addCue(new TextTrackCue('dog bark', 12.783, 13.612, '', '', '', true));
sounds.addCue(new TextTrackCue('kitten mew', 13.612, 15.091, '', '', '', true));
function playSound(id) {
sfx.currentTime = sounds.getCueById(id).startTime;
sfx.play();
}
You can find a better definition of the spec half way down the page here - http://dev.w3.org/html5/spec/Overview.html
So in your case, maybe something like:
$("#iframe_id").contents().find("#button_in_iframe<?php echo $segment;?>").bind("click", function(){
sounds.addCue(new TextTrackCue('<?php echo $segment; ?>', <?php echo $starttime; ?>, <?php echo ($starttime + $length); ?>, '', '', '', true));
var id = '<?php echo $segment; ?>';
sfx.currentTime = sounds.getCueById(id).startTime;
sfx.play();
}
Or maybe separate your logic into two loops. One for building the cues, then one for building/binding the click events.
Hope this helps!

Categories