Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

isDisplayed returns false for a visible element in Protractor

EDIT #4: Breakthrough!!

I started recursively going through the parent nodes and returning the same values as below. One of the parents--the inner mat-drawer-container--also returned false for isDisplayed (all the others returned true, which seems odd).

Looking at that node, it turns out that it's the only tag on the site to have the CSS of display: contents. Removing that causes the button in question--and everything else below it--to return true for isDisplayed. Heck, Protractor can even click the button and I can see the expected result in the browser.

Now I suppose the question remains: is this is expected behavior or a bug? It's not as straightforward as there being an ancestor with display: contents applied; I applied it directly to rb-container and Protractor was still able to find the button.


I'm working on end-to-end testing in Protractor for the first time, and I'm running into an issue when trying to test for a button being visible; despite the button element being in the DOM and visible, isDisplayed returns false and my assertion fails.

This is the initial assertion that's been failing:

expect(element(by.css("mat-drawer-content rb-container rb-fab-button[data-qaid='create-button'] > button")).isDisplayed()).toBe(true);

(Yes, the selector is a mess, but I don't have control over the HTML.)

I've used a long browser.sleep() interval to essentially pause the browser in place so I could use the dev tools to inspect the humanly-visible element, and the CSS leads me to believe it should be detected as visible.

After searching for answers and/or bugs to no avail, I logged some information to the console which still leads me to believe that isDisplayed should return true:

[ EDITS #2, #3: Logged some more information on all the direct children of rb-container; only one node is "visible", according to Protractor. ]

  let selector = element.all(by.tagName("mat-drawer-content")).get(0).all(by.css("rb-container > *"));

  selector.count().then(function(selCount) {

    for (let match = 0; match < selCount; match ++) {

      browser.sleep(1000).then(() => {
        let elm = selector.get(match);

        console.log("\n >> " + match + "]");

        elm.getTagName().then(tag => { console.log("tag name:", tag); });
        elm.getCssValue("visibility").then(vis => { console.log("visibility:", vis); });
        elm.getCssValue("display").then(disp => { console.log("display:", disp); });
        elm.getCssValue("opacity").then(opa => { console.log("opacity:", opa); });
        elm.getCssValue("overflow").then(ov => { console.log("overflow:", ov); });
        elm.getAttribute("hidden").then(hid => { console.log("hidden:", hid); });
        elm.getAttribute("class").then(c => { console.log("class:", c)});
        elm.getSize().then(size => { console.log("size:", size); });
        elm.getCssValue("position").then(ov => { console.log("position:", ov); });
        elm.getLocation().then(loc => { console.log("location:", loc); });
        elm.isPresent().then(pres => { console.log("isPresent:", pres); });
        elm.isDisplayed().then(disp => { console.log("isDisplayed:", disp); });
      });

    }

  });

This is what I see logged:

 >> 0]
tag name: div
visibility: visible
display: block
opacity: 1
overflow: auto
hidden: null
class: title-tab dn db-m mediumGreyColor pl4 pv2 overflow-auto
size: { ceil: {},
  clone: {},
  floor: {},
  height: 62,
  round: {},
  scale: {},
  toString: {},
  width: 898 }
position: static
location: { ceil: {},
  clone: {},
  floor: {},
  round: {},
  scale: {},
  toString: {},
  translate: {},
  x: 0,
  y: 74.765625 }
isPresent: true
isDisplayed: false

 >> 1]
tag name: div
visibility: visible
display: block
opacity: 1
overflow: auto
hidden: null
class: player-menu container overflow-auto dn db-m pv2 ng-star-inserted
size: { ceil: {},
  clone: {},
  floor: {},
  height: 64,
  round: {},
  scale: {},
  toString: {},
  width: 834 }
position: static
location: { ceil: {},
  clone: {},
  floor: {},
  round: {},
  scale: {},
  toString: {},
  translate: {},
  x: 32,
  y: 136.765625 }
isPresent: true
isDisplayed: false

 >> 2]
tag name: rb-fab-button
visibility: visible
display: block
opacity: 1
overflow: visible
hidden: null
class: add-fab-button absolute dn db-m ng-star-inserted
size: { ceil: {},
  clone: {},
  floor: {},
  height: 56,
  round: {},
  scale: {},
  toString: {},
  width: 56 }
position: absolute
location: { ceil: {},
  clone: {},
  floor: {},
  round: {},
  scale: {},
  toString: {},
  translate: {},
  x: 762,
  y: 154.765625 }
isPresent: true
isDisplayed: false

 >> 3]
tag name: div
visibility: visible
display: block
opacity: 1
overflow: visible
hidden: null
class: mr4-l w-100-m
size: { ceil: {},
  clone: {},
  floor: {},
  height: 0,
  round: {},
  scale: {},
  toString: {},
  width: 834 }
position: static
location: { ceil: {},
  clone: {},
  floor: {},
  round: {},
  scale: {},
  toString: {},
  translate: {},
  x: 32,
  y: 200.765625 }
isPresent: true
isDisplayed: false

 >> 4]
tag name: rb-table-wrapper
visibility: visible
display: block
opacity: 1
overflow: visible
hidden: null
class: dn db-m
size: { ceil: {},
  clone: {},
  floor: {},
  height: 672,
  round: {},
  scale: {},
  toString: {},
  width: 834 }
position: static
location: { ceil: {},
  clone: {},
  floor: {},
  round: {},
  scale: {},
  toString: {},
  translate: {},
  x: 32,
  y: 200.765625 }
isPresent: true
isDisplayed: true

 >> 5]
tag name: div
visibility: visible
display: block
opacity: 1
overflow: visible
hidden: null
class: container
size: { ceil: {},
  clone: {},
  floor: {},
  height: 0,
  round: {},
  scale: {},
  toString: {},
  width: 834 }
position: static
location: { ceil: {},
  clone: {},
  floor: {},
  round: {},
  scale: {},
  toString: {},
  translate: {},
  x: 32,
  y: 872.5625 }
isPresent: true
isDisplayed: false

 >> 6]
tag name: rb-table-wrapper
visibility: visible
display: none
opacity: 1
overflow: visible
hidden: null
class: db dn-m
size: { ceil: {},
  clone: {},
  floor: {},
  height: 0,
  round: {},
  scale: {},
  toString: {},
  width: 0 }
position: static
location: { ceil: {},
  clone: {},
  floor: {},
  round: {},
  scale: {},
  toString: {},
  translate: {},
  x: 0,
  y: 0 }
isPresent: true
isDisplayed: false

 >> 7]
tag name: div
visibility: visible
display: none
opacity: 1
overflow: auto
hidden: null
class: player-menu container overflow-auto db dn-m ng-star-inserted
size: { ceil: {},
  clone: {},
  floor: {},
  height: 0,
  round: {},
  scale: {},
  toString: {},
  width: 0 }
position: static
location: { ceil: {},
  clone: {},
  floor: {},
  round: {},
  scale: {},
  toString: {},
  translate: {},
  x: 0,
  y: 0 }
isPresent: true
isDisplayed: false

With no hidden attribute set, a display value that isn't "none", visibility set to "visible", and non-zero size dimensions, I would expect isDisplayed to return true.

It's interesting to look at node 4, the only child element for which isDisplayed returns true, and compare it with node 2, the rb-fab-button element I'm trying to access. The only noticeable difference I can see is that rb-fab-button is positioned absolutely; however, the other statically-positioned elements also return false for isDisplayed.

Am I missing something? I'd settle for checking for css visibility, but my next test is to click that button, which errors if the element is not visible.

[ EDIT #1: Added some HTML: ]

<mat-drawer-container _ngcontent-c0="" class="root-container w-100 mat-drawer-container mat-drawer-container-explicit-backdrop" hasbackdrop="true" ng-reflect-has-backdrop="true">
    <div class="mat-drawer-backdrop ng-star-inserted"></div>
    <div tabindex="-1" class="cdk-visually-hidden cdk-focus-trap-anchor"></div>
    <mat-drawer _ngcontent-c0="" class="mobile-drawer dn-m w-80 mat-drawer ng-tns-c2-0 ng-trigger ng-trigger-transform mat-drawer-over ng-star-inserted" tabindex="-1" ng-reflect-mode="over" style="box-shadow: none; visibility: hidden;">
      <!-- [... mobile nav ...] -->
    </mat-drawer>
    <div tabindex="-1" class="cdk-visually-hidden cdk-focus-trap-anchor"></div>
    <mat-drawer-content _ngcontent-c0="" class="mat-drawer-content">
        <rb-navbar _ngcontent-c0="" _nghost-c7="" class="ng-star-inserted">
          <!-- [... nav bar ...] -->
        </rb-navbar>
        <div _ngcontent-c0="" class="main-body">
            <div _ngcontent-c0="" class="container h-100">
                <router-outlet _ngcontent-c0=""></router-outlet>
                <rb-system-setup _nghost-c18="" class="ng-star-inserted">
                    <router-outlet _ngcontent-c18=""></router-outlet>
                    <rb-site-tab class="ng-star-inserted">
                        <mat-drawer-container autosize="" class="mat-drawer-container" ng-reflect-autosize="">
                            <div class="mat-drawer-backdrop ng-star-inserted"></div>
                            <div tabindex="-1" class="cdk-visually-hidden cdk-focus-trap-anchor"></div>
                            <mat-drawer class="mat-drawer ng-tns-c2-8 ng-trigger ng-trigger-transform mat-drawer-end mat-drawer-over ng-star-inserted" disableclose="true" mode="over" position="end" tabindex="-1" ng-reflect-position="end" ng-reflect-mode="over" ng-reflect-disable-close="true" style="box-shadow: none; visibility: hidden;">
                                <rb-create-site _nghost-c20="" ng-reflect-side-panel="[object Object]" ng-reflect-side-panel-container="[object Object]" ng-reflect-ng-grid="[object Object]" ng-reflect-is-editing="false" ng-reflect-timezones="[object Object],[object Object">
                                    <rb-side-panel _ngcontent-c20="" _nghost-c24="" ng-reflect-title="Add Site" ng-reflect-close-button-label="Cancel" ng-reflect-submit-button-label="CREATE_SITE.SUBMIT" ng-reflect-show-submit-button="true" ng-reflect-modal-submitting="true" ng-reflect-side-panel-container="[object Object]">
                                      <!-- [... side panel ...] -->
                                    </rb-side-panel>
                                </rb-create-site>
                            </mat-drawer>
                            <div tabindex="-1" class="cdk-visually-hidden cdk-focus-trap-anchor"></div>
                            <mat-drawer-content cdkscrollable="" class="mat-drawer-content ng-star-inserted">
                                <div class="ph4-m h-100">
                                    <rb-card _nghost-c21="">
                                        <div _ngcontent-c21="" class="card rb-min-width-1 h-100">
                                            <div _ngcontent-c21="" class="relative h-100">
                                                <rb-container _nghost-c22="" ng-reflect-row-data="[object Object]" ng-reflect-show-player="true" ng-reflect-show-search-bar="true" ng-reflect-include-edit="true" ng-reflect-include-delete="true" ng-reflect-include-stop="false" ng-reflect-include-sync="false" ng-reflect-include-checkbox="true" ng-reflect-include-fab-button="true" ng-reflect-route-type="systemSetup" ng-reflect-header="Sites" ng-reflect-mobile-table="site" ng-reflect-show-site-selector="false" ng-reflect-mobile-navigation="true">
                                                    <div _ngcontent-c22="" class="title-tab dn db-m mediumGreyColor pl4 pv2 overflow-auto">
                                                        <h1 _ngcontent-c22="" class="header-text pa2 fl ng-star-inserted">Sites</h1></div>
                                                    <div _ngcontent-c22="" class="player-menu container overflow-auto dn db-m pv2 ng-star-inserted">
                                                      <!-- [... player menu ...] -->
                                                    </div>
                                                    <rb-fab-button _ngcontent-c22="" class="add-fab-button absolute dn db-m ng-star-inserted" data-qaid="create-button" _nghost-c28="">
                                                        <button _ngcontent-c28="" class="w-10 z-1 mat-fab mat-accent" mat-fab="" type="button" ng-reflect-disabled="false"><span class="mat-button-wrapper"><mat-icon _ngcontent-c28="" aria-label="add" class="mat-icon material-icons ng-star-inserted" role="img" aria-hidden="true">add</mat-icon></span>
                                                            <div class="mat-button-ripple mat-ripple mat-button-ripple-round" matripple="" ng-reflect-centered="false" ng-reflect-disabled="false" ng-reflect-trigger="[object HTMLButtonElement]"></div>
                                                            <div class="mat-button-focus-overlay"></div>
                                                        </button>
                                                    </rb-fab-button>
                                                    <div _ngcontent-c22="" class="mr4-l w-100-m"></div>
                                                    <rb-table-wrapper _ngcontent-c22="" class="dn db-m" ng-reflect-row-data="[object Object]" ng-reflect-enable-sorting="true" ng-reflect-include-checkbox="true" ng-reflect-is-clickable="false" ng-reflect-row-selection="multiple" ng-reflect-dom-layout="" ng-reflect-columns="[object Object],[object Object" ng-reflect-un-select-all_="[object Object]" ng-reflect-mobile-table="site" ng-reflect-mobile-navigation="true" ng-reflect-row-drag="false" ng-reflect-row-drag-field-name="">
                                                        <ag-grid-angular class="ag-theme-material" ng-reflect-grid-options="[object Object]" ng-reflect-row-data="[object Object]" ng-reflect-column-defs="[object Object],[object Object" ng-reflect-default-col-def="[object Object]" ng-reflect-row-selection="multiple" ng-reflect-suppress-row-click-selection="true" ng-reflect-enable-sorting="true" ng-reflect-enable-filter="true" ng-reflect-suppress-no-rows-overlay="true" ng-reflect-dom-layout="" ng-reflect-row-drag-managed="false">
                                                          <!-- [... data grid ...] -->
                                                        </ag-grid-angular>
                                                        <div class="backgroundColor w-100 fixed bottom-0 left-0 dn db-m ng-star-inserted">
                                                            <div class="item-selection fr w-20">0/ 1 Selected</div>
                                                        </div>
                                                    </rb-table-wrapper>
                                                    <div _ngcontent-c22="" class="player-menu container overflow-auto db dn-m ng-star-inserted"></div>
                                                </rb-container>
                                            </div>
                                        </div>
                                    </rb-card>
                                </div>
                            </mat-drawer-content>
                        </mat-drawer-container>
                    </rb-site-tab>
                </rb-system-setup>
            </div>
            <div _ngcontent-c0="" class="snacks fixed mw6 rb-min-width-2">
                <rb-global-snack-bar _ngcontent-c0="" _nghost-c8="" class="ng-tns-c8-3">
                    <div _ngcontent-c8="" class="snackBar">
                        <ul _ngcontent-c8="" class="ma0 pa0 list"></ul>
                    </div>
                </rb-global-snack-bar>
            </div>
        </div>
    </mat-drawer-content>
</mat-drawer-container>
like image 386
kj. Avatar asked Sep 04 '18 22:09

kj.


1 Answers

I started recursively going through the parent nodes and returning the same values as below. One of the parents--the inner mat-drawer-container--also returned false for isDisplayed (all the others returned true, which seems odd).

Looking at that node, it turns out that it's the only tag on the site to have the CSS of display: contents. Removing that causes the button in question--and everything else below it--to return true for isDisplayed. Heck, Protractor can even click the button and I can see the expected result in the browser.

Now I suppose the question remains: is this is expected behavior or a bug? It's not as straightforward as there being an ancestor with display: contents applied; I applied it directly to rb-container and Protractor was still able to find the button.

I opened an issue on the protractor GitHub so they could look further into it.


[UPDATE: I've updated the issue on GitHub, but thought I'd share here as well:]

After some digging, I've found that it's a combination of two style declarations: display: contents with overflow: hidden.

On the surface, it sounds like common sense that you shouldn't really use those together, since you're trying to hide the overflow of a container element that isn't really there; however, I feel if a child of said element is being rendered visibly by the browser--and in this case, it is--it probably should be detected as visible by protractor.

The HTML/CSS:

<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <title>isDisplayed() issue</title>
    <style>

    .not-displayed .display-contents {
        overflow: hidden;
        display: contents; 
    }

    </style>
</head>
<body>

<div class="not-displayed">
    <div class="display-contents">
        <div class="wrong">isDisplayed() returns false</div>
    </div>
</div>

<div class="displayed">
    <div class="display-contents">
        <div class="right">isDisplayed() returns true</div>
    </div>
</div>

</body>
</html>

Spec file:

import { browser, by, element } from 'protractor';

describe('Testing the Protractor Bug', () => {

    it('should display debugging info', () => {

        browser.waitForAngularEnabled(false);
        browser.get('_[file location]_');

        element(by.css('.wrong')).isDisplayed().then(disp => { console.log('"wrong" isDisplayed:', disp); });
        element(by.css('.right')).isDisplayed().then(disp => { console.log('"right" isDisplayed:', disp); });

    });
});

The output from that:

[17:04:50] I/launcher - Running 1 instances of WebDriver
[17:04:50] I/direct - Using ChromeDriver directly...

DevTools listening on ws://127.0.0.1:59412/devtools/browser/3f76f1a9-25f5-4497-8ae3-6933d01d0c7e
Jasmine started
"wrong" isDisplayed: false
"right" isDisplayed: true

  Testing the Protractor Bug
    √ should display debugging info

Executed 1 of 1 spec SUCCESS in 0.202 sec.
[17:04:53] I/launcher - 0 instance(s) of WebDriver still running
[17:04:53] I/launcher - chrome #01 passed
like image 179
kj. Avatar answered Oct 20 '22 17:10

kj.