New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
[🐛 Bug]: Bug in newWindow
makes hard to work with tabs and use functions such as switchWindow/switchToWindow/getWindowHandles
#7619
Comments
First off, very well written issue, thanks for taking the time.
One is a WebdriverIO command and the other is a protocol command. The WebdriverIO one has a special implementation and uses the protocol command. I don't thing it is a good idea to get rid of one as we need some extra logic to make switching windows easier but want to allow users to use the raw protocol command as well if desired.
Wouldn't it improve the stability if we make a window handle check in the
WebdriverIO has two ways to automate the browser, via WebDriver protocol or through Puppeteer (read more on this in our docs). If you have Chromedriver or a any other arbitrary WebDriver running, you run through the WebDriver protocol and make requests to the driver. Here we run a script in the browser that opens the new tab. If you don't run any driver WebdriverIO falls back to Puppeteer and there we do essentially the same. Another improvement I see here is instead of using JavaScript to open a new window we can just use the What do you think. Would you be interested to take a stab? |
Hey @christian-bromann, thanks for the quick response, and sorry for my late.
I see It just wasn't obvious for us to understand the real differences without going to the source code. As a developer or automation engineer, it would've been more natural (or intuitive) to have:
Either way, to have a clear distinction between the strategies. Here, the Anyhow, it is probably breaking change this kind of change.
Yes, it would. I just assumed that there might be a better "low-level" solution. For instance, if there is a command (actual puppeteer command) to open the tab, this command will terminate once the browser updates the handle.
Yes, I'm aware of the differences. Internally, we are using I think that what was odd for us is that the Are they both, eventually, the same and equivalent to I can fix and push a PR we've talked about, it probably takes me some more time to get more familiar with how to contribute to the code and how to test it. |
Hi @christian-bromann and @avikha The issue is just with that particular website , it seems it is adding separate window name : if you change it with different website , everything works as expected
|
if you open console of browser and run above command and then run
But you replace si with any other website windowName will be test |
@christian-bromann the only fix I could think of is setting |
I agree with that. It might make sense to think about a more clear distinction between protocol commands and WebdriverIO commands. As this would be a breaking change we can consider this for v8.
As WebDriver users might experience the same issue it would be good if the solution can be for both protocols. We should keep protocol commands as low level as possible (no extra waits /logic etc.) so we can have a set of command building blocks that we can use in higher level commands. I however agree with the idea to have the Puppeteer driver be more smart about window management. There are surely more improvements to make.
Not sure, I doubt it.
Cool, let's coordinate with @praveendvd as it seems he looked already into this. @praveendvd it seems that window name always needs to be different. I remember there was a similar issue where having different window names has fixed the issue. What would you propose? |
@christian-bromann the issue I fixed was that if we use newWindow command without specifying a windowname then only one new window opens , Now we replaced, it with blank so it opens new window each time . About this issue, I am not able to reproduce the first one where all new window having same handle , The second issue where we get error saying si-window doesn't exist , is due to the behavior of that particular website. It changes the windowName that we set through newWindow command . It happens only for that particular website |
Correct analysis @praveendvd , here is a reproducible example: const { remote } = require('webdriverio')
let browser
const opts = {
capabilities: {
browserName: 'chrome',
}
}
;(async function () {
browser = await remote(opts)
await browser.url('https://google.com')
await browser.newWindow('https://json.org/', {
windowName: 'some-window-name',
})
console.log(await browser.execute(() => window.name)) // outputs: "some-window-name"
/**
* change name
*/
await browser.execute(() => {
window.name = 'foobar'
})
console.log(await browser.execute(() => window.name)) // outputs: "foobar"
await browser.deleteSession()
})().catch(async (e) => {
console.error(e.stack)
await browser.deleteSession()
}) If your app modifies the window name you should use the url or title to match the correct window name. I will close this as I don't believe there is something we can do here. If you have a suggestion for improvements I am happy to re-open. Thanks! |
Hey @christian-bromann and @praveendvd In our opinion, the problem with In our opinion, as I explained above, it is more of a "core" issue where the browser and the As we see it, the problem is in lines 61-64 in await this.execute(newWindowHelper, url, windowName, windowFeatures)
const tabs = await this.getWindowHandles()
const newTab = tabs.pop() The By the time In our internal framework, we've solved this problem with the I've tried to apply the same solution inside the |
@avikha sorry for loosing track of the actual issue. Reopening. |
@avikha can you help us provide a reproducible example on this? Again your example mentioned above fails due to the fact that the esports website overwrites the window name. This example: await browser.url('https://google.com')
await browser.newWindow('https://www.si.com/', {
windowName: 'si-window',
})
const siTitle = await browser.getTitle()
console.log({ siTitle }) also works as expected, it prints:
|
Hey @christian-bromann, Here is an example of the tabs problems with the it.only("many pages and many handles", async () => {
await browser.url("https://google.com");
const _0 = await browser.getWindowHandle();
const _1 = await browser.newWindow("https://facebook.com", {
windowName: "window1",
});
const _2 = await browser.newWindow("https://twitter.com", {
windowName: "window2",
});
// Here, I expect to get a list with three window handles, because there are three open tabs.
const allHandles = await browser.getWindowHandles();
console.log("all handles");
console.log(allHandles); // This one prints list with single handle, not as expected.
console.log("handles got from newWindow");
// This one prints 3 equal handles.
console.log({
_0,
_1,
_2,
});
}); The actual output from the script: Execution of 1 workers started at 2021-11-10T06:42:55.901Z
2021-11-10T06:42:56.256Z INFO @wdio/cli:launcher: Run onPrepare hook
2021-11-10T06:42:56.258Z INFO @wdio/cli:launcher: Run onWorkerStart hook
2021-11-10T06:42:56.258Z INFO @wdio/local-runner: Start worker 0-0 with arg: run,./wdio.conf.ts,--spec,example.e2e.ts
[0-0] 2021-11-10T06:42:56.573Z INFO @wdio/local-runner: Run worker command: run
[0-0] RUNNING in chrome - /test/specs/example.e2e.ts
[0-0] 2021-11-10T06:42:57.276Z INFO devtools:puppeteer: Initiate new session using the DevTools protocol
[0-0] 2021-11-10T06:42:57.277Z INFO devtools: Launch Google Chrome with flags: --enable-automation --disable-popup-blocking --disable-extensions --disable-background-networking --disable-background-timer-throttling --disable-backgrounding-occluded-windows --disable-sync --metrics-recording-only --disable-default-apps --mute-audio --no-first-run --no-default-browser-check --disable-hang-monitor --disable-prompt-on-repost --disable-client-side-phishing-detection --password-store=basic --use-mock-keychain --disable-component-extensions-with-background-pages --disable-breakpad --disable-dev-shm-usage --disable-ipc-flooding-protection --disable-renderer-backgrounding --force-fieldtrials=*BackgroundTracing/default/ --enable-features=NetworkService,NetworkServiceInProcess --disable-features=site-per-process,TranslateUI,BlinkGenPropertyTrees --window-position=0,0 --window-size=1200,900
[0-0] 2021-11-10T06:42:57.809Z INFO devtools: Connect Puppeteer with browser on port 62322
[0-0] 2021-11-10T06:42:58.404Z INFO devtools: COMMAND navigateTo("https://google.com/")
[0-0] 2021-11-10T06:42:59.920Z INFO devtools: RESULT null
[0-0] 2021-11-10T06:42:59.924Z INFO devtools: COMMAND getWindowHandle()
[0-0] 2021-11-10T06:42:59.924Z INFO devtools: RESULT 5091069a-43cd-4400-a7cd-88d173093b6b
[0-0] 2021-11-10T06:42:59.926Z INFO devtools: COMMAND executeScript(<fn>, <object>)
[0-0] 2021-11-10T06:43:00.006Z INFO devtools: COMMAND getWindowHandles()
[0-0] 2021-11-10T06:43:00.006Z INFO devtools: RESULT [ '5091069a-43cd-4400-a7cd-88d173093b6b' ]
[0-0] 2021-11-10T06:43:00.009Z INFO devtools: COMMAND switchToWindow("5091069a-43cd-4400-a7cd-88d173093b6b")
[0-0] 2021-11-10T06:43:00.023Z INFO devtools: RESULT 5091069a-43cd-4400-a7cd-88d173093b6b
[0-0] 2021-11-10T06:43:00.073Z INFO devtools: COMMAND executeScript(<fn>, <object>)
[0-0] 2021-11-10T06:43:00.135Z INFO devtools: COMMAND getWindowHandles()
[0-0] 2021-11-10T06:43:00.135Z INFO devtools: RESULT [ '5091069a-43cd-4400-a7cd-88d173093b6b' ]
[0-0] 2021-11-10T06:43:00.136Z INFO devtools: COMMAND switchToWindow("5091069a-43cd-4400-a7cd-88d173093b6b")
[0-0] 2021-11-10T06:43:00.142Z INFO devtools: RESULT 5091069a-43cd-4400-a7cd-88d173093b6b
[0-0] 2021-11-10T06:43:00.165Z INFO devtools: COMMAND getWindowHandles()
[0-0] 2021-11-10T06:43:00.165Z INFO devtools: RESULT [ '5091069a-43cd-4400-a7cd-88d173093b6b' ]
[0-0] all handles
[0-0] [ '5091069a-43cd-4400-a7cd-88d173093b6b' ]
[0-0] handles got from newWindow
[0-0] {
[0-0] _0: '5091069a-43cd-4400-a7cd-88d173093b6b',
[0-0] _1: '5091069a-43cd-4400-a7cd-88d173093b6b',
[0-0] _2: '5091069a-43cd-4400-a7cd-88d173093b6b'
[0-0] }
[0-0] 2021-11-10T06:43:05.171Z INFO devtools: COMMAND deleteSession()
[0-0] 2021-11-10T06:43:05.172Z INFO devtools: RESULT null
[0-0] PASSED in chrome - /test/specs/example.e2e.ts
2021-11-10T06:43:05.289Z INFO @wdio/cli:launcher: Run onComplete hook This is the same as "Example 1" presented in my initial post. You can see, that there is only one handle in Our guess is that If you add the following code right after await browser.waitUntil(
async () => {
const allHandles = await browser.getWindowHandles();
return allHandles.length === 3;
},
{
timeout: 3000,
timeoutMsg: "After three seconds, there is only one handle",
}
); Then you can see that the I've tried to implement the same solution in |
By the way, @christian-bromann, I ran your example above, and here is the result. But, unfortunately, it is still not working as expected. At least, for me. Execution of 1 workers started at 2021-11-10T07:07:52.210Z
2021-11-10T07:07:52.558Z INFO @wdio/cli:launcher: Run onPrepare hook
2021-11-10T07:07:52.559Z INFO @wdio/cli:launcher: Run onWorkerStart hook
2021-11-10T07:07:52.560Z INFO @wdio/local-runner: Start worker 0-0 with arg: run,./wdio.conf.ts,--spec,example.e2e.ts
[0-0] 2021-11-10T07:07:52.867Z INFO @wdio/local-runner: Run worker command: run
[0-0] RUNNING in chrome - /test/specs/example.e2e.ts
[0-0] 2021-11-10T07:07:53.575Z INFO devtools:puppeteer: Initiate new session using the DevTools protocol
[0-0] 2021-11-10T07:07:53.575Z INFO devtools: Launch Google Chrome with flags: --enable-automation --disable-popup-blocking --disable-extensions --disable-background-networking --disable-background-timer-throttling --disable-backgrounding-occluded-windows --disable-sync --metrics-recording-only --disable-default-apps --mute-audio --no-first-run --no-default-browser-check --disable-hang-monitor --disable-prompt-on-repost --disable-client-side-phishing-detection --password-store=basic --use-mock-keychain --disable-component-extensions-with-background-pages --disable-breakpad --disable-dev-shm-usage --disable-ipc-flooding-protection --disable-renderer-backgrounding --force-fieldtrials=*BackgroundTracing/default/ --enable-features=NetworkService,NetworkServiceInProcess --disable-features=site-per-process,TranslateUI,BlinkGenPropertyTrees --window-position=0,0 --window-size=1200,900
[0-0] 2021-11-10T07:07:54.108Z INFO devtools: Connect Puppeteer with browser on port 62585
[0-0] 2021-11-10T07:07:54.642Z INFO devtools: COMMAND navigateTo("https://google.com/")
[0-0] 2021-11-10T07:07:55.918Z INFO devtools: RESULT null
[0-0] 2021-11-10T07:07:55.922Z INFO devtools: COMMAND executeScript(<fn>, <object>)
[0-0] 2021-11-10T07:07:56.018Z INFO devtools: COMMAND getWindowHandles()
[0-0] 2021-11-10T07:07:56.019Z INFO devtools: RESULT [ '1fec2e08-9079-488f-970e-c390e3049ebf' ]
[0-0] 2021-11-10T07:07:56.025Z INFO devtools: COMMAND switchToWindow("1fec2e08-9079-488f-970e-c390e3049ebf")
[0-0] 2021-11-10T07:07:56.043Z INFO devtools: RESULT 1fec2e08-9079-488f-970e-c390e3049ebf
[0-0] 2021-11-10T07:07:56.103Z INFO devtools: COMMAND getTitle()
[0-0] 2021-11-10T07:07:56.104Z INFO devtools: RESULT Google
[0-0] { siTitle: 'Google' }
[0-0] 2021-11-10T07:07:56.107Z INFO devtools: COMMAND deleteSession()
[0-0] 2021-11-10T07:07:56.108Z INFO devtools: RESULT null
[0-0] PASSED in chrome - /test/specs/example.e2e.ts
2021-11-10T07:07:56.226Z INFO @wdio/cli:launcher: Run onComplete hook
"spec" Reporter:
------------------------------------------------------------------
[Chrome 95.0.4638.69 darwin #0-0] Running: Chrome (v95.0.4638.69) on darwin
[Chrome 95.0.4638.69 darwin #0-0] Session ID: d650ce20-75b4-4917-b000-a00cd737c0cb
[Chrome 95.0.4638.69 darwin #0-0]
[Chrome 95.0.4638.69 darwin #0-0] » /test/specs/example.e2e.ts
[Chrome 95.0.4638.69 darwin #0-0] Many pages
[Chrome 95.0.4638.69 darwin #0-0] ✓ Example
[Chrome 95.0.4638.69 darwin #0-0]
[Chrome 95.0.4638.69 darwin #0-0] 1 passing (1.4s)
Spec Files: 1 passed, 1 total (100% completed) in 00:00:04 From this part of the logs [0-0] 2021-11-10T07:07:55.922Z INFO devtools: COMMAND executeScript(<fn>, <object>)
[0-0] 2021-11-10T07:07:56.018Z INFO devtools: COMMAND getWindowHandles()
[0-0] 2021-11-10T07:07:56.019Z INFO devtools: RESULT [ '1fec2e08-9079-488f-970e-c390e3049ebf' ]
[0-0] 2021-11-10T07:07:56.025Z INFO devtools: COMMAND switchToWindow("1fec2e08-9079-488f-970e-c390e3049ebf")
[0-0] 2021-11-10T07:07:56.043Z INFO devtools: RESULT 1fec2e08-9079-488f-970e-c390e3049ebf We can see that it executes the Please note that I'm using the puppeteer/dev-tools protocol for automation. |
Thanks for your thorough comments. I added a patch that will fix the issue. Let's continue our convo there. |
WebdriverIO Version
7.16.3
Node.js Version
14.16
Mode
WDIO Testrunner
Which capabilities are you using?
What happened?
We find the
wdio
API dealing with tabs and window management slightly confusing and not working in the way it is supposed to.We strongly believe that the problematic function is
newWidow
, which makes it hard to manage tabs and use otherwdio
APIs ofswitchWindow
,switchToWindow
, andgetWindowHandles
.The first issue that find confusing is having two functions,
switchWindiow
andswitchToWindow
. They have similar names, basically responsible for the same functionality, but each has different arguments. It is probably better to have a single function that deals with switching windows/tabs.From the perspective of business logic and automation functionality, we present many scripts with inconsistent/confusing results.
Problematic code examples
Example 1
We would expect it to have three different handles, but they are all the same. So we can spread
await browser.wait(2000)
, which will have a different result between_0, _1
and_2
, but still incorrect. It all leads to a place where we cant use theswithToWindow
properly.Example 2
This one will result in
The above is true for the
getTitle()
function and other browser and element functions. This can be solved by addingawait browser.pause(3000)
, and it seems that the page title and operations are correct after that.Example 3
It also won't work. Most cases would end with an error message like
Or messages similar to that one. We've tried to spread some `pause's in different combinations, but it won't help much.
We have some more combinations of scripts, but most of them are going around the same point.
Our workaround and temporary solution
After many tries and retries, we came up with the following workaround that looks consistent. We present two solutions, one is trivial, and another is more elegant.
Trivial Solution - Using
await browser.pause
andawait browser.getWindowHandle
It seems to work fine and to produce the correct result. However, we are afraid of the hard-coded
browser.pause
and its dependency on it. Our experience shows us that usingsleep's
orpause
s or explicitwait
s can result in flakiness on ci/cd.The problem with this solution is that
pause
is manually hard-coded, and our intuition and experiments show us that the update of thegetWindowHandles()
list depends on the page (how heavy is the page we load) andElegant solution - proposal for
newWindow
updateOur thoughts on the problem
We believe that the problem is due to synchronization issues between the node script and the browser. Our intuition tells us that the
newWindow
operation is an expensive one, and it takes the browser time to create a new tab, load the page and update the handles. Meanwhile, thenewWindow
operation is not waiting on the browser and now waiting for the update state. Hence, it returns a bad handle, and the functiongetWindowHandles()
is also inconsistent.We've checked the
webdriverio
code for how thenewWindow
function is implemented and we found out that the problem might occur inThe
this.execute
initiates the asynchronouswindow.open
. The command ofthis.execute
finishes while the asynchronous process of creating tab and loading page is still going on. Therefore, proceeding tothis.getWindowHandles
results in a place wherewdio
thinks that browser is done, but the browser is not done yet.The question is, whether the proposed solution above is a good solution or perhaps the correct solution is to use
devtools
protocol and puppeteer. We were surprised to know that thenewWindow
useswindow.open
instead of using the puppeteer / selenium.What is your expected behavior?
I wrote everything above.
How to reproduce the bug.
I wrote everything above.
Relevant log output
Code of Conduct
Is there an existing issue for this?
The text was updated successfully, but these errors were encountered: