La question est telle que donnée dans le titre, c'est-à-dire d'accéder à l'élément dont le parent est caché. Le problème est que, selon les documents cypress.io :
Un élément est considéré caché si:
- Sa largeur ou hauteur est 0.
- Sa propriété CSS (ou ancêtres) est visibilité: masqué.
- sa propriété CSS (ou ses ancêtres) est display: none.
- Sa propriété CSS est position: fixed et elle est masquée ou masquée.
Mais le code avec lequel je travaille nécessite que je clique sur un élément dontle parent est caché, l'élément lui-même étant visible.
Ainsi, chaque fois que j'essaie de cliquer sur l'élément, une erreur est générée:
CypressError: Nouvelles tentatives ayant expiré: attendu '<<Mdc-select-item # mdc-select-item-4.mdc-list-item>' pour être 'visible'
Cet élément '<mdc-select-item # mdc-select-item-4.mdc-list-item>' est Non visible car son parent '<Mdc-select-menu.mdc -simple-menu.mdc-select__menu> 'possède la propriété CSS: ' display: none '
L'élément sur lequel je travaille est un dropdown item
, qui est écrit dans pug
. L'élément est un composant défini dans angular-mdc-web , qui utilise le mdc-select
pour le menu déroulant et le mdc-select-item
pour ses éléments (éléments), ce à quoi je dois accéder.
Un exemple de code de structure similaire:
//pug
mdc-select(placeholder="installation type"
'[closeOnScroll]'="true")
mdc-select-item(value="false") ITEM1
mdc-select-item(value="true") ITEM2
Dans ce qui précède, ITEM1
est l'élément auquel je dois accéder. Ce que je fais dans cypress.io
comme suit:
//cypress.io
// click on the dropdown menu to show the dropdown (items)
cy.get("mdc-select").contains("installation type").click();
// try to access ITEM1
cy.get('mdc-select-item').contains("ITEM1").should('be.visible').click();
J'ai essayé avec {force:true}
de forcer l'élément à cliquer, mais pas de chance. J'ai essayé de sélectionner les éléments à l'aide de {enter}
sur la touche mdc-select
du parent, mais là encore, aucune chance car elle jette:
CypressError: cy.type () ne peut être appelé que sur textarea ou: text. Votre sujet Est un: <mdc-select-label Class = "mdc-select__selected-text"> Sélectionnez ... </ mdc-select-label>
Également essayé d'utiliser la commande select
, mais ce n'est pas possible car le moteur Cypress n'est pas en mesure d'identifier l'élément en tant qu'élément select
(car ce n'est pas le cas, le fonctionnement interne est différent). Il jette:
CypressError: cy.select () ne peut être appelé que sur un. Votre sujet Est un: <mdc-select-label Class = "mdc-select__selected-text"> Sélectionnez ... </ mdc-select-label>
Le problem est que le mdc-select-menu
qui est le parent du mdc-select-item
a la propriété display:none
selon certains calculs internes lors de l’ouverture des éléments déroulants.
Cette propriété est écrasée en display:flex
, mais cela n’aide en rien.
Tout en idées. Cela fonctionne dans Selenium
, mais pas avec cypress.io
. Avez-vous une idée de ce que pourrait être un bidouillage possible pour la situation autre que de passer à d'autres frameworks ou de changer le code de l'interface utilisateur?
Après beaucoup de grincements de dents, je pense avoir une réponse.
Je pense que la cause fondamentale est que mdc-select-item
a display:flex
, ce qui lui permet de dépasser les limites de ses parents (à proprement parler, cela ressemble à une mauvaise application de flex d'affichage, si je me souviens bien du tutoriel, cependant ...).
Cypress effectue beaucoup de vérifications par les parents lors de la détermination de la visibilité, voir visibilité.coffee ,
## WARNING:
## developer beware. visibility is a sink hole
## that leads to sheer madness. you should
## avoid this file before its too late.
...
when $parent = parentHasDisplayNone($el.parent())
parentNode = $elements.stringify($parent, "short")
"This element '#{node}' is not visible because its parent '#{parentNode}' has CSS property: 'display: none'"
...
when $parent = parentHasNoOffsetWidthOrHeightAndOverflowHidden($el.parent())
parentNode = $elements.stringify($parent, "short")
width = elOffsetWidth($parent)
height = elOffsetHeight($parent)
"This element '#{node}' is not visible because its parent '#{parentNode}' has CSS property: 'overflow: hidden' and an effective width and height of: '#{width} x #{height}' pixels."
Mais, lorsque vous utilisez .should('be.visible')
, nous sommes bloqués avec les propriétés parent qui échouent à la vérification de la visibilité de l'enfant, même si nous pouvons réellement voir l'enfant.
Nous avons besoin d’un autre test.
Ref jquery.js , il s'agit d'une définition de la visibilité de l'élément lui-même (en ignorant les propriétés parent).
jQuery.expr.pseudos.visible = function( elem ) {
return !!( elem.offsetWidth || elem.offsetHeight || elem.getClientRects().length );
}
nous pourrions donc utiliser cela comme base pour une alternative.
describe('Testing select options', function() {
// Change this function if other criteria are required.
const isVisible = (elem) => !!(
elem.offsetWidth ||
elem.offsetHeight ||
elem.getClientRects().length
)
it('checks select option is visible', function() {
const doc = cy.visit('http://localhost:4200')
cy.get("mdc-select").contains("installation type").click()
//cy.get('mdc-select-item').contains("ITEM1").should('be.visible') //this will fail
cy.get('mdc-select-item').contains("ITEM1").then (item1 => {
expect(isVisible(item1[0])).to.be.true
});
});
it('checks select option is not visible', function() {
const doc = cy.visit('http://localhost:4200')
cy.get("mdc-select").contains("installation type").click()
cy.document().then(function(document) {
const item1 = document.querySelectorAll('mdc-select-item')[0]
item1.style.display = 'none'
cy.get('mdc-select-item').contains("ITEM1").then (item => {
expect(isVisible(item[0])).to.be.false
})
})
});
it('checks select option is clickable', function() {
const doc = cy.visit('http://localhost:4200')
cy.get("mdc-select").contains("installation type").click()
//cy.get('mdc-select-item').contains("ITEM1").click() // this will fail
cy.get('mdc-select-item').contains("ITEM1").then (item1 => {
cy.get('mdc-select-item').contains("ITEM2").then (item2 => {
expect(isVisible(item2[0])).to.be.true //visible when list is first dropped
});
item1.click();
cy.wait(500)
cy.get('mdc-select-item').contains("ITEM2").then (item2 => {
expect(isVisible(item2[0])).to.be.false // not visible after item1 selected
});
});
})
Note de bas de page - Utilisation de 'then' (ou 'each')
La manière dont vous utilisez normalement l'assertion dans cyprès se fait via des chaînes de commandes, qui englobent les éléments testés et traitent des tâches telles que les tentatives et l'attente des modifications du DOM.
Cependant, dans ce cas, nous avons une contradiction entre l'assertion de visibilité standard .should('be.visible')
et le cadre utilisé pour générer la page. Nous utilisons donc then(fn)
( ref ) pour accéder au DOM non enveloppé. Nous pouvons ensuite appliquer notre propre version du test de visibilité en utilisant la syntaxe de stand jasmine expect.
Il s'avère que vous pouvez également utiliser une fonction avec .should(fn)
, cela fonctionne aussi
it('checks select option is visible - 2', function() {
const doc = cy.visit('http://localhost:4200')
cy.get("mdc-select").contains("installation type").click()
cy.get('mdc-select-item').contains("ITEM1").should(item1 => {
expect(isVisible(item1[0])).to.be.true
});
});
L'utilisation de should
au lieu de then
ne fait aucune différence dans le test de visibilité, mais notez que la version should
peut réessayer la fonction plusieurs fois; elle ne peut donc pas être utilisée avec le test click
(par exemple).
De la docs,
Quelle est la différence entre .then () et .should () /. Et ()?
L'utilisation de .then () vous permet simplement d'utiliser le sujet renvoyé dans une fonction de rappel et doit être utilisée lorsque vous devez manipuler certaines valeurs ou effectuer certaines actions.
Lorsque vous utilisez une fonction de rappel avec .should () ou .and (), par contre, il existe une logique spéciale pour réexécuter la fonction de rappel jusqu'à ce qu'aucune assertion ne soit renvoyée à l'intérieur. Vous devez faire attention aux effets secondaires dans une fonction de rappel .should () ou .and () que vous ne voudriez pas exécuter plusieurs fois.
Vous pouvez également résoudre le problème en étendant les assertions chai, mais la documentation à ce sujet n’est pas exhaustive, elle risque donc d’être plus laborieuse.
Dans la documentation, Cypress select syntax , la syntaxe est la suivante:
cy.get('mdc-select-item').select('ITEM1')
Vous aurez peut-être aussi besoin du {force: true}
. Voir ici select_spec.coffee pour des exemples de leurs propres tests, par exemple
it "can forcibly click even when element is invisible", (done) ->
select = cy.$$("select:first").hide()
select.click -> done()
cy.get("select:first").select("de_dust2", {force: true})
Je suis tombé sur ce sujet mais je n'ai pas été capable d'exécuter votre exemple. J'ai donc essayé un peu et ma solution finale est la suivante. peut-être que quelqu'un d'autre a aussi besoin de ça. S'il vous plaît noter que j'utilise TypeScript.
Premièrement: Définir une commande personnalisée
Cypress.Commands.add("isVisible", { prevSubject: true}, (p1: string) => {
cy.get(p1).should((jq: JQuery<HTMLElement>) => {
if (!jq || jq.length === 0) {
//assert.fail(); seems that we must not assetr.fail() otherwise cypress will exit immediately
return;
}
const elem: HTMLElement = jq[0];
const doc: HTMLElement = document.documentElement;
const pageLeft: number = (window.pageXOffset || doc.scrollLeft) - (doc.clientLeft || 0);
const pageTop: number = (window.pageYOffset || doc.scrollTop) - (doc.clientTop || 0);
let elementLeft: number;
let elementTop: number;
let elementHeight: number;
let elementWidth: number;
const length: number = elem.getClientRects().length;
if (length > 0) {
// TODO: select correct border box!!
elementLeft = elem.getClientRects()[length - 1].left;
elementTop = elem.getClientRects()[length - 1].top;
elementWidth = elem.getClientRects()[length - 1].width;
elementHeight = elem.getClientRects()[length - 1].height;
}
const val: boolean = !!(
elementHeight > 0 &&
elementWidth > 0 &&
elem.getClientRects().length > 0 &&
elementLeft >= pageLeft &&
elementLeft <= window.outerWidth &&
elementTop >= pageTop &&
elementTop <= window.outerHeight
);
assert.isTrue(val);
});
});
S'il vous plaît noter le TODO. Dans mon cas, je visais un bouton qui a deux zones de bordure. Le premier avec la hauteur et la largeur 0. Je dois donc sélectionner le second. Veuillez adapter cela à vos besoins.
Deuxièmement: Utilisez-le
cy.wrap("#some_id_or_other_locator").isVisible();