With Behat/Mink, how to match an exact number? - behat

I'm trying to match an exact number for a page element with Behat/Mink.
My test looks like this:
Then the "td.points" element should contain "1"
This matches if td.points is 1 (good), but it also matches if td.points is 10 or 21 (bad).
I tried using a regex like this:
Then the "td.views-field-field-int-repetitions" element should contain "\b1\b"
But the regex wasn't picked up.
I tried to dig through the code and I see that MinkContext has assertElementContains, but I couldn't find anything like AssertElementIs.
What I want is something like
Then the "td.points" element should be exactly "1"
How can I implement this?
EDIT: This is the element contains feature from MinkContext.php:
/**
* Checks, that element with specified CSS contains specified HTML
* Example: Then the "body" element should contain "style=\"color:black;\""
* Example: And the "body" element should contain "style=\"color:black;\""
*
* #Then /^the "(?P<element>[^"]*)" element should contain "(?P<value>(?:[^"]|\\")*)"$/
*/
public function assertElementContains($element, $value)
{
$this->assertSession()->elementContains('css', $element, $this->fixStepArgument($value));
}

For extracting the number from the step you could use for a number:
the "(.*)" element should contain (\d+)
or for string
the "(.*)" element should contain "(.*)"
or other example for string
the "(.*)" element should contain (.*)
and for asserting depends on how your code is organized, use what you have or you could just do:
if($someActual != $expected)
{
throw new \Exception("something meaningful");
}

Thanks to #lauda, I was able to write the code I wanted:
/**
* #Then the :element element should be exactly :value
*
* Checks that the element with the specified CSS is the exact value.
*/
public function theElementShouldBeExactly($element, $value) {
$page = $this->getSession()->getPage();
$element_text = $page->find('css', "$element")->getText();
if ($element_text === NULL || strlen($element_text < 1)) {
throw new Exception("The element $element had a NULL value.");
}
if ($element_text !== $value) {
throw new Exception("Element $element_text did not match value $value.");
}
}

Related

How can I save part of a string in an alias using Cypress?

I'm trying to save just a number from a string I get from a paragraph but when I try to asign an alias to it and then check the value it returns undefined. I've tried a few solutions I found but none of those seem to work for me. These are two ways I tried (I tried another one similar to the second one but using split, had same result). The console.log inside of the 'then' doesn't show in the console, and when I try the alias after the code is when I get undefined.
cy.get('p')
.eq(1)
.should('have.text', '/[0-9]+/g')
.as('solNumber')
cy.get('p')
.eq(1)
.invoke('text')
.then((text)=>{
var fullText = text;
var pattern = /[0-9]+/g;
var number = fullText.match(pattern);
console.log(number);
})
.as('solNumber')
Please convert with + operator and return the numeric value if you want numeric type to be stored.
cy.get('p').eq(1)
.invoke('text')
.then(fullText => {
const number = fullText.match(/[0-9]+/);
return +number // text to numeric
})
.as('solNumber')
cy.get('#solNumber')
.should('eq', 42) // numeric type
});
Running your 2nd code on this,
<p>21</p>
<p>42</p>
gives the correct outcome
cy.get('p')
.eq(1)
.invoke('text')
.then((text)=>{
var fullText = text;
var pattern = /[0-9]+/g;
var number = fullText.match(pattern);
console.log(number); // logs 42
})
.as('solNumber')
cy.get('#solNumber')
.should('eq', '42') // passes
So, you need to inspect the DOM, it looks like it's not what you expect.
The first attempt you were passing a jquery element to the .should() and although some chainers change the subject yours did not so it saved the jquery element as solNumber.
The second attempt invokes the .text() which was passed to the .then() it logs the number correctly. However, you did not return anything at the end of the .then() block, therefore, solNumber should hold the entire paragraph.
This should help you out to extract the specific number and save it as an alias.
cy.get('p')
.invoke('text')
.invoke('trim')
.then(paragraph => {
const matcher = /some/
expect(paragraph).to.match(matcher) // check number is there
const indexOfText = paragraph.match(matcher) // get index of match text
return paragraph.substring(indexOfText.index, indexOfText.index + indexOfText[0].length) // return substring
})
.as('savedText')
cy.get('#savedText')
.then(cy.log) // will print out the number you seek

Behat Pass a value from a test step

I'm trying to make assertion that the random text entered in one field appears on next page (confirmation)
I do it like this
When I fill in "edit-title" with random value of length "8"
/**
* Fills in form field with specified id|name|label|value with random string
* Example: And I fill in "bwayne" with random value of length "length"
*
* #When /^(?:|I )fill in "(?P<field>(?:[^"]|\\")*)" with random value of length "(?P<length>(?:[^"]|\\")*)"$/
*/
public function fillFieldWithRandomValue($field, $length)
{
$field = $this->fixStepArgument($field);
$value = $this->generateRandomString($length);
$this->getSession()->getPage()->fillField($field, $value);
}
Than I want to make assertion - something like this:
Then I should see text matching "<RANDOM VALUE ENTERED IN THE PREVIOUS STEP>"
is it possible?
UPDATE:
But how would it look like with setters and getters if i want to use a generateRandomString method multiple times and then get the values of this methods one after another? DO I have to make variables and functions for every test step? like this:
When I fill in "x" with random value of length "8"
And I fill in "y" with random value of length "12"
And I go to other page
Then I should see text matching "VALUE ENTERED TO X"
And I should see text matching "VALUE ENTERED TO Y"
You can create a property and set it in the previous step. And use it in the next one, but assert it if it has value.
Also it would be nice and readable to define that property with proper visibility type
/**
* #var string
*/
private randomString;
/**
* Fills in form field with specified id|name|label|value with random string
* Example: And I fill in "bwayne" with random value of length "length"
*
* #When /^(?:|I )fill in "(?P<field>(?:[^"]|\\")*)" with random value of length "(?P<length>(?:[^"]|\\")*)"$/
*/
public function fillFieldWithRandomValue($field, $length)
{
$field = $this->fixStepArgument($field);
$this->randomString = $this->generateRandomString($length);
$this->getSession()->getPage()->fillField($field, $this->randomString);
}
/**
*
* #Then /^(?:|I )should see that page contains random generated text$/
*/
public function assertPageContainsRandomGeneratedText()
{
//Assertion from phpunit
$this->assertNotNull($this->randomString);
$this->assertPageContainsText($this->randomString);
}
NOTE: Depending on your behat setup - assertion from phpunit might not work.
Since you will will call the generateRandomString method in multiple places then you should also have a method for getting this value like getRandomString like setters and getters.
My recommendation would be to have a class with related methods that handle all the data and not saving in variable in every place you will use data, generate+save and read from the same place anywhere you need.
Tip: You could be more flexible about the step definition and have a default length for the random string in case one one not provided.
High level example:
class Data
{
public static $data = array();
public static function generateRandomString($length = null, $name = null)
{
if ($name = null) {
$name = 'random';
};
if ($length = null) {
$length = 8;
};
// generate string like $string =
return self::$data[$name] = $string;
}
public static function getString($name = null)
{
if ($name = null) {
$name = 'random';
};
// exception handling
if (array_key_exists($name, self::$data) === false) {
return null;
}
return self::$data[$name];
}
}
In context:
/**
* #Then /^I fill in "x" with random value as (.*?)( and length (\d+))?$/
*/
public function iFillInWithRandomValue($selector, $name, $length = null){
$string = Data::generateRandomString($length, $name);
// fill method
}
/**
* #Then /^I should see text matching "first name"$/
*/
public function iShouldSeeTextMatching($variableName){
$string = Data::getString($variableName);
// assert/check method
}
This is high level example, you might need to do some adjustments.
If you have the validation in the same class then you can also have all these in the same class, meaning generateRandomString and getString in the same class with the steps.

Does Laravel Input::hasfile() work on input arrays?

I'm working on a Laravel project that uses a form with multiple file inputs. If I submit the form with the first input empty and all other inputs with a file, then hasFile returns false. It will only return true if the first input contains a file.
if(Input::hasfile('file'))
{
// do something
}
This is the input array via Input::file('file). The small image input is empty, but the large is not. I'd like it to look at the whole array and if there any files present, then proceed with the "do something".
Array
(
[small] =>
[large] => Symfony\Component\HttpFoundation\File\UploadedFile Object
(
[test:Symfony\Component\HttpFoundation\File\UploadedFile:private] =>
[originalName:Symfony\Component\HttpFoundation\File\UploadedFile:private] => image_name.jpg
[mimeType:Symfony\Component\HttpFoundation\File\UploadedFile:private] => image/jpeg
[size:Symfony\Component\HttpFoundation\File\UploadedFile:private] => 44333
[error:Symfony\Component\HttpFoundation\File\UploadedFile:private] => 0
[pathName:SplFileInfo:private] => /Applications/MAMP/tmp/php/phpHILgX2
[fileName:SplFileInfo:private] => phpHILgX2
)
)
Is this expected behavior? Or, should it be looking at the entire array?
You can check by using the array key for example like below :-
HTML Input type File Element :
<input type="file" name="your_file_name[]" />
Laravel 5 : $request->hasFile('your_file_name.'.$key)
Laravel 4.2 : Input::hasFile('your_file_name.'.$key)
Taken from source:
/**
* Determine if the uploaded data contains a file.
*
* #param string $key
* #return bool
*/
public function hasFile($key)
{
if (is_array($file = $this->file($key))) $file = head($file);
return $file instanceof \SplFileInfo;
}
It seems that it only checks the first one from the array, head returns the first item from the array.
Since I can't comment, seems I'll have to post.
Ronak Shah's answer really should be marked the correct one here, and when I figured out why, it instantly had me saying "Sonnofa--" after 30-40 minutes trying to figure this... "mess" out.
Turns out to use hasFile() on an input array, you need to use dot notation.
So (using my own example) instead of
$request->hasFile("img[29][file]")
it needs to be
$request->hasFile("img.29.file")
That's certainly an eye-opener, given that PHP and dot notation don't really go together. Input arrays really are problem children.
here is a snippet that may help
if(Input::hasFile('myfile')){
$file = Input::file('myfile');
// multiple files submitted
if(is_array($file))
{
foreach($file as $part) {
$filename = $part->getClientOriginalName();
$part->move($destinationPath, $filename);
}
}
else //single file
{
$filename = $file->getClientOriginalName();
$uploadSuccess = Input::file('myfile')->move($destinationPath, $filename);
}
} else {
echo 'Error: no file submitted.';
}
Taken from
http://forumsarchive.laravel.io/viewtopic.php?id=13291
At the time of writing (Laravel 8) the Request class now supports arrays for the hasFile method, as from the source code:
/**
* Determine if the request contains the given file.
*
* #param string $name
* #param string|null $value
* #param string|null $filename
* #return bool
*/
public function hasFile($name, $value = null, $filename = null)
{
if (! $this->isMultipart()) {
return false;
}
return collect($this->data)->reject(function ($file) use ($name, $value, $filename) {
return $file['name'] != $name ||
($value && $file['contents'] != $value) ||
($filename && $file['filename'] != $filename);
})->count() > 0;
}

Getting text from a node

I have a piece of HTML like this:
<a href="/something">
Title
<span>Author</span>
</a>
I got a WebElement that matches this HTML. How can I extract only "Title" from it? Method .getText() returns "Title\nAuthor"...
You can't do this in the WebDriver API, you have to do it in your code. For example:
var textOfA = theAElement.getText();
var textOfSpan = theSpanElement.getText();
var text = textOfA.substr(0, textOfA.length - textOfSpan.length).trim('\n');
Note that the trailing newline is actually part of the text of the <a> element, so if you don't want it, you need to strip it.
Here is the method developed in python.
def get_text_exclude_children(element):
return driver.execute_script(
"""
var parent = arguments[0];
var child = parent.firstChild;
var textValue = "";
while(child) {
if (child.nodeType === Node.TEXT_NODE)
textValue += child.textContent;
child = child.nextSibling;
}
return textValue;""",
element).strip()
How to use in this:
liElement = driver.find_element_by_xpath("//a[#href='your_href_goes_here']")
liOnlyText = get_text_exclude_children(liElement)
print(liOnlyText)
Please use your possible strategy to get the element, this method need an element from which you need the text (without children text).
If using Python:
[x['textContent'].strip() for x in element.get_property('childNodes') if isinstance(x, dict)]
Where element is your element.
This will return ['Title', ''] (because there are spaces after span).
you can use jsexecutor to iterate the child nodes, trap the textNode 'Title' and then return its content like below
WebElement link = driver.findElement(By.xpath("//a[#href='something']"));
JavascriptExecutor js = ((JavascriptExecutor)driver);
String authorText = (String) js.executeScript("for(var i = 0; i < arguments[0].childNodes.length; i++) {
if(arguments[0].childNodes[i].nodeName == \"#text\") { return arguments[0].childNodes[i].textContent; } }", link);
The javascript code block above iterates both textNode ('Title') and SPAN ('Author') but returns only the text content of textNode.
Note: Previous to this, I have tried including text node in xpath like below, but webdriver throws invalidselector exception as it requires element not textnode
WebElement link = driver.findElement(By.xpath("//a[#href='something']/text()"));
Verify the element present for "//a[normalize-space(text())=Title]". It will return true if the text present inside 'a' tag is 'Title'.

Selenium: Get unique value?

Just using selenium to fill out some forms for me. I need it to generate a unique value for my username field. How can I do that?
I've got
Command: type
Target: id_of_my_field
Value: username+unique_value ???
You can use javascript to do that:
Value: javascript{'username'+Math.floor(Math.random()*100000)}
This will append a 6 digit random number to your username.
See this SO question and answers for more details and other ways to do this...
My solution which works well for me:
Save the following TEXT as a .js file and add to the Options->Options "Selenium Core Extensions" list...
Selenium.prototype.doTypeRandomName = function(locator)
{
/**
* Sets the value of an input field to a random "name"
* as though you typed it in.
*/
// All locator-strategies are automatically handled by "findElement"
var element = this.page().findElement(locator);
/* The following block generates a random name 8 characters in length */
var allowedChars = "abcdefghiklmnopqrstuvwxyz";
var stringLength = 8;
var randomstring = '';
for (var i=0; i<stringLength; i++) {
var rnum = Math.floor(Math.random() * allowedChars.length);
randomstring += allowedChars.substring(rnum,rnum+1);
}
// Replace the element text with the new text
this.browserbot.replaceText(element, randomstring);
}
Once done, you can simply select the TypeRandomName command in Selenium for each text box where you want a random "name" to be generated.
Steps for a globally reusable solution is as follows
1) Download sideflow.js from Download here
2) Add following lines into it :
Selenium.prototype.doTypeRandomName = function(locator) {
/**
* Sets the value of an input field to a random email id,
* as though you typed it in.
*
* #param locator an element locator
*/
// All locator-strategies are automatically handled by "findElement"
var element = this.page().findElement(locator);
/* The following block generates a random email string */
var allowedChars = "abcdefghiklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ1234567890";
var stringLength = 8;
var randomstring = '';
for (var i=0; i<stringLength; i++) {
var rnum = Math.floor(Math.random() * allowedChars.length);
randomstring += allowedChars.substring(rnum,rnum+1);
}
// Replace the element text with the new text
this.browserbot.replaceText(element, randomstring);
};
3) Save the file
4) Go to Selenium ide -> options -> options ->Selenium Core extensions -> give reference of your file there.
5) Now your randomname function will appear in auto-intellisense and will be as "typerandomname" command category.
6) Sample usage could be (if base url is google.com)
<tr>
<td>open</td>
<td>/</td>
<td></td>
</tr>
<tr>
<td>typerandomname</td>
<td>css=input[class='gbqfif']</td>
<td></td>
</tr>
Hope this helps you
Here is a way to generate unique numbers without using JavaScript in your code: (i used Java)
DateFormat dateFormat = new SimpleDateFormat("ddHHmmss"); /* Here you create object of the class DateFormat, and SPECIFY THE FORMAT (ddHHmmss). (Don`enter code here`t forget to import class Date(ctrl+shift+o if you are using Eclipse)) */
Date date = new Date(); /* Here you create object of the class Date. (Dont forget to import class Date. (Dont forget to import class Date(ctrl+shift+o if you are using Eclipse)) */
String random_number = dateFormat.format(date); /* Assign your current Date(something like 19184123) (ddHHmmSS) to a local variable(it require a String) */
yourWebDriver().findElemnt(By.cssSelector("input#someId")).sendKeys("test"+random_number+"#tester.com"); /* send keys to your input injecting random number */
Such method will give truly unique number that will never repeat itself, because it use your current time...
You can add even further randomness if include mile seconds into DateFormat