runner.js 18 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445
  1. "use strict";
  2. Object.defineProperty(exports, "__esModule", { value: true });
  3. const events_1 = require("events");
  4. const q = require("q");
  5. const selenium_webdriver_1 = require("selenium-webdriver");
  6. const util = require("util");
  7. const browser_1 = require("./browser");
  8. const driverProviders_1 = require("./driverProviders");
  9. const logger_1 = require("./logger");
  10. const plugins_1 = require("./plugins");
  11. const ptor_1 = require("./ptor");
  12. const helper = require("./util");
  13. let logger = new logger_1.Logger('runner');
  14. /*
  15. * Runner is responsible for starting the execution of a test run and triggering
  16. * setup, teardown, managing config, etc through its various dependencies.
  17. *
  18. * The Protractor Runner is a node EventEmitter with the following events:
  19. * - testPass
  20. * - testFail
  21. * - testsDone
  22. *
  23. * @param {Object} config
  24. * @constructor
  25. */
  26. class Runner extends events_1.EventEmitter {
  27. constructor(config) {
  28. super();
  29. /**
  30. * Responsible for cleaning up test run and exiting the process.
  31. * @private
  32. * @param {int} Standard unix exit code
  33. */
  34. this.exit_ = function (exitCode) {
  35. return helper.runFilenameOrFn_(this.config_.configDir, this.config_.onCleanUp, [exitCode])
  36. .then((returned) => {
  37. if (typeof returned === 'number') {
  38. return returned;
  39. }
  40. else {
  41. return exitCode;
  42. }
  43. });
  44. };
  45. this.config_ = config;
  46. if (config.v8Debug) {
  47. // Call this private function instead of sending SIGUSR1 because Windows.
  48. process['_debugProcess'](process.pid);
  49. }
  50. if (config.nodeDebug) {
  51. process['_debugProcess'](process.pid);
  52. let flow = selenium_webdriver_1.promise.controlFlow();
  53. this.ready_ = flow.execute(() => {
  54. let nodedebug = require('child_process').fork('debug', ['localhost:5858']);
  55. process.on('exit', function () {
  56. nodedebug.kill('SIGTERM');
  57. });
  58. nodedebug.on('exit', function () {
  59. process.exit(1);
  60. });
  61. }, 'start the node debugger').then(() => {
  62. return flow.timeout(1000, 'waiting for debugger to attach');
  63. });
  64. }
  65. if (config.capabilities && config.capabilities.seleniumAddress) {
  66. config.seleniumAddress = config.capabilities.seleniumAddress;
  67. }
  68. this.loadDriverProvider_(config);
  69. this.setTestPreparer(config.onPrepare);
  70. }
  71. /**
  72. * Registrar for testPreparers - executed right before tests run.
  73. * @public
  74. * @param {string/Fn} filenameOrFn
  75. */
  76. setTestPreparer(filenameOrFn) {
  77. this.preparer_ = filenameOrFn;
  78. }
  79. /**
  80. * Executor of testPreparer
  81. * @public
  82. * @param {string[]=} An optional list of command line arguments the framework will accept.
  83. * @return {q.Promise} A promise that will resolve when the test preparers
  84. * are finished.
  85. */
  86. runTestPreparer(extraFlags) {
  87. let unknownFlags = this.config_.unknownFlags_ || [];
  88. if (extraFlags) {
  89. unknownFlags = unknownFlags.filter((f) => extraFlags.indexOf(f) === -1);
  90. }
  91. if (unknownFlags.length > 0 && !this.config_.disableChecks) {
  92. // TODO: Make this throw a ConfigError in Protractor 6.
  93. logger.warn('Ignoring unknown extra flags: ' + unknownFlags.join(', ') + '. This will be' +
  94. ' an error in future versions, please use --disableChecks flag to disable the ' +
  95. ' Protractor CLI flag checks. ');
  96. }
  97. return this.plugins_.onPrepare().then(() => {
  98. return helper.runFilenameOrFn_(this.config_.configDir, this.preparer_);
  99. });
  100. }
  101. /**
  102. * Called after each test finishes.
  103. *
  104. * Responsible for `restartBrowserBetweenTests`
  105. *
  106. * @public
  107. * @return {q.Promise} A promise that will resolve when the work here is done
  108. */
  109. afterEach() {
  110. let ret;
  111. this.frameworkUsesAfterEach = true;
  112. if (this.config_.restartBrowserBetweenTests) {
  113. this.restartPromise = this.restartPromise || q(ptor_1.protractor.browser.restart());
  114. ret = this.restartPromise;
  115. this.restartPromise = undefined;
  116. }
  117. return ret || q();
  118. }
  119. /**
  120. * Grab driver provider based on type
  121. * @private
  122. *
  123. * Priority
  124. * 1) if directConnect is true, use that
  125. * 2) if seleniumAddress is given, use that
  126. * 3) if a Sauce Labs account is given, use that
  127. * 4) if a seleniumServerJar is specified, use that
  128. * 5) try to find the seleniumServerJar in protractor/selenium
  129. */
  130. loadDriverProvider_(config) {
  131. this.config_ = config;
  132. this.driverprovider_ = driverProviders_1.buildDriverProvider(this.config_);
  133. }
  134. /**
  135. * Getter for the Runner config object
  136. * @public
  137. * @return {Object} config
  138. */
  139. getConfig() {
  140. return this.config_;
  141. }
  142. /**
  143. * Get the control flow used by this runner.
  144. * @return {Object} WebDriver control flow.
  145. */
  146. controlFlow() {
  147. return selenium_webdriver_1.promise.controlFlow();
  148. }
  149. /**
  150. * Sets up convenience globals for test specs
  151. * @private
  152. */
  153. setupGlobals_(browser_) {
  154. // Keep $, $$, element, and by/By under the global protractor namespace
  155. ptor_1.protractor.browser = browser_;
  156. ptor_1.protractor.$ = browser_.$;
  157. ptor_1.protractor.$$ = browser_.$$;
  158. ptor_1.protractor.element = browser_.element;
  159. ptor_1.protractor.by = ptor_1.protractor.By = browser_1.ProtractorBrowser.By;
  160. ptor_1.protractor.ExpectedConditions = browser_.ExpectedConditions;
  161. if (!this.config_.noGlobals) {
  162. // Export protractor to the global namespace to be used in tests.
  163. global.browser = browser_;
  164. global.$ = browser_.$;
  165. global.$$ = browser_.$$;
  166. global.element = browser_.element;
  167. global.by = global.By = ptor_1.protractor.By;
  168. global.ExpectedConditions = ptor_1.protractor.ExpectedConditions;
  169. }
  170. global.protractor = ptor_1.protractor;
  171. if (!this.config_.skipSourceMapSupport) {
  172. // Enable sourcemap support for stack traces.
  173. require('source-map-support').install();
  174. }
  175. // Required by dart2js machinery.
  176. // https://code.google.com/p/dart/source/browse/branches/bleeding_edge/dart/sdk/lib/js/dart2js/js_dart2js.dart?spec=svn32943&r=32943#487
  177. global.DartObject = function (o) {
  178. this.o = o;
  179. };
  180. }
  181. /**
  182. * Create a new driver from a driverProvider. Then set up a
  183. * new protractor instance using this driver.
  184. * This is used to set up the initial protractor instances and any
  185. * future ones.
  186. *
  187. * @param {Plugin} plugins The plugin functions
  188. * @param {ProtractorBrowser=} parentBrowser The browser which spawned this one
  189. *
  190. * @return {Protractor} a protractor instance.
  191. * @public
  192. */
  193. createBrowser(plugins, parentBrowser) {
  194. let config = this.config_;
  195. let driver = this.driverprovider_.getNewDriver();
  196. let blockingProxyUrl;
  197. if (config.useBlockingProxy) {
  198. blockingProxyUrl = this.driverprovider_.getBPUrl();
  199. }
  200. let initProperties = {
  201. baseUrl: config.baseUrl,
  202. rootElement: config.rootElement,
  203. untrackOutstandingTimeouts: config.untrackOutstandingTimeouts,
  204. params: config.params,
  205. getPageTimeout: config.getPageTimeout,
  206. allScriptsTimeout: config.allScriptsTimeout,
  207. debuggerServerPort: config.debuggerServerPort,
  208. ng12Hybrid: config.ng12Hybrid,
  209. waitForAngularEnabled: true
  210. };
  211. if (parentBrowser) {
  212. initProperties.baseUrl = parentBrowser.baseUrl;
  213. initProperties.rootElement = parentBrowser.angularAppRoot();
  214. initProperties.untrackOutstandingTimeouts = !parentBrowser.trackOutstandingTimeouts_;
  215. initProperties.params = parentBrowser.params;
  216. initProperties.getPageTimeout = parentBrowser.getPageTimeout;
  217. initProperties.allScriptsTimeout = parentBrowser.allScriptsTimeout;
  218. initProperties.debuggerServerPort = parentBrowser.debuggerServerPort;
  219. initProperties.ng12Hybrid = parentBrowser.ng12Hybrid;
  220. initProperties.waitForAngularEnabled = parentBrowser.waitForAngularEnabled();
  221. }
  222. let browser_ = new browser_1.ProtractorBrowser(driver, initProperties.baseUrl, initProperties.rootElement, initProperties.untrackOutstandingTimeouts, blockingProxyUrl);
  223. browser_.params = initProperties.params;
  224. browser_.plugins_ = plugins || new plugins_1.Plugins({});
  225. if (initProperties.getPageTimeout) {
  226. browser_.getPageTimeout = initProperties.getPageTimeout;
  227. }
  228. if (initProperties.allScriptsTimeout) {
  229. browser_.allScriptsTimeout = initProperties.allScriptsTimeout;
  230. }
  231. if (initProperties.debuggerServerPort) {
  232. browser_.debuggerServerPort = initProperties.debuggerServerPort;
  233. }
  234. if (initProperties.ng12Hybrid) {
  235. browser_.ng12Hybrid = initProperties.ng12Hybrid;
  236. }
  237. browser_.ready =
  238. browser_.ready
  239. .then(() => {
  240. return browser_.waitForAngularEnabled(initProperties.waitForAngularEnabled);
  241. })
  242. .then(() => {
  243. return driver.manage().timeouts().setScriptTimeout(initProperties.allScriptsTimeout || 0);
  244. })
  245. .then(() => {
  246. return browser_;
  247. });
  248. browser_.getProcessedConfig = () => {
  249. return selenium_webdriver_1.promise.when(config);
  250. };
  251. browser_.forkNewDriverInstance =
  252. (useSameUrl, copyMockModules, copyConfigUpdates = true) => {
  253. let newBrowser = this.createBrowser(plugins);
  254. if (copyMockModules) {
  255. newBrowser.mockModules_ = browser_.mockModules_;
  256. }
  257. if (useSameUrl) {
  258. newBrowser.ready = newBrowser.ready
  259. .then(() => {
  260. return browser_.driver.getCurrentUrl();
  261. })
  262. .then((url) => {
  263. return newBrowser.get(url);
  264. })
  265. .then(() => {
  266. return newBrowser;
  267. });
  268. }
  269. return newBrowser;
  270. };
  271. let replaceBrowser = () => {
  272. let newBrowser = browser_.forkNewDriverInstance(false, true);
  273. if (browser_ === ptor_1.protractor.browser) {
  274. this.setupGlobals_(newBrowser);
  275. }
  276. return newBrowser;
  277. };
  278. browser_.restart = () => {
  279. // Note: because tests are not paused at this point, any async
  280. // calls here are not guaranteed to complete before the tests resume.
  281. // Seperate solutions depending on if the control flow is enabled (see lib/browser.ts)
  282. if (browser_.controlFlowIsEnabled()) {
  283. return browser_.restartSync().ready;
  284. }
  285. else {
  286. return this.driverprovider_.quitDriver(browser_.driver)
  287. .then(replaceBrowser)
  288. .then(newBrowser => newBrowser.ready);
  289. }
  290. };
  291. browser_.restartSync = () => {
  292. if (!browser_.controlFlowIsEnabled()) {
  293. throw TypeError('Unable to use `browser.restartSync()` when the control flow is disabled');
  294. }
  295. this.driverprovider_.quitDriver(browser_.driver);
  296. return replaceBrowser();
  297. };
  298. return browser_;
  299. }
  300. /**
  301. * Final cleanup on exiting the runner.
  302. *
  303. * @return {q.Promise} A promise which resolves on finish.
  304. * @private
  305. */
  306. shutdown_() {
  307. return driverProviders_1.DriverProvider.quitDrivers(this.driverprovider_, this.driverprovider_.getExistingDrivers());
  308. }
  309. /**
  310. * The primary workhorse interface. Kicks off the test running process.
  311. *
  312. * @return {q.Promise} A promise which resolves to the exit code of the tests.
  313. * @public
  314. */
  315. run() {
  316. let testPassed;
  317. let plugins = this.plugins_ = new plugins_1.Plugins(this.config_);
  318. let pluginPostTestPromises;
  319. let browser_;
  320. let results;
  321. if (this.config_.framework !== 'explorer' && !this.config_.specs.length) {
  322. throw new Error('Spec patterns did not match any files.');
  323. }
  324. if (this.config_.SELENIUM_PROMISE_MANAGER != null) {
  325. selenium_webdriver_1.promise.USE_PROMISE_MANAGER = this.config_.SELENIUM_PROMISE_MANAGER;
  326. }
  327. if (this.config_.webDriverLogDir || this.config_.highlightDelay) {
  328. this.config_.useBlockingProxy = true;
  329. }
  330. // 0) Wait for debugger
  331. return q(this.ready_)
  332. .then(() => {
  333. // 1) Setup environment
  334. // noinspection JSValidateTypes
  335. return this.driverprovider_.setupEnv();
  336. })
  337. .then(() => {
  338. // 2) Create a browser and setup globals
  339. browser_ = this.createBrowser(plugins);
  340. this.setupGlobals_(browser_);
  341. return browser_.ready.then(browser_.getSession)
  342. .then((session) => {
  343. logger.debug('WebDriver session successfully started with capabilities ' +
  344. util.inspect(session.getCapabilities()));
  345. }, (err) => {
  346. logger.error('Unable to start a WebDriver session.');
  347. throw err;
  348. });
  349. // 3) Setup plugins
  350. })
  351. .then(() => {
  352. return plugins.setup();
  353. // 4) Execute test cases
  354. })
  355. .then(() => {
  356. // Do the framework setup here so that jasmine and mocha globals are
  357. // available to the onPrepare function.
  358. let frameworkPath = '';
  359. if (this.config_.framework === 'jasmine' || this.config_.framework === 'jasmine2') {
  360. frameworkPath = './frameworks/jasmine.js';
  361. }
  362. else if (this.config_.framework === 'mocha') {
  363. frameworkPath = './frameworks/mocha.js';
  364. }
  365. else if (this.config_.framework === 'debugprint') {
  366. // Private framework. Do not use.
  367. frameworkPath = './frameworks/debugprint.js';
  368. }
  369. else if (this.config_.framework === 'explorer') {
  370. // Private framework. Do not use.
  371. frameworkPath = './frameworks/explorer.js';
  372. }
  373. else if (this.config_.framework === 'custom') {
  374. if (!this.config_.frameworkPath) {
  375. throw new Error('When config.framework is custom, ' +
  376. 'config.frameworkPath is required.');
  377. }
  378. frameworkPath = this.config_.frameworkPath;
  379. }
  380. else {
  381. throw new Error('config.framework (' + this.config_.framework + ') is not a valid framework.');
  382. }
  383. if (this.config_.restartBrowserBetweenTests) {
  384. // TODO(sjelin): replace with warnings once `afterEach` support is required
  385. let restartDriver = () => {
  386. if (!this.frameworkUsesAfterEach) {
  387. this.restartPromise = q(browser_.restart());
  388. }
  389. };
  390. this.on('testPass', restartDriver);
  391. this.on('testFail', restartDriver);
  392. }
  393. // We need to save these promises to make sure they're run, but we
  394. // don't
  395. // want to delay starting the next test (because we can't, it's just
  396. // an event emitter).
  397. pluginPostTestPromises = [];
  398. this.on('testPass', (testInfo) => {
  399. pluginPostTestPromises.push(plugins.postTest(true, testInfo));
  400. });
  401. this.on('testFail', (testInfo) => {
  402. pluginPostTestPromises.push(plugins.postTest(false, testInfo));
  403. });
  404. logger.debug('Running with spec files ' + this.config_.specs);
  405. return require(frameworkPath).run(this, this.config_.specs);
  406. // 5) Wait for postTest plugins to finish
  407. })
  408. .then((testResults) => {
  409. results = testResults;
  410. return q.all(pluginPostTestPromises);
  411. // 6) Teardown plugins
  412. })
  413. .then(() => {
  414. return plugins.teardown();
  415. // 7) Teardown
  416. })
  417. .then(() => {
  418. results = helper.joinTestLogs(results, plugins.getResults());
  419. this.emit('testsDone', results);
  420. testPassed = results.failedCount === 0;
  421. if (this.driverprovider_.updateJob) {
  422. return this.driverprovider_.updateJob({ 'passed': testPassed }).then(() => {
  423. return this.driverprovider_.teardownEnv();
  424. });
  425. }
  426. else {
  427. return this.driverprovider_.teardownEnv();
  428. }
  429. // 8) Let plugins do final cleanup
  430. })
  431. .then(() => {
  432. return plugins.postResults();
  433. // 9) Exit process
  434. })
  435. .then(() => {
  436. let exitCode = testPassed ? 0 : 1;
  437. return this.exit_(exitCode);
  438. })
  439. .fin(() => {
  440. return this.shutdown_();
  441. });
  442. }
  443. }
  444. exports.Runner = Runner;
  445. //# sourceMappingURL=runner.js.map