web-dev-qa-db-ja.com

親が非表示になっているアクセス要素-cypress.io

質問はタイトルにあるとおりです。つまり、親が非表示になっている要素にアクセスします。問題は cypress.io docs のとおりです:

次の場合、要素はhiddenと見なされます。

  • 幅または高さが0です。
  • そのCSSプロパティ(または祖先)は可視性です:非表示。
  • そのCSSプロパティ(または祖先)は表示されます:なし
  • そのCSSプロパティは位置です:修正され、画面外または隠されます。

しかし、私が使用しているコードでは、親が非表示になっている要素をクリックする必要がありますが、要素自体は表示されています

したがって、要素をクリックしようとするたびに、エラーの読み取りがスローされます。

CypressError:再試行がタイムアウトしました: '<mdc-select-item#mdc-select-item-4.mdc-list-item>'が 'visible'であることが期待されています

この要素 '<mdc-select-item#mdc-select-item-4.mdc-list-item>'は、親 '<mdc-select-menu.mdc-simple-menu.mdc-select__menu>'のため表示されませんCSSプロパティがあります: 'display:none'

enter image description here

私が使用している要素は、pugで記述されたdropdown itemです。要素は angular-mdc-web で定義されたコンポーネントであり、ドロップダウンメニューにmdc-selectを使用し、その要素(アイテム)にmdc-select-itemを使用します。アクセス。

同様の構造のサンプルコード:

//pug
mdc-select(placeholder="installation type"
            '[closeOnScroll]'="true")
    mdc-select-item(value="false") ITEM1
    mdc-select-item(value="true") ITEM2

上記のITEM1は、アクセスする必要がある要素です。これは、次のようにcypress.ioで行います。

//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();

{force:true}でアイテムを強制的にクリックしようとしましたが、うまくいきませんでした。親{enter}mdc-selectキープレスを使用して項目を選択しようとしましたが、再びスローされるので運がありません:

CypressError:cy.type()は、textareaまたは:textでのみ呼び出すことができます。あなたの主題は次のとおりです:<mdc-select-label class = "mdc-select__selected-text">選択... </ mdc-select-label>

selectコマンド も使用してみましたが、Cypressエンジンが要素をselect要素として識別できないため、不可能です(内部の仕組みではないため)異なっています)。それがスローされます:

CypressError:cy.select()はでのみ呼び出すことができます。あなたの主題は次のとおりです:<mdc-select-label class = "mdc-select__selected-text">選択... </ mdc-select-label>

問題は、mdc-select-menuの親であるmdc-select-itemdisplay:noneのプロパティを持っていることですドロップダウン項目を開いたときの内部計算。

enter image description here

このプロパティはdisplay:flexに上書きされますが、これは役に立ちません。

enter image description here

すべてアイデアから。これはSeleniumでは機能しますが、cypress.ioでは機能しません。他のフレームワークに移行したり、UIコードを変更したりする以外に、状況をハックする可能性のある手がかりはありますか?

15
Kaushik NP

多くの歯ぎしりの後、私は答えがあると思います。

根本的な原因は_mdc-select-item_が_display:flex_を持っているためだと思います。これにより、親の境界を超えることができます(厳密に言うと、チュートリアルを正しく覚えていれば、これはディスプレイフレックスの間違ったアプリケーションのように感じます。しかしながら...)。

サイプレスは、可視性を決定するときに多くの親チェックを行います。 visibility.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."
_

しかし、.should('be.visible')を使用すると、実際には子を表示できても、親のプロパティが子の可視性チェックに失敗するという問題が発生します。
別のテストが必要です。

回避策

Ref jquery.js 、これは要素自体の可視性の定義の1つです(親プロパティは無視されます)。

_jQuery.expr.pseudos.visible = function( elem ) {
  return !!( elem.offsetWidth || elem.offsetHeight || elem.getClientRects().length );
}
_

そのため、これを代替の基礎として使用する場合があります。

_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
      });
    });

  })
_

脚注-「then」(または「each」)の使用

サイプレスで通常アサーションを使用する方法は、コマンドチェーンを使用する方法です。コマンドチェーンは、基本的に、テストされる要素をラップし、再試行やDOM変更の待機などを処理します。

ただし、この場合、標準の可視性アサーション.should('be.visible')とページの作成に使用されたフレームワークとの間に矛盾があるため、then(fn)ref )を使用します。ラップされていないDOMにアクセスします。次に、stand jasmine expect構文を使用して、独自のバージョンの可視性テストを適用できます。

.should(fn)で関数を使用することもできます。これも機能します

_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
  });
});
_

shouldの代わりにthenを使用しても、可視性テストに違いはありませんが、shouldバージョンは関数を複数回再試行できるため、clickテストでは使用できません(たとえば)。

ドキュメントから、

.then()と.should()/。and()の違いは何ですか?

.then()を使用すると、生成されたサブジェクトをコールバック関数で使用できるようになるだけで、いくつかの値を操作したりアクションを実行したりする必要がある場合に使用する必要があります。

一方、.should()または.and()でコールバック関数を使用する場合、アサーションがスローされなくなるまでコールバック関数を再実行する特別なロジックがあります。 .should()または.and()コールバック関数で、複数回実行したくない副作用に注意する必要があります。

Chaiアサーションを拡張することで問題を解決することもできますが、これに関するドキュメントは広範ではないため、作業が増える可能性があります。

8
Richard Matsen

ドキュメントから、 Cypress select syntax 、構文は

cy.get('mdc-select-item').select('ITEM1')

{force: true}も必要になる場合があります。独自のテストの例については、ここ select_spec.coffee を参照してください。

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})
1
Richard Matsen

私はこのトピックに出くわしましたが、あなたの例を実行できませんでした。だから私は少し試しました、そして私の最終的な解決策はこれです。多分他の誰かもこれを必要としています。 TypeScriptを使用していることに注意してください。

最初:カスタムコマンドを定義

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);
      });
});

TODOに注意してください。私の場合、2つの境界ボックスを持つボタンをターゲットにしていました。最初は高さと幅が0なので、2番目のものを選択する必要があります。必要に応じて調整してください。

2番目:使用する

cy.wrap("#some_id_or_other_locator").isVisible();
1
Josef Biehler

利便性と再利用性のために、Richard MatsenとJosef Biehlerの答えを混ぜる必要がありました。

コマンドを定義する

// Access element whose parent is hidden
Cypress.Commands.add('isVisible', {
  prevSubject: true
}, (subject) => {
  const isVisible = (elem) => !!(
    elem.offsetWidth ||
    elem.offsetHeight ||
    elem.getClientRects().length
  )
  expect(isVisible(subject[0])).to.be.true
})

コンテナからチェーンできるようになりました

describe('Testing select options', function() {
  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").isVisible()
  });
});
1
BTL

BTLの答えを少し広げるために、誰かがエラーに直面した場合-TypeScriptの_Property 'isVisible' does not exist on type 'Chainable<JQuery<HTMLElement>>_は、それを回避するためにサイプレスの_commands.ts_の上部に追加したものです-

_declare global {
    namespace Cypress {
        interface Chainable {
            isVisible;
        }
    }
} 
_

ジョセフビーラーの回答のように、chaiアサーションエラーが予想され、インポートしたくない場合は、expect(isVisible(subject[0])).to.be.trueassert.True(isVisible(subject[0]));に置き換えている可能性があります。

0
apun

関連する問題:サイプレスは表示スタイルがあるため、タブ要素を見つけることができませんでした:(ページに表示されていても)なし

私の回避策:サイプレスはテキストを照合してクリックすることでタブをターゲティングできます

    cy.get("[data-cy=parent-element]").contains("target text").click();
0
Erica Handelman