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

Commit d9f3f48

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

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
@@ -112,8 +112,7 @@ export class Runner extends EventEmitter {
112112
let ret: q.Promise<void>;
113113
this.frameworkUsesAfterEach = true;
114114
if (this.config_.restartBrowserBetweenTests) {
115-
// TODO(sjelin): remove the `|| q()` once `restart()` returns a promise
116-
this.restartPromise = this.restartPromise || protractor.browser.restart() || q();
115+
this.restartPromise = this.restartPromise || q(protractor.browser.restart());
117116
ret = this.restartPromise;
118117
this.restartPromise = undefined;
119118
}
@@ -246,7 +245,8 @@ export class Runner extends EventEmitter {
246245
browser_.ng12Hybrid = config.ng12Hybrid;
247246
}
248247

249-
browser_.ready = driver.manage().timeouts().setScriptTimeout(config.allScriptsTimeout);
248+
browser_.ready =
249+
driver.manage().timeouts().setScriptTimeout(config.allScriptsTimeout).then(() => browser_);
250250

251251
browser_.getProcessedConfig = () => {
252252
return wdpromise.fulfilled(config);
@@ -265,12 +265,35 @@ export class Runner extends EventEmitter {
265265
return newBrowser;
266266
};
267267

268+
let replaceBrowser = () => {
269+
let newBrowser = browser_.forkNewDriverInstance(false, true);
270+
if (browser_ === protractor.browser) {
271+
this.setupGlobals_(newBrowser);
272+
}
273+
return newBrowser;
274+
};
275+
268276
browser_.restart = () => {
269277
// Note: because tests are not paused at this point, any async
270278
// calls here are not guaranteed to complete before the tests resume.
279+
280+
// Seperate solutions depending on if the control flow is enabled (see lib/browser.ts)
281+
if (browser_.controlFlowIsEnabled()) {
282+
return browser_.restartSync().ready;
283+
} else {
284+
return this.driverprovider_.quitDriver(browser_.driver)
285+
.then(replaceBrowser)
286+
.then(newBrowser => newBrowser.ready);
287+
}
288+
};
289+
290+
browser_.restartSync = () => {
291+
if (!browser_.controlFlowIsEnabled()) {
292+
throw TypeError('Unable to use `browser.restartSync()` when the control flow is disabled');
293+
}
294+
271295
this.driverprovider_.quitDriver(browser_.driver);
272-
browser_ = browser_.forkNewDriverInstance(false, true);
273-
this.setupGlobals_(browser_);
296+
return replaceBrowser();
274297
};
275298

276299
return browser_;
@@ -358,8 +381,7 @@ export class Runner extends EventEmitter {
358381
// TODO(sjelin): replace with warnings once `afterEach` support is required
359382
let restartDriver = () => {
360383
if (!this.frameworkUsesAfterEach) {
361-
// TODO(sjelin): remove the `|| q()` once `restart()` returns a promise
362-
this.restartPromise = browser_.restart() || q();
384+
this.restartPromise = q(browser_.restart());
363385
}
364386
};
365387
this.on('testPass', restartDriver);

0 commit comments

Comments
 (0)