Skip to content
This repository was archived by the owner on Jul 29, 2024. It is now read-only.

Commit ed2534a

Browse files
authored
feat(restart): browser.restart should return a promise (#4008)
Also allows `browser.restart` to work when the control flow is disabled, and fixes it for forked browsers. Closes #3899 and #3896
1 parent 5ad7865 commit ed2534a

File tree

2 files changed

+135
-12
lines changed

2 files changed

+135
-12
lines changed

lib/browser.ts

Lines changed: 106 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -236,11 +236,16 @@ export class ProtractorBrowser extends AbstractExtendedWebDriver {
236236
params: any;
237237

238238
/**
239+
* Resolved when the browser is ready for use. Resolves to the browser, so
240+
* you can do:
241+
*
242+
* forkedBrowser = await browser.forkNewDriverInstance().ready;
243+
*
239244
* Set by the runner.
240245
*
241-
* @type {q.Promise} Done when the new browser is ready for use
246+
* @type {webdriver.promise.Promise.<ProtractorBrowser>}
242247
*/
243-
ready: wdpromise.Promise<any>;
248+
ready: wdpromise.Promise<ProtractorBrowser>;
244249

245250
/*
246251
* Set by the runner.
@@ -418,7 +423,15 @@ export class ProtractorBrowser extends AbstractExtendedWebDriver {
418423
/**
419424
* Fork another instance of browser for use in interactive tests.
420425
*
421-
* Set by the runner.
426+
* @example
427+
* // Running with control flow enabled
428+
* var fork = browser.forkNewDriverInstance();
429+
* fork.get('page1'); // 'page1' gotten by forked browser
430+
*
431+
* @example
432+
* // Running with control flow disabled
433+
* var forked = await browser.forkNewDriverInstance().ready;
434+
* await forked.get('page1'); // 'page1' gotten by forked browser
422435
*
423436
* @param {boolean} opt_useSameUrl Whether to navigate to current url on
424437
* creation
@@ -432,11 +445,85 @@ export class ProtractorBrowser extends AbstractExtendedWebDriver {
432445
}
433446

434447
/**
435-
* Restart the browser instance.
448+
* Restart the browser. This is done by closing this browser instance and creating a new one.
449+
* A promise resolving to the new instance is returned, and if this function was called on the
450+
* global `browser` instance then Protractor will automatically overwrite the global `browser`
451+
* variable.
452+
*
453+
* When restarting a forked browser, it is the caller's job to overwrite references to the old
454+
* instance.
455+
*
456+
* This function behaves slightly differently depending on if the webdriver control flow is
457+
* enabled. If the control flow is enabled, the global `browser` object is synchronously
458+
* replaced. If the control flow is disabled, the global `browser` is replaced asynchronously
459+
* after the old driver quits.
436460
*
437461
* Set by the runner.
462+
*
463+
* @example
464+
* // Running against global browser, with control flow enabled
465+
* browser.get('page1');
466+
* browser.restart();
467+
* browser.get('page2'); // 'page2' gotten by restarted browser
468+
*
469+
* @example
470+
* // Running against global browser, with control flow disabled
471+
* await browser.get('page1');
472+
* await browser.restart();
473+
* await browser.get('page2'); // 'page2' gotten by restarted browser
474+
*
475+
* @example
476+
* // Running against forked browsers, with the control flow enabled
477+
* // In this case, you may prefer `restartSync` (documented below)
478+
* var forked = browser.forkNewDriverInstance();
479+
* fork.get('page1');
480+
* fork.restart().then(function(fork) {
481+
* fork.get('page2'); // 'page2' gotten by restarted fork
482+
* });
483+
*
484+
* @example
485+
* // Running against forked browsers, with the control flow disabled
486+
* var forked = await browser.forkNewDriverInstance().ready;
487+
* await fork.get('page1');
488+
* fork = await fork.restart();
489+
* await fork.get('page2'); // 'page2' gotten by restarted fork
490+
*
491+
* @example
492+
* // Unexpected behavior can occur if you save references to the global `browser`
493+
* var savedBrowser = browser;
494+
* browser.get('foo').then(function() {
495+
* console.log(browser === savedBrowser); // false
496+
* });
497+
* browser.restart();
498+
*
499+
* @returns {webdriver.promise.Promise<ProtractorBrowser>} A promise resolving to the restarted
500+
* browser
438501
*/
439-
restart() {
502+
restart(): wdpromise.Promise<ProtractorBrowser> {
503+
return;
504+
}
505+
506+
/**
507+
* Like `restart`, but instead of returning a promise resolving to the new browser instance,
508+
* returns the new browser instance directly. Can only be used when the control flow is enabled.
509+
*
510+
* @example
511+
* // Running against global browser
512+
* browser.get('page1');
513+
* browser.restartSync();
514+
* browser.get('page2'); // 'page2' gotten by restarted browser
515+
*
516+
* @example
517+
* // Running against forked browsers
518+
* var forked = browser.forkNewDriverInstance();
519+
* fork.get('page1');
520+
* fork = fork.restartSync();
521+
* fork.get('page2'); // 'page2' gotten by restarted fork
522+
*
523+
* @throws {TypeError} Will throw an error if the control flow is not enabled
524+
* @returns {ProtractorBrowser} The restarted browser
525+
*/
526+
restartSync(): ProtractorBrowser {
440527
return;
441528
}
442529

@@ -1054,4 +1141,18 @@ export class ProtractorBrowser extends AbstractExtendedWebDriver {
10541141
};
10551142
this.debugHelper.init(debuggerClientPath, onStartFn, opt_debugPort);
10561143
}
1144+
1145+
/**
1146+
* Determine if the control flow is enabled.
1147+
*
1148+
* @returns true if the control flow is enabled, false otherwise.
1149+
*/
1150+
controlFlowIsEnabled() {
1151+
if ((wdpromise as any).USE_PROMISE_MANAGER !== undefined) {
1152+
return (wdpromise as any).USE_PROMISE_MANAGER;
1153+
} else {
1154+
// True for old versions of `selenium-webdriver`, probably false in >=5.0.0
1155+
return !!wdpromise.ControlFlow;
1156+
}
1157+
}
10571158
}

lib/runner.ts

Lines changed: 29 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -115,8 +115,7 @@ export class Runner extends EventEmitter {
115115
let ret: q.Promise<void>;
116116
this.frameworkUsesAfterEach = true;
117117
if (this.config_.restartBrowserBetweenTests) {
118-
// TODO(sjelin): remove the `|| q()` once `restart()` returns a promise
119-
this.restartPromise = this.restartPromise || protractor.browser.restart() || q();
118+
this.restartPromise = this.restartPromise || q(protractor.browser.restart());
120119
ret = this.restartPromise;
121120
this.restartPromise = undefined;
122121
}
@@ -249,7 +248,8 @@ export class Runner extends EventEmitter {
249248
browser_.ng12Hybrid = config.ng12Hybrid;
250249
}
251250

252-
browser_.ready = driver.manage().timeouts().setScriptTimeout(config.allScriptsTimeout);
251+
browser_.ready =
252+
driver.manage().timeouts().setScriptTimeout(config.allScriptsTimeout).then(() => browser_);
253253

254254
browser_.getProcessedConfig = () => {
255255
return wdpromise.fulfilled(config);
@@ -268,12 +268,35 @@ export class Runner extends EventEmitter {
268268
return newBrowser;
269269
};
270270

271+
let replaceBrowser = () => {
272+
let newBrowser = browser_.forkNewDriverInstance(false, true);
273+
if (browser_ === protractor.browser) {
274+
this.setupGlobals_(newBrowser);
275+
}
276+
return newBrowser;
277+
};
278+
271279
browser_.restart = () => {
272280
// Note: because tests are not paused at this point, any async
273281
// calls here are not guaranteed to complete before the tests resume.
282+
283+
// Seperate solutions depending on if the control flow is enabled (see lib/browser.ts)
284+
if (browser_.controlFlowIsEnabled()) {
285+
return browser_.restartSync().ready;
286+
} else {
287+
return this.driverprovider_.quitDriver(browser_.driver)
288+
.then(replaceBrowser)
289+
.then(newBrowser => newBrowser.ready);
290+
}
291+
};
292+
293+
browser_.restartSync = () => {
294+
if (!browser_.controlFlowIsEnabled()) {
295+
throw TypeError('Unable to use `browser.restartSync()` when the control flow is disabled');
296+
}
297+
274298
this.driverprovider_.quitDriver(browser_.driver);
275-
browser_ = browser_.forkNewDriverInstance(false, true);
276-
this.setupGlobals_(browser_);
299+
return replaceBrowser();
277300
};
278301

279302
return browser_;
@@ -365,8 +388,7 @@ export class Runner extends EventEmitter {
365388
// TODO(sjelin): replace with warnings once `afterEach` support is required
366389
let restartDriver = () => {
367390
if (!this.frameworkUsesAfterEach) {
368-
// TODO(sjelin): remove the `|| q()` once `restart()` returns a promise
369-
this.restartPromise = browser_.restart() || q();
391+
this.restartPromise = q(browser_.restart());
370392
}
371393
};
372394
this.on('testPass', restartDriver);

0 commit comments

Comments
 (0)