Cypress - element is detached from the DOM error - javascript

I tested our (nearly all of the components are created by DevExtreme) web application but I have a strange re-rendering problem.
I posted a picture of the error message. As you can see the "element exists" and "element is visible" assertions both pass. The next action command click, type, clear sometimes gives me this error.
I watched the network but there are no suspicious API requests.
How can I fix it?
cy.get("div.c-title.pe-2.me-2.active")
.parent()
.find("[name='Property Unsafe']")
.parent()
.find(".dx-item-content")
.contains("Yes")
.scrollIntoView()
.should("be.visible")
.should("be.exist")
.click();
You can see which components are I working on it. (I get similar errors frequently from every components not just dropdown or text input)
Hello, the issue is still the same. Not working.
As you can see the latest updates here: I used Cypress.dom.isAttached($el) I re-queried until the element was attached into dom with Cypress recurse library. Still the result same.
recurse(
() =>
cy.get(
"#txtErrormeter_number > .dx-texteditor-container > .dx-texteditor-input-container > .dx-texteditor-input"
),
($el) => Cypress.dom.isAttached($el),
{
debugLog: true,
log: true,
limit: 50, // max number of iterations
timeout: 10000, // time limit in ms
delay: 250, // delay before next iteration, ms
}
).then(() => {
cy.get(
"#txtErrormeter_number > .dx-texteditor-container > .dx-texteditor-input-container > .dx-texteditor-input"
).then(($el) => {
if (Cypress.dom.isAttached($el)) {
cy.log("aattacchheeed");
cy.wrap($el)
.clear({ force: true })
.type(variables.meterId, { force: true });
} else {
cy.log("NOOOOOOTTTTTTTTTT aattacchheeed");
}
});
});

The Cypress log is saying you have a .click() two lines above the scrollIntoView(), but it is not in the posted test code.
You should split the long command after that first click, it is most likely causing the re-render.
Whatever you selected above click #1 should be selected again after it.
I can't tell what the context is, only a mention of DevExtreme. If you provide more information about the control (select, input, dropdown) it would be more useful.
Also the correct check for attachment is isAttached() not visible or exist.
.then(($el) => {
expect(Cypress.dom.isAttached($el).to.eq(true)
})

Related

unable to access html element using cypress

I am trying to test the login functionalities using cypress, but for some reason I am unable to access the input text-box.
It shows Timed out retrying after 4000ms: Expected to find element: runner container, but never found it.
describe("User authentication", () => {
beforeEach(() => {
cy.visit("https://lmflf.com");
});
it("it takes to the correct page", () => {
cy.get('[data-field=password]')
});
});
I could see a shadow DOM in your webpage and in that case you have to use .shadow()(Cypress Docs). Your code would look something like:
cy.get('view-login').shadow().find('input[data-field="username"]').type('username', {force: true})
cy.get('view-login').shadow().find('input[data-field="password"]').type('password', {force: true})
cy.get('view-login').shadow().find('input[value="personal"]').click()
cy.get('view-login').shadow().find('.ViewLogin__button').click()

Cypress refresh page unless text is present?

I'm trying to write a test but I've got to a point where I need to wait for some text to become visible.
Basically up to this point I have uploaded a file and then navigated to another page, this new page just says "Processing.." when the file is finished being checked it will say "Success!"
The problem is this page isn't calling an API every x seconds to update the text it just does it once on a page load so I want to check if the page says "Processing.." call cy.reload() check again, call cy.wait(1000) reload and check again etc until the page says "Success!".
My issue is how do I check if text is present without it being an assert and failing the test?
This has been asked a few times, and the short answer is to use jQuery, e.g
cy.visit(...)
cy.wait(1000) // in case the page is initially slow to load
const text = Cypress.$('div').text();
if (text.trim().startsWith('Processing') {
cy.wait(1000)
cy.reload()
}
That gets you one reload, but I guess you want to repeat that until 'Success...', for which recursion seems to be the only way to repeat stuff until the right DOM appears.
function waitForText(attempt = 0) {
if (attempt > 100) { // choose cutoff point, must have this limiter
throw 'Failed'
}
cy.wait(1000);
const text = Cypress.$('div').text();
if (text.trim().startsWith('Processing') {
cy.reload();
waitForText(attempt + 1)
}
}
cy.visit(...)
waitForText()
I had the same issue and authored the following based on other answers in SO (nobody had quite what I wanted).
Add this to commands.js to make it available to all tests.
Cypress.Commands.add('reloadUntilFound', (url, selector, retries=3, retry_wait=1000) => {
if(retries==0){
throw `exhausted retries looking for ${selector} on ${url}`
}
cy.visit(url)
cy.get('body').then(body => {
let msg = `url:${url} selector:${selector} retries:${retries}`
if (body.find(selector).length===1) {
console.log(`found ${msg}`)
}else{
console.log(`NOT found ${msg}`)
cy.wait(retry_wait)
cy.reloadUntilFound(url, selector, retries - 1)
}
})
})
Invoke it as follows.
cy.reloadUntilFound('/transactions', 'td:contains($4.44)')

Is there any way to hide a log in cypress?

I'd like to know if there's a way to not show a log in Cypress, for example.
If I make a test to log into any application, when it types the password:
cy.get(#id).type(password)
and the test is executed, the password value appears in the log.
Is there any way of stopping this?
as per docs, this should work:
cy.get("#id").type( password, { log: false });
If you want to hide the command log sidebar altogether then set the ENV CYPRESS_NO_COMMAND_LOG to 1 before running Cypress this way:
CYPRESS_NO_COMMAND_LOG=1 cypress run
Documentation
It works for the Cypress v10.9.0
With some older versions (8.7.0, 9.6.0) it may not work.
CYPRESS_NO_COMMAND_LOG=1 leaves faulty empty space
fix: respect CYPRESS_NO_COMMAND_LOG in env
We can hide the details of each step inside cy.getIframeBody code by disabling internal commands' logging.
Cypress.Commands.add('getIframeBody', () => {
// get the iframe > document > body
// and retry until the body element is not empty
cy.log('getIframeBody')
return cy
.get('iframe[data-cy="the-frame"]', { log: false })
.its('0.contentDocument.body', { log: false }).should('not.be.empty')
// wraps "body" DOM element to allow
// chaining more Cypress commands, like ".find(...)"
// https://on.cypress.io/wrap
.then((body) => cy.wrap(body, { log: false }))
})

Protractor not finding the same element after a promise

I'm trying to obtain the text from a element in protractor, and after doing something with the text, i want to click on the same element. This is what i have
HTML:
<span class="span-user" id="spanuser"> {{user?.login}}</span>
Protractor test:
describe('Login OK with correct pass', () => {
it('should login successfully with admin account', () => {
// logging
username.clear();
username.sendKeys('admin');
password.clear();
password.sendKeys('admin');
element(by.css('button[type=submit]')).click();
// check if the username <span> has the current login username
const expect2 = /admin/;
const spanuser = element(by.css('span#spanuser'));
spanuser.getText().then((value) => {
console.log('inside');
console.log(value ? value : 'no value');
expect(value).toMatch(expect2);
});
// then i try to click on the same span, to do some stuff
spanuser.click().then(() => {
console.log('It has been pressed!');
});
});
});
The first part works just fine, it gets the test and it passes the expect, but when i try to do the click() function on the span, i get the following error:
Failed: Timed out waiting for asynchronous Angular tasks to finish after 5 seconds. This may be because the current page is not an Angular application. Please see the FAQ for moredetails: https://github.com/angular/protractor/blob/master/docs/timeouts.md#waiting-for-angular
While waiting for element with locator - Locator: By(css selector,
span#spanuser)
What have i tried:
browser.waitForAngular() before spanuser.click()
browser.wait(10000) before spanuser.click()
also i have a waitForAngular() in the beforeAll function.
Does anyone has a idea on this? It doesn't really make any sense to me, why wouldn't find the same element that has already found before?
Thanks a lot!
add browser.waitForAngularEnabled('false') in onPrepare of protractor conf.js
onPrepare: function() {
browser.waitForAngularEnabled('false')
// if you set this in onPrepare(), it will be a global setting, will
// effect all script, so you no need to set it in other place.
}
use the jasmin defaulttimeoutinterval in config instead of manual sleep
jasmineNodeOpts: {
defaultTimeoutInterval: 250000
},
allScriptsTimeout: 180000

Firebase array item is removed and immediately auto-added back (with AngularFire)

I am trying to remove an item from $firebaseArray (boxes).
The remove funcion:
function remove(boxJson) {
return boxes.$remove(boxJson);
}
It works, however it is immediately added back:
This is the method that brings the array:
function getBoxes(screenIndex) {
var boxesRef = screens
.child("s-" + screenIndex)
.child("boxes");
return $firebaseArray(boxesRef);
}
I thought perhaps I'm holding multiple references to the firebaseArray and when one deletes, the other adds, but then I thought firebase should handle it, no?
Anyway I'm lost on this, any idea?
UPDATE
When I hack it and delete twice (with a timeout) it seems to work:
function removeForce(screenIndex, boxId) {
setTimeout(function () {
API.removeBox(screenIndex, boxId);
}, 1000);
return API.removeBox(screenIndex, boxId);
}
and the API.removeBox:
function removeBox(screenIndex, boxId) {
var boxRef = screens
.child("s-" + screenIndex)
.child("boxes")
.child(boxId);
return boxRef.remove();
}
When you remove something from firebase it is asynchronous. Per the docs the proper way to remove an item is from firebase, using AngularFire is:
var obj = $firebaseObject(ref);
obj.$remove().then(function(ref) {
// data has been deleted locally and in the database
}, function(error) {
console.log("Error:", error);
});
$remove() ... Removes the entire object locally and from the database. This method returns a promise that will be fulfilled when the data has been removed from the server. The promise will be resolved with a Firebase reference for the exterminated record.
Link to docs: https://www.firebase.com/docs/web/libraries/angular/api.html#angularfire-firebaseobject-remove
The most likely cause is that you have a security rules that disallows the deletion.
When you call boxes.$remove Firebase immediately fires the child_removed event locally, to ensure the UI is updated quickly. It then sends the command to the Firebase servers to check it and update the database.
On the server there is a security rule that disallows this deletion. The servers send a "it failed" response back to the client, which then raises a child_added event to fix the UI.
Appearantly I was saving the items again after deleting them. Clearly my mistake:
function removeSelected(boxes) {
var selectedBoxes = Selector.getSelectedBoxes(boxes);
angular.forEach(selectedBoxes, function (box) {
BoxManager.remove(box);
});
Selector.clearSelection(boxes, true);
}
In the clearSelection method I was updating a field on the boxes and saved them again.
Besides the obvious mistake this is a lesson for me on how to work with Firebase. If some part of the system keeps a copy of your deleted item, saving it won't produce a bug but revive the deleted item.
For those, who have the similar issue, but didn't solve it yet.
There are two methods for listening events: .on() and .once(). In my case that was the cause of a problem.
I was working on a migration procedure, that should run once
writeRef
.orderByChild('text_hash')
.equalTo(addItem.text_hash)
.on('value', val => { // <--
if (!val.exists()) {
writeRef.push(addItem)
}
});
So the problem was exactly because of .on method. It fires each time after a data manipulation from FB's console.
Changing to .once solved that.

Categories