selenium: `async`/`await` breaks the control flow
Meta -
OS:
Ubuntu
Selenium Version:
2.53.3
or 3.0.0-beta-3
Browser:
node
Browser Version:
v5.10.1
or v4.0.0
Bug
I was trying to improve async
/await
support for Protractor when I ran into a problem with the folllowing test:
describe('async function + control flow', function() {
var val;
var seven;
it('should wait for this it() to finish', async function() {
val = 1;
seven = await webdriver.promise.fulfilled(7);
controlFlow.execute(() => {
return webdriver.promise.delayed(1000).then(() => {
val = seven;
}));
});
it('should have waited for setter in previous it()', function() {
expect(val).toBe(7); // <<------ This fails
});
});
async
/await
are being compiled by the typescript compiler in this case, and jasminewd2
wraps each it()
block in the control flow, so this test should have worked. However, the final assertion failed, with val
still being 1
.
Compiling down to ES6 and stripping out the jasmine/jasminewd2
, the above translates to the following:
var webdriver = require('selenium-webdriver'),
flow = webdriver.promise.controlFlow();
var val;
function runInFlow(fun, name) {
return flow.execute(() => {
return webdriver.promise.fulfilled(fun());
}, name);
}
runInFlow(() => {
val = 1;
return new Promise((resolve) => {
resolve(webdriver.promise.fulfilled(7));
}).then((seven) => {
runInFlow(() => {
return webdriver.promise.delayed(1000).then(() => {
val = seven;
});
}, 'set outer');
});
}, 'set inner');
runInFlow(() => {
console.log('RESULT: val = ' + val); // 1, should be 7
}, 'log');
Basically, by putting a webdriver promise inside an ES6 promise inside a webdriver promise, we somehow break the control flow. This is a problem because await
compiles down to an ES6 promise, and async
functions then return those promises. So if you await
some webdriver promise, and then wrap the async
function in the control flow, you will run into this bug (as in the first example). This means that Protractor users (or any users who wrap blocks of code in the control flow) basically cannot await
an element.getText()
command or any other webdriver promise or else everything will become desynchronized.
I know that as per https://github.com/SeleniumHQ/selenium/issues/2969 you plan on removing ManagedPromise/the control flow entirely. But in the mean time, async
functions are practically unusable, so this seemed worth bringing to your attention.
About this issue
- Original URL
- State: closed
- Created 8 years ago
- Reactions: 4
- Comments: 22 (11 by maintainers)
Well, the good news is I already have the code for disabling the control flow completed, I just haven’t pushed it (hoping this week). So you could tell users if they want to use async, disable the control flow (there’s no need for it if you’re using async)
@jleyba
is there any specific date already for the release 4.0 since this is causing our tests to fail.
FYI 3.0 has been pushed to npm, so you can disable the promise manager now.
The promise manager has been removed from the code base (5650b96185df72ee074f3c660ef5efd227b2739a).
This will be included in the 4.0 release (which won’t go out until Node releases their next LTS later this month).
It’s a timing issue with how the control flow tracks individual turns of the js event loop (which dictates when actions are linked). With your initial example, that first promise just needs to be asynchronously resolved - reproduces with native promises. I really want to sweep this under the rug, but given the control flow isn’t going anywhere for a while, I’ll see if I can track it down.
Your example is working as intended. The control flow synchronizes actions within “frames”, which are tied to the JavaScript event loop (as best as the control flow can). Will comment further using your code sample:
Same with your first example, add an await/return to link everything up: