For the analysis and solution of this error message, please refer to Cypress's official document .
This error message tells us that the Cypress code we've written is interacting with a "dead" DOM element.
Obviously, in a real usage scenario, a user cannot interact with this type of UI element.
See a practical example:
<body>
<div id="parent">
<button>delete</button>
</div>
</body>
When the button is clicked, it removes itself from the DOM tree:
$('button').click(function () {
// when the <button> is clicked
// we remove the button from the DOM
$(this).remove()
})
The following line of test code causes the error:
cy.get('button').click().parent()
When cypress executes the next parent command, it detects that the button on which the command was applied has been removed from the DOM tree, so it throws an error.
solution:
cy.get('button').click()
cy.get('#parent')
Guidelines for addressing such issues:
Guard Cypress from running commands until a specific condition is met
There are two ways to implement guard:
- Writing an assertion
- Waiting on an XHR
See another example:
Enter clem and select User clementine from the result list, the so-called type head search effect.
The test code is as follows:
it('selects a value by typing and selecting', () => {
// spy on the search XHR
cy.server()
cy.route('https://jsonplaceholder.cypress.io/users?term=clem&_type=query&q=clem').as('user_search')
// first open the container, which makes the initial ajax call
cy.get('#select2-user-container').click()
// then type into the input element to trigger search, and wait for results
cy.get('input[aria-controls="select2-user-results"]').type('clem{enter}')
// select a value, again by retrying command
// https://on.cypress.io/retry-ability
cy.contains('.select2-results__option', 'Clementine Bauch').should('be.visible').click()
// confirm Select2 widget renders the name
cy.get('#select2-user-container').should('have.text', 'Clementine Bauch')
})
Key Points:
Use cy.route to monitor an XHR, can you monitor relative paths here?
Local tests pass, running on CI with the following errors:
How to analyze this problem? The pause operation can be used to pause the test runner.
// first open the container, which makes the initial ajax call
cy.get('#select2-user-container').click().pause()
When we click on the select2 widget, an AJAX call is fired immediately. The test code does not wait for the clem search request to return. It's just looking for the DOM element for "Clementine Bauch".
// first open the container, which makes the initial ajax call
cy.get('#select2-user-container').click()
// then type into the input element to trigger search,
// and wait for results
cy.get('input[aria-controls="select2-user-results"]').type('clem{enter}')
cy.contains('.select2-results__option',
'Clementine Bauch').should('be.visible').click()
The above test might pass most of the time when run locally, but on CI it might fail quite often because of slower network calls and possibly slower browser DOM updates. Here's how tests and applications can get into race conditions that cause "detached element" errors.
- Test the click widget
- The Select2 widget triggers the first search Ajax call. On CI, this call may be slower than expected.
- The test code enters "clem" to search, which triggers a second AJAX call.
- The Select2 widget receives the response to the first search Ajax call with ten usernames, one of which is "Clementine Bauch". These names are added to the DOM
Then test search for the visible selection "Clementine Bauch" - and find it in the initial user list.
The test runner will then want to click on the found element. Note the race condition here. When the second search Ajax call "term=clem" returns from the server. The Select2 widget removes the current list of options and displays only the two found users: "Clementine Bauch" and "Clementina DuBuque".
The test code then executes the click of the Clem element.
Cypress is throwing an error because the DOM element with the text "Clementine Bauch" that it wants to click is no longer linked to the HTML document; it has been removed from the document by the application, and Cypress is still referencing the element.
This is the source of the problem.
The following code can artificially make the race condition always fire:
cy.contains('.select2-results__option',
'Clementine Bauch').should('be.visible')
.pause()
.then(($clem) => {
// remove the element from the DOM using jQuery method
$clem.remove()
// pass the element to the click
cy.wrap($clem)
})
.click()
Now that the root cause of the race condition trigger is understood, there is a direction to correct it.
We want the test to always wait for the application to complete its operation before continuing.
solution:
cy.get('#select2-user-container').click()
// flake solution: wait for the widget to load the initial set of users
cy.get('.select2-results__option').should('have.length.gt', 3)
// then type into the input element to trigger search
// also avoid typing "enter" as it "locks" the selection
cy.get('input[aria-controls="select2-user-results"]').type('clem')
We use cy.get('XXX').should('') to ensure that the AJAX corresponding to the initial user list must be returned to the server before executing the clem input, otherwise the length of select2-options must be less than 3.
When the test types "clem" into the search box, the application triggers an Ajax call that returns a subset of users. So the test needs to wait for the new collection to be displayed - otherwise it will find "Clementine Bauch" from the initial list and encounter a detached error. We know that there are only two users matching "clem", so we can double-check the displayed number of users to wait for the application.
/ then type into the input element to trigger search, and wait for results
cy.get('input[aria-controls="select2-user-results"]').type('clem')
// flake solution: wait for the search for "clem" to finish
cy.get('.select2-results__option').should('have.length', 2)
cy.contains('.select2-results__option', 'Clementine Bauch')
.should('be.visible').click()
// confirm Select2 widget renders the name
cy.get('#select2-user-container')
.should('have.text', 'Clementine Bauch')
force:true
is blindly passed in the click call, new problems may be introduced.
More Jerry's original articles, all in: "Wang Zixi":
**粗体** _斜体_ [链接](http://example.com) `代码` - 列表 > 引用
。你还可以使用@
来通知其他用户。