How do I test if an img tag exists? - protractor

if I do expect(img).not.toBe(null) then I get an error:
Error: expect called with WebElement argment, expected a Promise. Did you mean to use .getText()?. I don't want to get the text inside an img, I just want to know if the tag exists on the page.
describe('company homepage', function() {
it('should have a captcha', function() {
var driver = browser.driver;
driver.get('http://dev.company.com/');
var img =driver.findElement(by.id('recaptcha_image'));
expect(img.getText()).not.toBe(null);
});
});
Passes but I'm not sure it is testing the right thing. Changing the id to something that doesn't exist does fail.
How do I properly test for a tag to exist with protractor in a non-angular app context?

Edit 2:
Per Coding Smackdown below, an even shorter answer is now available in protractor:
expect(element(by.id('recaptcha_image')).isPresent()).toBe(true);
Edit 1:
I discovered isElementPresent() today which is just a more readable shortcut for what I described below. See: http://www.protractortest.org/#/api
Usage for you would be:
driver.isElementPresent(by.id('recaptcha_image')).then(function(present){
expect(present).toBe(false);
})
Old answer (this works but the above is more reader friendly)
In general you should use findElements (or $$ which is an alias for findElements by css) if you're not sure a tag will be there. Then test for the array length. FindElement (and $) will just throw an error if it cant find the element.
Therefore instead of
var img =driver.findElement(by.id('recaptcha_image'));
expect(img.getText()).not.toBe(null);
use:
driver.findElements(by.id('recaptcha_image')).then(function(array){
expect(array.length).not.toBe(0);
})
Also, getText() returns a promise which is why you're getting that error.

Using the latest Protractor build you can shorten it down to the following:
expect(element(by.id('recaptcha_image')).isPresent()).toBe(true);

Related

Stop huge error output from testing-library

I love testing-library, have used it a lot in a React project, and I'm trying to use it in an Angular project now - but I've always struggled with the enormous error output, including the HTML text of the render. Not only is this not usually helpful (I couldn't find an element, here's the HTML where it isn't); but it gets truncated, often before the interesting line if you're running in debug mode.
I simply added it as a library alongside the standard Angular Karma+Jasmine setup.
I'm sure you could say the components I'm testing are too large if the HTML output causes my console window to spool for ages, but I have a lot of integration tests in Protractor, and they are SO SLOW :(.
I would say the best solution would be to use the configure method and pass a custom function for getElementError which does what you want.
You can read about configuration here: https://testing-library.com/docs/dom-testing-library/api-configuration
An example of this might look like:
configure({
getElementError: (message: string, container) => {
const error = new Error(message);
error.name = 'TestingLibraryElementError';
error.stack = null;
return error;
},
});
You can then put this in any single test file or use Jest's setupFiles or setupFilesAfterEnv config options to have it run globally.
I am assuming you running jest with rtl in your project.
I personally wouldn't turn it off as it's there to help us, but everyone has a way so if you have your reasons, then fair enough.
1. If you want to disable errors for a specific test, you can mock the console.error.
it('disable error example', () => {
const errorObject = console.error; //store the state of the object
console.error = jest.fn(); // mock the object
// code
//assertion (expect)
console.error = errorObject; // assign it back so you can use it in the next test
});
2. If you want to silence it for all the test, you could use the jest --silent CLI option. Check the docs
The above might even disable the DOM printing that is done by rtl, I am not sure as I haven't tried this, but if you look at the docs I linked, it says
"Prevent tests from printing messages through the console."
Now you almost certainly have everything disabled except the DOM recommendations if the above doesn't work. On that case you might look into react-testing-library's source code and find out what is used for those print statements. Is it a console.log? is it a console.warn? When you got that, just mock it out like option 1 above.
UPDATE
After some digging, I found out that all testing-library DOM printing is built on prettyDOM();
While prettyDOM() can't be disabled you can limit the number of lines to 0, and that would just give you the error message and three dots ... below the message.
Here is an example printout, I messed around with:
TestingLibraryElementError: Unable to find an element with the text: Hello ther. This could be because the text is broken up by multiple elements. In this case, you can provide a function for your text matcher to make your matcher more flexible.
...
All you need to do is to pass in an environment variable before executing your test suite, so for example with an npm script it would look like:
DEBUG_PRINT_LIMIT=0 npm run test
Here is the doc
UPDATE 2:
As per the OP's FR on github this can also be achieved without injecting in a global variable to limit the PrettyDOM line output (in case if it's used elsewhere). The getElementError config option need to be changed:
dom-testing-library/src/config.js
// called when getBy* queries fail. (message, container) => Error
getElementError(message, container) {
const error = new Error(
[message, prettyDOM(container)].filter(Boolean).join('\n\n'),
)
error.name = 'TestingLibraryElementError'
return error
},
The callstack can also be removed
You can change how the message is built by setting the DOM testing library message building function with config. In my Angular project I added this to test.js:
configure({
getElementError: (message: string, container) => {
const error = new Error(message);
error.name = 'TestingLibraryElementError';
error.stack = null;
return error;
},
});
This was answered here: https://github.com/testing-library/dom-testing-library/issues/773 by https://github.com/wyze.

Unable to select an option from a drop down in Katalon studio

I am new to Katalon Studio and I am facing an issue regarding selection of the drop down.
Please find below the details:
This is the HTML :
I have tried using selectByIndex with the object xpath as:
//div[#class='paCriteriaContainer']//select[#class = 'pa-criteria-select a-select initialized']
It does not select any option and fails with an error stating "Unable to select option by index '2' of object"
Note:
I tried clicking on the input and then selecting the option, but that doesn't seem to work either.
Selecting by label and value don't work either
Please help me here.
Thank you
Try to capture an object and then use following methods :
WebUI.click(findTestObject(Your captured object))
WebUI.selectOptionByValue(findTestObject(Your captured object), 'TEST (2020)', false)
Did you done as I've described and it does not work ?
I tried clicking on the input and then selecting the option, but that doesn't seem to work either.
Are you sure you are clicking the right element in this case?
Try the following instead: create programmatically the element and select by value (note, value isn't the text contained, it is the value html attribute):
TestObject to = new TestObject().addProperty("xpath", ConditionType.EQUALS, "//div[#class='paCriteriaContainer']//select[#class = 'pa-criteria-select a-select initialized']")
WebUI.selectOptionByValue(to, '40696', false)
You have some options to do that, I reggardly suggest you that use always the xpath to reach all the elements that you want to use. The reasson is because the object reports usually fail and, in my opinion, this way is so much more complicated.
But obviously, the xpath will change if the web do, so take care with it.
The imports you need:
import static org.junit.Assert.*
import org.openqa.selenium.By
import org.openqa.selenium.Keys
import com.kms.katalon.core.webui.driver.DriverFactory as DriverFactory
def driver = DriverFactory.getWebDriver()
//If you want to click your input would be:
WebUI.click(WebUI.convertWebElementToTestObject(driver.findElement(By.xpath("(//input[#id='a-select-paCricteriaId_6908'])"))))
//**you just can click on "TestObject" type, and findElement returns "Element" type**
And if you want to select the option you need to know the whole path (I cannot get it with the given information).
An important tip for testing the xpath is to use this function in console mode (F12):
function getElementByXpath(path) {
return document.evaluate(path, document, null, XPathResult.FIRST_ORDERED_NODE_TYPE, null).singleNodeValue;
}
//And this code in the same console to test your xpath:
getElementByXpath("YOURTESTXPATH")
Furthermore, there are other ways to reach the same objetive with xpath, for example:
import com.kms.katalon.core.testobject.TestObject as TestObject
...
TestObject tobj = new TestObject('myTestObject')
String expr = '/some/valid/xpath/expression'
tobj.addProperty('xpath', ConditionType.EQUALS, expr)
WebUI.click(tobj)
You have a lot of information if you google "how to get elements by xpath katalon".
Here you can get official information about it:
https://docs.katalon.com/katalon-studio/tutorials/detect_elements_xpath.html#what-is-xpath
Test in the browser console
$x('//*[contains(#class, "pa-criteria-select a-select initialized")]')
if more than one result appear then you can access it like this
$x('(//*[contains(#class, "pa-criteria-select a-select initialized")])[1]')
then you can also access their children
$x('(//*[contains(#class, "pa-criteria-select a-select initialized")])[1]/option')
Use WebUI.selectOptionByIndex keyword but the object should point to the select tag instead of the div.
Update the object element and your code should work

writing a Jacada Interaction extension

I want to create an "extension" for a Jacada Interaction (to extend functionality), in my case to parse and assign the numerical part of serialNumber (a letter, followed by digits) to a numeric global ("system") variable, say serialNumeric. What I am lacking is the structure and syntax to make this work, including the way to reference interaction variables from within the extension.
Here is my failed attempt, with lines commented out to make it innocuous after failing; I think I removed "return page;" after crashing, whereupon it still crashed:
initExtensions("serialNumeric", function(app){
app.registerExtension("loaded", function(ctx, page) {
// Place your extension code here
//$('[data-refname="snum"]').val('serialNumber');
// snum = Number(substring(serialNumber,1))
});
});
Here is an example of one that works:
/**
* Description: Add swiping gestures to navigate the next/previous pages
*/
initExtensions("swipe", function(app) {
// Swipe gestures (mobile only)
app.registerExtension('pageRenderer', function(ctx, page) {
page.swipe(function(evt) {
(evt.swipestart.coords[0] - evt.swipestop.coords[0] > 0)
? app.nextButton.trigger('click')
: app.backButton.trigger('click')
});
return page;
});
});
After reading the comment below, I tried the following, unsuccessfully (the modified question variable is not written back to that variable). It rendered poorly in the comment section, so I am putting it here:
initExtensions("serialNumeric", function(app){
app.registerExtension("loaded", function(ctx, page) {
var sernum = new String($('[data-refname="enter"] input'));
var snumeric = new String(sernum.substr(1));
$('[data-refname="enter"] input').val(snumeric);
});
});
I would like to understand when this code will run: it seems logical that it would run when the variable is assigned. Thanks for any insight ~
In your case, you extend loaded event. You don't have to return the page from the extension like in your working example below.
The page argument contains the DOM of the page you have just loaded, the ctx argument contains the data of the page in JSON form. You can inspect the content of both arguments in the browser's inspection tools. I like Chrome. Press F12 on Windows or Shift+Ctrl+I on Mac.
The selector $('[data-refname="snum"] input') will get you the input field from the question with the name snum that you defined in the designer. You can then place the value in the input field with the value from the serialNumber variable.
$('[data-refname="snum"] input').val(serialNumber);
You can also read values in the same way.
You can't (at this point) access interaction variables in the extension, unless you place theses variables inside question fields.
Here is a simple example how to put your own value programmatically into a input field and cause it to read it into the model, so upon next it will be sent to the server. You are welcome to try more sophisticated selectors to accommodate for your own form.
initExtensions("sample", function(app){
app.registerExtension("loaded", function(ctx, page) {
// simple selector
var i = $('input');
// set new value
i.val('some new value');
// cause trigger so we can read into our model
i.trigger('change');
});
});

Protractor + ionicPopup

Has anyone succeeded in using Protractor to detect an ionicPopup alert?
I've tried all the workarounds suggested here but no luck.
I need Protractor to detect the alert and check the text in the alert.
Here's the class I wrote to test that the popup exists and to ensure the text is correct in the header and body:
var TestUtilities = function(){
this.popup = element(by.css('.popup-container.popup-showing.active'));
//Tests to see if $ionicPopup.alert exists
this.popupShouldExist = function() {
expect(this.popup.isDisplayed()).toBeTruthy();
};
//Tests to see if $ionicPopup.alert contains the text provided in the argument exists in the header
this.popupContainsHeaderText = function (text) {
this.popupShouldExist();
expect(this.popup.element(by.css('.popup-head')).getText()).toMatch(text);
};
//Tests to see if $ionicPopup.alert contains the text provided in the argument exists in the body
this.popupContainsText = function (text) {
this.popupShouldExist();
expect(this.popup.element(by.css('.popup-body')).getText()).toMatch(text);
};
};
module.exports=TestUtilities;
Also check out this site for more on testing Ionic in protractor it talks about how to check to see if the popup exists: http://gonehybrid.com/how-to-write-automated-tests-for-your-ionic-app-part-3/
I've tested Ionic popups successfully by setting the popup variable as follows:
var popup = element(by.css('.popup-container.popup-showing.active'));
And in the test:
expect(popup.isDisplayed()).toBeTruthy();
Ionic Popups are just made of DOM elements, so you should be able to use normal locators to find/test them. Because they're not made of alerts, the workarounds in the issue you linked to are probably not useful.
I got it - I saw a lot of issues out there trying to do it in very complex ways, but in the end I tried this and it turns out to be this simple.
Inspect your element and find its ng-repeat value, then
var button = element(by.repeater('button in buttons')).getText()
You also need to have the browser sit out somehow for a couple seconds so it doesn't resolve to the tests while the ionic popup isn't actually there.
For that, browser.sleep(3000);
That's it! However, getting the other button in there is proving to be a little problem. var button = element(by.repeater('button in buttons')).get(0) or .get(1) return undefined is not a function.
Please accept the answer if you like it! If I figure out how to get the other button, I'll post it here.

AngularJS - binding input file scope to a different scope

Wasn't sure how to properly title my question, I guess in my case it can also be titled "DOM manipulation not being detected by the scope", but it all depends on the approach of my problem.
To start off, I followed an official example on AngularJS main website with the Projects app which connects with Mongolab. The only difference is I want to add a file input, that reads file name and its lastModifiedDate properties and then applies those values to my form. To make file input work I followed this example here.
I made it work, but the problem is that when values get applied to my form scope is not picking up the changes.
I am doing DOM manipulation in my .apply() function and using $compile too, but something is missing. Or perhaps there's an easier way altogether without doing DOM manipulation?
Here's what I have so far, please take a look at this plunker - http://plnkr.co/edit/mkc4K4?p=preview
(Just click on the plus sign icon to add new entry, then try choosing a file.)
You need to add a watch statement in the CreateCtrl
function CreateCtrl($scope, $location, Movie) {
$scope.inputfile = {};
$scope.movie = {};
$scope.$watch('inputfile.file', function(value){
$scope.movie.filename = value ? value.name : '';
$scope.movie.dateadded = value ? value.lastModifiedDate : '';
})
$scope.save = function() {
Movie.save($scope.movie, function(movie) {
$location.path('/edit/' + movie._id.$oid);
});
};
}
Demo: Sample