0

I ran into failures using Protractor to test for the visibility of "success toast" type messages which are displayed via PrimeNG Toast. Usually, the messages showed as visible, but every now and then, the call to ExpectedConditions.VisibilityOf would fail with

Failed: Not visible after 20000: By(css selector, .ui-toast-detail)
Wait timed out after 20006ms

It seemed to fail whenever I wasn't looking. As long as I was carefully watching the test execution, the message would always display and the test would pass, but if I started working on something else while letting it run in the background, then suddenly it started to fail. Upon further investigation, I discovered that the Document has a property called VisibilityState which changes between 'visible' and 'hidden', depending on whether some part of the document is actually being displayed on the screen. https://www.w3.org/TR/page-visibility-2/

partial interface Document {
    readonly attribute boolean         hidden;
    readonly attribute VisibilityState visibilityState;
             attribute EventHandler    onvisibilitychange;
};

I assume that PrimeNG Toast looks at this visibilityState and/or hidden property on the DOM and it simply doesn't bother to render its toast message if the VisibilityState is 'hidden' and if 'hidden' is 'true'

Since I want my tests to be as reliable as possible in any windowed environment — I also want to be able to run my tests while tabbing away to full-screen apps like Slack — I began to search for a way to force the visibilityState even though some other window may be displayed on top of my browser window.

Given that these Document properties are readonly, is there any way modify them from Selenium?

Or is there a way to at least simulate a visibilityState of true so that components which are sensitive to visibilityState will behave as if the document is visible?

It seems like there is an answer here https://sqa.stackexchange.com/questions/32152/force-a-browsers-visibility-setting-to-true

Object.defineProperty(document, 'visibilityState', {value: 'visible', writable: true});
Object.defineProperty(document, 'hidden', {value: false, writable: true});
document.dispatchEvent(new Event("visibilitychange"));

...but I have not yet been able to get it to work. If I run it with executeScript then it does not have any effect and if I run it with executeAsyncScript then it times out.


Update: I have tried the following and many variants on it, nothing is working. My executeAsyncScript executions no longer time out now that I put callback() at the end, but none of them seem to be triggering the visibilityState to actually show as visible as far as PrimeNG Toast is concerned. I don't want to have to load a plugin into Chrome if there is any way around it.

browser.driver.manage().timeouts().setScriptTimeout(15000);
const visibilityScript = `
var callback = arguments[arguments.length - 1];
window.document.addEventListener('blur', (e) => {
  e.stopImmediatePropagation();
  e.stopPropagation();
  e.preventDefault();
});
Object.defineProperty(window.document,'hidden',{get:function(){return false;},configurable:true});
Object.defineProperty(window.document,'visibilityState',{get:function(){return 'visible';},configurable:true});
window.document.dispatchEvent(new Event('blur'));
window.document.dispatchEvent(new Event('visibilitychange'));
window.document.dispatchEvent(new Event('focus'));
callback();
`;
await browser.wait(browser.executeAsyncScript(visibilityScript));
emery
  • 8,603
  • 10
  • 44
  • 51
  • 2
    see this post: https://stackoverflow.com/questions/58772369/headless-google-chrome-how-to-prevent-sites-to-know-whether-their-window-is-foc DebanjanB's answer talks about using browser plugins/extensions to get around this. – pcalkins Jan 13 '21 at 19:32
  • My preference now is to avoid selenium entirely and use playwright. It just works better. – emery Apr 26 '23 at 00:05

2 Answers2

1

I was able to get the code working with Selenium by changing the third line from document to window, as such:

Object.defineProperty(document, 'visibilityState', {value: 'hidden', writable: true});
Object.defineProperty(document, 'hidden', {value: true, writable: true});
window.dispatchEvent(new Event("visibilitychange"));
kittycatbytes
  • 995
  • 8
  • 14
0

Given an element that only displays when the page has a visibilityState of 'visible', I eventually decided to simply detect the page visibility and wrap my test for that element inside a conditional.

I had already tried many variations of execute_script and execute_async_script blocks to fool the visibility API into returning a visibilityState of "visibile" when my page is actually hidden -- but none of them worked.

The Don't Make Me Watch plugin may have worked, but I want to avoid plugins.

Here is the solution:

const docVisible: boolean = await browser.executeScript(`
  return document.visibilityState == "visible";
`);
if (docVisible) { 
  // check for the element here
}

This has the downside that your test for the presence of that element may be skipped in cases where your browser isn't actually visible on a display screen. In those circumstances, however, the element isn't being rendered by the browser anyway.

emery
  • 8,603
  • 10
  • 44
  • 51