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

Commit b77cb92

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 90cb13f commit b77cb92

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
@@ -317,11 +317,16 @@ export class ProtractorBrowser extends AbstractExtendedWebDriver {
317317
params: any;
318318

319319
/**
320+
* Resolved when the browser is ready for use. Resolves to the browser, so
321+
* you can do:
322+
*
323+
* forkedBrowser = await browser.forkNewDriverInstance().ready;
324+
*
320325
* Set by the runner.
321326
*
322-
* @type {q.Promise} Done when the new browser is ready for use
327+
* @type {webdriver.promise.Promise.<ProtractorBrowser>}
323328
*/
324-
ready: wdpromise.Promise<any>;
329+
ready: wdpromise.Promise<ProtractorBrowser>;
325330

326331
/*
327332
* Set by the runner.
@@ -499,7 +504,15 @@ export class ProtractorBrowser extends AbstractExtendedWebDriver {
499504
/**
500505
* Fork another instance of browser for use in interactive tests.
501506
*
502-
* Set by the runner.
507+
* @example
508+
* // Running with control flow enabled
509+
* var fork = browser.forkNewDriverInstance();
510+
* fork.get('page1'); // 'page1' gotten by forked browser
511+
*
512+
* @example
513+
* // Running with control flow disabled
514+
* var forked = await browser.forkNewDriverInstance().ready;
515+
* await forked.get('page1'); // 'page1' gotten by forked browser
503516
*
504517
* @param {boolean} opt_useSameUrl Whether to navigate to current url on
505518
* creation
@@ -513,11 +526,85 @@ export class ProtractorBrowser extends AbstractExtendedWebDriver {
513526
}
514527

515528
/**
516-
* Restart the browser instance.
529+
* Restart the browser. This is done by closing this browser instance and creating a new one.
530+
* A promise resolving to the new instance is returned, and if this function was called on the
531+
* global `browser` instance then Protractor will automatically overwrite the global `browser`
532+
* variable.
533+
*
534+
* When restarting a forked browser, it is the caller's job to overwrite references to the old
535+
* instance.
536+
*
537+
* This function behaves slightly differently depending on if the webdriver control flow is
538+
* enabled. If the control flow is enabled, the global `browser` object is synchronously
539+
* replaced. If the control flow is disabled, the global `browser` is replaced asynchronously
540+
* after the old driver quits.
517541
*
518542
* Set by the runner.
543+
*
544+
* @example
545+
* // Running against global browser, with control flow enabled
546+
* browser.get('page1');
547+
* browser.restart();
548+
* browser.get('page2'); // 'page2' gotten by restarted browser
549+
*
550+
* @example
551+
* // Running against global browser, with control flow disabled
552+
* await browser.get('page1');
553+
* await browser.restart();
554+
* await browser.get('page2'); // 'page2' gotten by restarted browser
555+
*
556+
* @example
557+
* // Running against forked browsers, with the control flow enabled
558+
* // In this case, you may prefer `restartSync` (documented below)
559+
* var forked = browser.forkNewDriverInstance();
560+
* fork.get('page1');
561+
* fork.restart().then(function(fork) {
562+
* fork.get('page2'); // 'page2' gotten by restarted fork
563+
* });
564+
*
565+
* @example
566+
* // Running against forked browsers, with the control flow disabled
567+
* var forked = await browser.forkNewDriverInstance().ready;
568+
* await fork.get('page1');
569+
* fork = await fork.restart();
570+
* await fork.get('page2'); // 'page2' gotten by restarted fork
571+
*
572+
* @example
573+
* // Unexpected behavior can occur if you save references to the global `browser`
574+
* var savedBrowser = browser;
575+
* browser.get('foo').then(function() {
576+
* console.log(browser === savedBrowser); // false
577+
* });
578+
* browser.restart();
579+
*
580+
* @returns {webdriver.promise.Promise<ProtractorBrowser>} A promise resolving to the restarted
581+
* browser
519582
*/
520-
restart() {
583+
restart(): wdpromise.Promise<ProtractorBrowser> {
584+
return;
585+
}
586+
587+
/**
588+
* Like `restart`, but instead of returning a promise resolving to the new browser instance,
589+
* returns the new browser instance directly. Can only be used when the control flow is enabled.
590+
*
591+
* @example
592+
* // Running against global browser
593+
* browser.get('page1');
594+
* browser.restartSync();
595+
* browser.get('page2'); // 'page2' gotten by restarted browser
596+
*
597+
* @example
598+
* // Running against forked browsers
599+
* var forked = browser.forkNewDriverInstance();
600+
* fork.get('page1');
601+
* fork = fork.restartSync();
602+
* fork.get('page2'); // 'page2' gotten by restarted fork
603+
*
604+
* @throws {TypeError} Will throw an error if the control flow is not enabled
605+
* @returns {ProtractorBrowser} The restarted browser
606+
*/
607+
restartSync(): ProtractorBrowser {
521608
return;
522609
}
523610

@@ -1138,4 +1225,18 @@ export class ProtractorBrowser extends AbstractExtendedWebDriver {
11381225
};
11391226
this.debugHelper.init(debuggerClientPath, onStartFn, opt_debugPort);
11401227
}
1228+
1229+
/**
1230+
* Determine if the control flow is enabled.
1231+
*
1232+
* @returns true if the control flow is enabled, false otherwise.
1233+
*/
1234+
controlFlowIsEnabled() {
1235+
if ((wdpromise as any).USE_PROMISE_MANAGER !== undefined) {
1236+
return (wdpromise as any).USE_PROMISE_MANAGER;
1237+
} else {
1238+
// True for old versions of `selenium-webdriver`, probably false in >=5.0.0
1239+
return !!wdpromise.ControlFlow;
1240+
}
1241+
}
11411242
}

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)