I am using 'protractor' for testing my web application.
I add 'vue2-editor' in my project, I want to write something in the editor and save data.
this is the html that generate by Vue :
<div id="quill-container" class="ql-container ql-snow">
<div class="ql-editor ql-blank" data-gramm="false" contenteditable="true" data-placeholder="Add Note...">
<p>
<br>
</p>
</div>
<div class="ql-clipboard" contenteditable="true" tabindex="-1"></div>
<div class="ql-mention-list-container" style="display: none; position: absolute;">
<ul class="ql-mention-list"></ul>
</div>
</div>
this is my test code :
describe('When you save a note', function () {
beforeAll(function () {
browser.get('http://example');
element.all(by.css(" [id='quill-container'] > div.ql-editor.ql-blank")).click();
element.all(by.css(" [id='quill-container'] > div.ql-editor.ql-blank")).sendKeys('Some Notes add here');
element(by.css("[id='add-note-btn']")).click();
browser.sleep(1000);
});
it('You must see your note at the current page', function () {
expect(element(by.xpath('//p[text()="Some Notes add here"]')).isPresent()).toBe(true);
});
});
When I execute the code, Protractor gives this message :
Failed: element not interactable
What should i do ?
Basically, there are four reasons why an element is not interactable.
1) Timing - the time it takes for elements to load. For this, you need to check how to use implicit an explicit wait
2)Check if the element is in a frame
3) Incorrect locator
4) Wrong implementation of responsiveness. This still stems from no 3). Some websites have only one code turned on for mobile and web versions. So, the element will have more than one instances when you check the xxxxx.size. You will have to search through the list for the one whose display != none.
Related
Page source (only iFrame part which contains to form i need to fill)
<iframe title="Form 0" id="hs-form-iframe-0" >
#document
<html>
<body>
<form id="hsForm_405e4c3f-98da-4eb1-bd27-c1886a1f811e">
<div>
<label placeholder="Enter your Vorname">Vorname</span>
<div class="input">
<input name="firstname">
</input>
</div>
</div>
</form>
</body>
</html>
</iframe>
Code i tried:
cy.get('#hs-form-iframe-0').its('0.contentDocument').should('exist')
cy.get('input[name="firstname"]').type( 'Smith') //failes as never found. Is the iFrame the cause of it? Of the form?
TLDR The correct way would be to use .find() on the iframe contentWindow.
cy.get('#hs-form-iframe-0').its('0.contentWindow').should('exist')
.its('body').should('not.be.undefined')
.find('input[name="firstname"]').type( 'Smith')
Example from Working with iframes in Cypress
const getIframeWindow = () => {
return cy.get('iframe[data-cy="the-frame"]')
.its('0.contentWindow').should('exist')
.its('body').should('not.be.undefined')
}
cy.getIframeBody().find('#run-button').should('have.text', 'Try it').click()
There are other potential problems, such as delayed loading of the iframe source. The .should('exist') check on the iframe window does not cover all situations, nor does performing visibility checks on the input.
The cypress-iframe package has a lot more checks built in, so it's a safer way to handle iframes.
You have found the iframe and access its contents but then you search for the input at the root of your DOM instead of the iframe. You can continue the chain of commands by removing the second cy.
cy.get('#hs-form-iframe-0')
.its('0.contentDocument')
.should('exist')
.get('input[name="firstname"]')
.should('be.visible') // always good to check before action
.type( 'Smith')
Unable to find the textbox element in a new pop-up window.
Actual Result:
Expected Result:
Able to type value in the text box.
Adding the cypress snippet below,
it("Add business test",function(){
cy.xpath("//a[contains(.,'1099/W-2')]").click({force:true});
cy.wait(5000);
cy.get(':nth-child(2) > .btn-hover-shrink > .v-btn__content').click({force: true});
cy.contains('Start Now').click({force:true});
//Add business pop-up open
cy.contains('Business Name').click({force: true}).type("Test LLC");
})
You can add {force: true} with type() to disable error checking -
cy.get('[id*="input-"]').type("Test LLC", {force: true});
The error message indicates that you are trying to type() into the label. That's because cy.contains('sometext') selects the element "owning" the text, which is the label, but you can also select a parent by using the pattern cy.contains(<parentSelector>, 'sometext')
Take a look at the page DOM, if you have a common parent of the <label> and the <textarea> (or <input>), like this
<div>
<label>Business Name</label>
<input />
</div>
you can target that parent in the .contains()
cy.contains('div', 'Business Name')
.find('input') // drill down to the element receiving the text
.should('be.visible') // since there's a toolbar in the mix, wait for visibility
.type('Test LLC')
An alternative might be to use .closest()
cy.contains('Business Name') // gives you the label
.closest('input') // nearby element receiving the text
.should('be.visible') // wait for visibility
.type('Test LLC')
Here's one more way, making use of the label's "for" attribute
cy.contains('Business Name') // gives you the label
.invoke('attr', 'for') // which id is it for?
.then(id => {
cy.get('#' + id) // get the actionable element
.should('be.visible') // wait for visibility
.type('Test LLC')
})
Taking a look at the Vuetify form component here which has a similar HTML to yours
<div class="v-text-field__slot">
<label for="input-6" class="v-label theme--light" style="left: 0px; right: auto; position: absolute;">Last name</label>
<input required="required" id="input-6" type="text">
</div>
the same test code you have succeeds on the sample code
cy.contains('Last name')
.click({force: true})
.type("Test LLC"); // text appears in the input
but if I simulate the covering toolbar, it fails with the same error you have.
Adding .type("Test LLC", {force: true}) also fails with a different error
cy.contains('Last name')
.click({force: true})
.type("Test LLC", {force: true});
cy.type() failed because it requires a valid typeable element.
Using the parent contains to find the "typeable element" and applying force: true option works
cy.contains('div', 'Business Name')
.find('input')
.should('be.visible')
.type("Test LLC", {force: true})
This assumes the toolbar remains static and does not animate away, in which case it would work without the force: true option.
I have an app that is using ngx-bootstrap to show a tooltip on mouseover. I want to test that the content, which is dynamically added, shows properly. In order to do this I have a test that looks like this:
it(shows the right tooltip', fakeAsync(() => {
fixture.debugElement.query(By.directive(TooltipDirective))
.triggerEventHandler('mouseover', null);
tick();
fixture.detectChanges();
expect(fixture.debugElement.query(By.css('.tooltip-inner')).nativeElement)
.toBe('the tooltip text');
}
This results in an error that indicates that fixture.debugElement.query(By.css('.tooltip-inner')): "Cannot read property 'nativeElement' of null"
If I print out the content of fixture.debugElement.nativeElement I get this:
<div id="root1" ng-version="5.2.9">
<my-component>
<div ng-reflect-tooltip="the tooltip text">
<img src="images/test.png">
</div>
<bs-tooltip-container role="tooltip" class="tooltip in tooltip-right">
<div class="tooltip-arrow arrow"></div>
<div class="tooltip-inner">the tooltip text</div>
</bs-tooltip-container>
<my-component>
</div>
The important take away is that the html exists - it is just not accessible by the DebugElement.query.
My current solution to get the spec passing is to change the expect to:
expect(fixture.debugElement.nativeElement.textContent.trim())
.toBe('the tooltip text');
This works, but it is a hack that will fall to pieces if I run into a similar situation with multiple tooltips (for example). Has anyone been able to handle this in a better way? Am I not setting this spec up correctly?
I've got a hidden file input field like this:
<input type="file" id="fileToUpload-1827" multiple="" onchange="angular.element(this).scope().setFiles(this)" data-upload-id="1827" class="hidden-uploader">
I'd like to be able to upload files to this. The normal way to do this in protractor would be to do:
ptor.findElement(protractor.By.css('.file-upload-form input')).sendKeys('/path/to/file')
But because the input element isn't visible, I get an error.
I tried:
ptor.driver.executeScript("return $('.file-upload-form input')[0].removeClass('hidden-uploader');").then(function () {
ptor.findElement(protractor.By.css('.file-upload-form input')).sendKeys('hello');
})
But got the error
UnknownError: $(...)[0].removeClass is not a function
It seems ridiculous to have to use executeScript to make an element visible so that I can upload a file, is there a better way? If not, how do I unhide the element?
The full html for the input form is:
<form class="file-upload-form ng-scope ng-pristine ng-valid" ng-if="ajaxUploadSupported">
<strong>Drag files here to upload</strong> or
<label for="fileToUpload-1953">
<div class="btn btn-info select-file-btn">
Click to Select
</div>
</label>
<div>
<input type="file" id="fileToUpload-1953" multiple="" onchange="angular.element(this).scope().setFiles(this)" data-upload-id="1953" class="hidden-uploader">
</div>
</form>
The only way I could find to do this in the end was to use javascript to make the input element visible.
So I have a function unhideFileInputs:
var unhideFileInputs = function () {
var makeInputVisible = function () {
$('input[type="file"]').removeClass('hidden-uploader');
};
ptor.driver.executeScript(makeInputVisible);
}
This contains the function 'makeInputVisible' which is executed in the browser when I call ptor.driver.executeScript(makeInputVisible). Because I know my page contains jQuery I can use the jQuery removeClass method to unhide my file input element.
To see more on how to execute javascript in the browser using webdriver, see the answer to this question (although the answer uses executeAsyncScript rather than executeScript).
To add on user2355213s answer for the more current releases of protractor. ptor is obsolote and instead browser should be used. Also, executeScript() expects a string as parameter. So I ended up using
browser.executeScript('$(\'input[type="file"]\').attr("style", "");');
as my visibility setting was directly applied to the element. Of course, you can also use
browser.executeScript('$(\'input[type="file"]\').removeClass("hidden-uploader");');
depending on your HTML/CSS.
I've got a very simple function, of replacing the innerHTML of a element. I've been trying to debug this for hours but simply can't, and it's infuriating.
When called from a button press the JavaScript (as follows) works well, but when called from another function it doesn't work. I am totally lost as to why this might be, and its a fairly core part of my app
// This loaded function in my actual code is a document listener
// checking for when Cordova is loaded which then calls the loaded function
loaded();
function loaded() {
alert("loaded");
changeText();
}
function changeText() {
alert("started");
document.getElementById('boldStuff').innerHTML = 'Fred Flinstone';
}
Button press and HTML to replace
<div id="main">
<input type='button' onclick='changeText()' value='Change Text'/>
<p>Change this text >> <b id='boldStuff'> THIS TEXT</b> </p>
</div>
It is also here in full on JSFiddle
You are already changed the innerHTML by calling the function loaded(); on onLoad.
Put this in an empty file and same as .html and open with browser and try. I have commented the function loaded();. Now it will be changed in onclick.
<div id="main">
<input type='button' onclick='changeText();' value='Change Text'/>
<p>Change this text >> <b id='boldStuff'> THIS TEXT</b> </p>
</div>
<script>
//loaded();
function loaded() {
alert("loaded");
changeText();
}
function changeText() {
alert("started");
document.getElementById('boldStuff').innerHTML = 'Fred Flinstone';
}
</script>
The problem here is, that the element you're trying to manipulate is not yet existing when you are calling the changeText() function.
To ensure that the code is only executed after the page has finished loading (and all elements are in place) you can use the onload handler on the body element like this:
<body onload="loaded();">
Additionally you should know, that it's very bad practice to manipulate values by using the innerHTML property. The correct way is to use DOM Manipulations, maybe this can help you.
You script loads before the element (boldStuff) is loaded,
Test Link - 1 - Put the js in a seperate file
Test Link - 2 - put the js at the very end, before closing the <body>