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

Commit 982755d

Browse files
committed
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 4689442 commit 982755d

File tree

2 files changed

+135
-12
lines changed

2 files changed

+135
-12
lines changed

Diff for: lib/browser.ts

+106-5
Original file line numberDiff line numberDiff line change
@@ -282,11 +282,16 @@ export class ProtractorBrowser extends AbstractExtendedWebDriver {
282282
params: any;
283283

284284
/**
285+
* Resolved when the browser is ready for use. Resolves to the browser, so
286+
* you can do:
287+
*
288+
* forkedBrowser = await browser.forkNewDriverInstance().ready;
289+
*
285290
* Set by the runner.
286291
*
287-
* @type {q.Promise} Done when the new browser is ready for use
292+
* @type {webdriver.promise.Promise.<ProtractorBrowser>}
288293
*/
289-
ready: wdpromise.Promise<any>;
294+
ready: wdpromise.Promise<ProtractorBrowser>;
290295

291296
/*
292297
* Set by the runner.
@@ -464,7 +469,15 @@ export class ProtractorBrowser extends AbstractExtendedWebDriver {
464469
/**
465470
* Fork another instance of browser for use in interactive tests.
466471
*
467-
* Set by the runner.
472+
* @example
473+
* // Running with control flow enabled
474+
* var fork = browser.forkNewDriverInstance();
475+
* fork.get('page1'); // 'page1' gotten by forked browser
476+
*
477+
* @example
478+
* // Running with control flow disabled
479+
* var forked = await browser.forkNewDriverInstance().ready;
480+
* await forked.get('page1'); // 'page1' gotten by forked browser
468481
*
469482
* @param {boolean} opt_useSameUrl Whether to navigate to current url on
470483
* creation
@@ -478,11 +491,85 @@ export class ProtractorBrowser extends AbstractExtendedWebDriver {
478491
}
479492

480493
/**
481-
* Restart the browser instance.
494+
* Restart the browser. This is done by closing this browser instance and creating a new one.
495+
* A promise resolving to the new instance is returned, and if this function was called on the
496+
* global `browser` instance then Protractor will automatically overwrite the global `browser`
497+
* variable.
498+
*
499+
* When restarting a forked browser, it is the caller's job to overwrite references to the old
500+
* instance.
501+
*
502+
* This function behaves slightly differently depending on if the webdriver control flow is
503+
* enabled. If the control flow is enabled, the global `browser` object is synchronously
504+
* replaced. If the control flow is disabled, the global `browser` is replaced asynchronously
505+
* after the old driver quits.
482506
*
483507
* Set by the runner.
508+
*
509+
* @example
510+
* // Running against global browser, with control flow enabled
511+
* browser.get('page1');
512+
* browser.restart();
513+
* browser.get('page2'); // 'page2' gotten by restarted browser
514+
*
515+
* @example
516+
* // Running against global browser, with control flow disabled
517+
* await browser.get('page1');
518+
* await browser.restart();
519+
* await browser.get('page2'); // 'page2' gotten by restarted browser
520+
*
521+
* @example
522+
* // Running against forked browsers, with the control flow enabled
523+
* // In this case, you may prefer `restartSync` (documented below)
524+
* var forked = browser.forkNewDriverInstance();
525+
* fork.get('page1');
526+
* fork.restart().then(function(fork) {
527+
* fork.get('page2'); // 'page2' gotten by restarted fork
528+
* });
529+
*
530+
* @example
531+
* // Running against forked browsers, with the control flow disabled
532+
* var forked = await browser.forkNewDriverInstance().ready;
533+
* await fork.get('page1');
534+
* fork = await fork.restart();
535+
* await fork.get('page2'); // 'page2' gotten by restarted fork
536+
*
537+
* @example
538+
* // Unexpected behavior can occur if you save references to the global `browser`
539+
* var savedBrowser = browser;
540+
* browser.get('foo').then(function() {
541+
* console.log(browser === savedBrowser); // false
542+
* });
543+
* browser.restart();
544+
*
545+
* @returns {webdriver.promise.Promise<ProtractorBrowser>} A promise resolving to the restarted
546+
* browser
484547
*/
485-
restart() {
548+
restart(): wdpromise.Promise<ProtractorBrowser> {
549+
return;
550+
}
551+
552+
/**
553+
* Like `restart`, but instead of returning a promise resolving to the new browser instance,
554+
* returns the new browser instance directly. Can only be used when the control flow is enabled.
555+
*
556+
* @example
557+
* // Running against global browser
558+
* browser.get('page1');
559+
* browser.restartSync();
560+
* browser.get('page2'); // 'page2' gotten by restarted browser
561+
*
562+
* @example
563+
* // Running against forked browsers
564+
* var forked = browser.forkNewDriverInstance();
565+
* fork.get('page1');
566+
* fork = fork.restartSync();
567+
* fork.get('page2'); // 'page2' gotten by restarted fork
568+
*
569+
* @throws {TypeError} Will throw an error if the control flow is not enabled
570+
* @returns {ProtractorBrowser} The restarted browser
571+
*/
572+
restartSync(): ProtractorBrowser {
486573
return;
487574
}
488575

@@ -1100,4 +1187,18 @@ export class ProtractorBrowser extends AbstractExtendedWebDriver {
11001187
};
11011188
this.debugHelper.init(debuggerClientPath, onStartFn, opt_debugPort);
11021189
}
1190+
1191+
/**
1192+
* Determine if the control flow is enabled.
1193+
*
1194+
* @returns true if the control flow is enabled, false otherwise.
1195+
*/
1196+
controlFlowIsEnabled() {
1197+
if ((wdpromise as any).USE_PROMISE_MANAGER !== undefined) {
1198+
return (wdpromise as any).USE_PROMISE_MANAGER;
1199+
} else {
1200+
// True for old versions of `selenium-webdriver`, probably false in >=5.0.0
1201+
return !!wdpromise.ControlFlow;
1202+
}
1203+
}
11031204
}

Diff for: lib/runner.ts

+29-7
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)