Repository: Codeception/CodeceptJS
Pull request #967 (2018-02-26 16:30:19)

[Events] Added event.all.before and event.all.after events
#### Highlights

* Added event.all.before and event.all.after events. #604 
* Added unit and acceptance tests for events
* Adding new Helper hook (`_passed`) which is triggered after a passed tests (similar to _failed). #764 

#### Background

These two events are already documented however there were not being emitted.



Patch:
diff --git a/bin/codecept.js b/bin/codecept.js
index fc720e62..630e1ba5 100755
--- a/bin/codecept.js
+++ b/bin/codecept.js
@@ -25,7 +25,6 @@ program.command('list [path]')
 
 program.command('def [path]')
   .description('List all actions for I.')
-  .option('-c, --config [file]', 'configuration file to be used')
   .action(require('../lib/command/definitions'));
 
 program.command('generate:test [path]')
diff --git a/docs/commands.md b/docs/commands.md
index 67b17d03..8f6ecf75 100644
--- a/docs/commands.md
+++ b/docs/commands.md
@@ -131,7 +131,6 @@ TypeScript Definitions allows IDEs to provide autocompletion when writing tests.
 
 ```sh
 codeceptjs def
-codeceptjs def --config path/to/codecept.json
 ```
 
 Produces `steps.d.ts` file, which referenced in the very beginning of a test file.
diff --git a/docs/helpers.md b/docs/helpers.md
index d96064cb..e3ccaa17 100644
--- a/docs/helpers.md
+++ b/docs/helpers.md
@@ -189,6 +189,8 @@ Implement corresponding methods to them.
 * `_afterStep` - after each step
 * `_beforeSuite` - before each suite
 * `_afterSuite` - after each suite
+* `_passed` - after a test passed
+* `_failed` - after a test failed
 
 Each implemented method should return a value as they will be added to global promise chain as well.
 
diff --git a/lib/codecept.js b/lib/codecept.js
index b5433f46..04921c9a 100644
--- a/lib/codecept.js
+++ b/lib/codecept.js
@@ -104,6 +104,7 @@ class Codecept {
     mocha.run(() => {
       const done = () => {
         event.emit(event.all.result, this);
+        event.emit(event.all.after, this);
       };
       this.teardown(done);
     });
diff --git a/lib/command/definitions.js b/lib/command/definitions.js
index 346788b8..a0532f21 100644
--- a/lib/command/definitions.js
+++ b/lib/command/definitions.js
@@ -29,10 +29,9 @@ declare module "codeceptjs" {
 }
 `;
 
-module.exports = function (genPath, options) {
-  const configFile = options.config || genPath;
-  const testsPath = getTestRoot(configFile);
-  const config = getConfig(configFile);
+module.exports = function (genPath) {
+  const testsPath = getTestRoot(genPath);
+  const config = getConfig(testsPath);
   if (!config) return;
 
   const codecept = new Codecept(config, {});
diff --git a/lib/command/run.js b/lib/command/run.js
index b6a3ac4d..f55147f7 100644
--- a/lib/command/run.js
+++ b/lib/command/run.js
@@ -8,6 +8,7 @@ const mkdirp = require('mkdirp');
 const Config = require('../config');
 const Codecept = require('../codecept');
 const output = require('../output');
+const event = require('../event');
 
 module.exports = function (test, options) {
   // registering options globally to use in config
@@ -34,6 +35,7 @@ module.exports = function (test, options) {
     codecept = new Codecept(config, options);
     codecept.init(testRoot, (err) => {
       if (err) throw new Error(`Error while running bootstrap file :${err}`);
+      event.emit(event.all.before, this);
       codecept.loadTests();
       codecept.run(test);
     });
diff --git a/lib/helper.js b/lib/helper.js
index 0eb45c70..dd6ad9d1 100644
--- a/lib/helper.js
+++ b/lib/helper.js
@@ -57,6 +57,15 @@ class Helper {
 
   }
 
+  /**
+   * Hook executed after each passed test
+   *
+   * @param {*} test
+   */
+  _passed(test) {
+
+  }
+
   /**
    * Hook executed after each failed test
    *
diff --git a/lib/helper/Puppeteer.js b/lib/helper/Puppeteer.js
index b9598fb8..f53e9303 100644
--- a/lib/helper/Puppeteer.js
+++ b/lib/helper/Puppeteer.js
@@ -1234,97 +1234,6 @@ class Puppeteer extends Helper {
     }));
   }
 
-  /**
-   * {{> ../webapi/waitForEnabled }}
-   */
-  async waitForEnabled(locator, sec) {
-    const waitTimeout = sec ? sec * 1000 : this.options.waitForTimeout;
-    locator = new Locator(locator, 'css');
-    const matcher = await this.context;
-    let waiter;
-    const context = await this._getContext();
-    if (locator.isCSS()) {
-      const enabledFn = function (locator) {
-        const els = document.querySelectorAll(locator);
-        if (!els || els.length === 0) {
-          return false;
-        }
-        return Array.prototype.filter.call(els, el => !el.disabled).length > 0;
-      };
-      waiter = context.waitForFunction(enabledFn, { timeout: waitTimeout }, locator.value);
-    } else {
-      const enabledFn = function (locator, $XPath) {
-        eval($XPath); // eslint-disable-line no-eval
-        return $XPath(null, locator).filter(el => !el.disabled).length > 0;
-      };
-      waiter = context.waitForFunction(enabledFn, { timeout: waitTimeout }, locator.value, $XPath.toString());
-    }
-    return waiter.catch((err) => {
-      throw new Error(`element (${locator.toString()}) still not enabled after ${waitTimeout / 1000} sec\n${err.message}`);
-    });
-  }
-
-  /**
-   * {{> ../webapi/waitForValue }}
-   */
-  async waitForValue(locator, value, sec) {
-    const waitTimeout = sec ? sec * 1000 : this.options.waitForTimeout;
-    locator = new Locator(locator, 'css');
-    const matcher = await this.context;
-    let waiter;
-    const context = await this._getContext();
-    if (locator.isCSS()) {
-      const valueFn = function (locator, value) {
-        const els = document.querySelectorAll(locator);
-        if (!els || els.length === 0) {
-          return false;
-        }
-        return Array.prototype.filter.call(els, el => (el.value || '').indexOf(value) !== -1).length > 0;
-      };
-      waiter = context.waitForFunction(valueFn, { timeout: waitTimeout }, locator.value, value);
-    } else {
-      const valueFn = function (locator, $XPath, value) {
-        eval($XPath); // eslint-disable-line no-eval
-        return $XPath(null, locator).filter(el => (el.value || '').indexOf(value) !== -1).length > 0;
-      };
-      waiter = context.waitForFunction(valueFn, { timeout: waitTimeout }, locator.value, $XPath.toString(), value);
-    }
-    return waiter.catch((err) => {
-      const loc = locator.toString();
-      throw new Error(`element (${loc}) is not in DOM or there is no element(${loc}) with value "${value}" after ${waitTimeout / 1000} sec\n${err.message}`);
-    });
-  }
-
-  /**
-   * {{> ../webapi/waitNumberOfVisibleElements }}
-   */
-  async waitNumberOfVisibleElements(locator, num, sec) {
-    const waitTimeout = sec ? sec * 1000 : this.options.waitForTimeout;
-    locator = new Locator(locator, 'css');
-    const matcher = await this.context;
-    let waiter;
-    const context = await this._getContext();
-    if (locator.isCSS()) {
-      const visibleFn = function (locator, num) {
-        const els = document.querySelectorAll(locator);
-        if (!els || els.length === 0) {
-          return false;
-        }
-        return Array.prototype.filter.call(els, el => el.offsetParent !== null).length === num;
-      };
-      waiter = context.waitForFunction(visibleFn, { timeout: waitTimeout }, locator.value, num);
-    } else {
-      const visibleFn = function (locator, $XPath, num) {
-        eval($XPath); // eslint-disable-line no-eval
-        return $XPath(null, locator).filter(el => el.offsetParent !== null).length === num;
-      };
-      waiter = context.waitForFunction(visibleFn, { timeout: waitTimeout }, locator.value, $XPath.toString(), num);
-    }
-    return waiter.catch((err) => {
-      throw new Error(`The number of elements (${locator.toString()}) is not ${num} after ${waitTimeout / 1000} sec\n${err.message}`);
-    });
-  }
-
   /**
    * {{> ../webapi/waitForElement }}
    */
@@ -1886,3 +1795,4 @@ function $XPath(element, selector) {
   }
   return res;
 }
+
diff --git a/lib/helper/WebDriverIO.js b/lib/helper/WebDriverIO.js
index bb122db9..e1a8b6c4 100644
--- a/lib/helper/WebDriverIO.js
+++ b/lib/helper/WebDriverIO.js
@@ -1012,6 +1012,7 @@ class WebDriverIO extends Helper {
    */
   async grabNumberOfVisibleElements(locator) {
     const res = await this._locate(locator);
+    assertElementExists(res, locator);
 
     let selected = await forEachAsync(res.value, async el => this.browser.elementIdDisplayed(el.ELEMENT));
     if (!Array.isArray(selected)) selected = [selected];
@@ -1563,7 +1564,7 @@ class WebDriverIO extends Helper {
 
       if (!Array.isArray(selected)) selected = [selected];
       return selected.length === num;
-    }, aSec * 1000, `The number of elements (${locator}) is not ${num} after ${aSec} sec`);
+    }, aSec * 1000, `The number of elements ${locator} is not ${num} after ${aSec} sec`);
   }
 
   /**
diff --git a/lib/listener/helpers.js b/lib/listener/helpers.js
index f77eee21..0e61683b 100644
--- a/lib/listener/helpers.js
+++ b/lib/listener/helpers.js
@@ -43,6 +43,12 @@ module.exports = function () {
     runAsyncHelpersHook('_before');
   });
 
+  event.dispatcher.on(event.test.passed, (test) => {
+    runAsyncHelpersHook('_passed', test, true);
+    // should not fail test execution, so errors should be caught
+    recorder.catchWithoutStop(e => error(e));
+  });
+
   event.dispatcher.on(event.test.failed, (test) => {
     runAsyncHelpersHook('_failed', test, true);
     // should not fail test execution, so errors should be caught


Test patch:
diff --git a/test/acceptance/codecept.Nightmare.js b/test/acceptance/codecept.Nightmare.js
index 45794b78..fb83b668 100644
--- a/test/acceptance/codecept.Nightmare.js
+++ b/test/acceptance/codecept.Nightmare.js
@@ -1,4 +1,5 @@
 const TestHelper = require('../support/TestHelper');
+const eventHandlers = require('../data/sandbox/eventHandlers');
 
 module.exports.config = {
   tests: './*_test.js',
diff --git a/test/acceptance/codecept.Puppeteer.js b/test/acceptance/codecept.Puppeteer.js
index 8e959464..ef24e78c 100644
--- a/test/acceptance/codecept.Puppeteer.js
+++ b/test/acceptance/codecept.Puppeteer.js
@@ -1,4 +1,5 @@
-const TestHelper = require('../support/TestHelper.js');
+const TestHelper = require('../support/TestHelper');
+const eventHandlers = require('../data/sandbox/eventHandlers');
 
 module.exports.config = {
   tests: './*_test.js',
diff --git a/test/acceptance/codecept.WebDriverIO.js b/test/acceptance/codecept.WebDriverIO.js
index e22031b3..c9a060a6 100644
--- a/test/acceptance/codecept.WebDriverIO.js
+++ b/test/acceptance/codecept.WebDriverIO.js
@@ -1,4 +1,5 @@
-const TestHelper = require('../support/TestHelper.js');
+const TestHelper = require('../support/TestHelper');
+const eventHandlers = require('../data/sandbox/eventHandlers');
 
 module.exports.config = {
   tests: './*_test.js',
diff --git a/test/acceptance/event_test.js b/test/acceptance/event_test.js
new file mode 100644
index 00000000..fc737bfe
--- /dev/null
+++ b/test/acceptance/event_test.js
@@ -0,0 +1,54 @@
+const event = require('../../lib').event;
+const assert = require('assert');
+const expect = require('chai').expect;
+const eventHandlers = require('../data/sandbox/eventHandlers');
+
+const expectedEvents = [];
+
+Feature('Events', { retries: 0 });
+
+BeforeSuite((I) => {
+  expectedEvents.push(...[
+    event.all.before,
+    event.suite.before,
+  ]);
+  expect(eventHandlers.events).to.deep.equal(expectedEvents);
+});
+
+Before((I) => {
+  expectedEvents.push(...[
+    event.test.before,
+  ]);
+  expect(eventHandlers.events).to.deep.equal(expectedEvents);
+});
+
+After((I) => {
+  expectedEvents.push(...[
+    event.test.passed,
+    // event.test.after, // Does not fire until after After()
+  ]);
+  expect(eventHandlers.events).to.deep.equal(expectedEvents);
+});
+
+AfterSuite(() => {
+  expectedEvents.push(...[
+    event.test.after,
+    // event.suite.after, // Does not fire until after AfterSuite()
+    // event.all.result, // Does not fire until after AfterSuite()
+    // event.all.after, // Does not fire until after AfterSuite()
+  ]);
+
+  expect(eventHandlers.events).to.deep.equal(expectedEvents);
+
+  expectedEvents.forEach((name) => {
+    assert.equal(eventHandlers.counter[name], 1, `${name} should have been fired`);
+  });
+});
+
+Scenario('Event Hooks @WebDriverIO @Protractor @Nightmare @Puppeteer', (I) => {
+  expectedEvents.push(...[
+    event.test.started,
+  ]);
+  expect(eventHandlers.events).to.deep.equal(expectedEvents);
+  assert.ok(true);
+});
diff --git a/test/acceptance/within_test.js b/test/acceptance/within_test.js
index e01405d0..da0741a9 100644
--- a/test/acceptance/within_test.js
+++ b/test/acceptance/within_test.js
@@ -35,7 +35,7 @@ Scenario('within on iframe @WebDriverIO @Puppeteer', (I) => {
   I.dontSee('Email Address');
 });
 
-Scenario('within on iframe without iframe navigation @WebDriverIO @nightmare @Puppeteer', (I) => {
+Scenario('within on iframe without iframe navigation @WebDriverIO @Nightmare @Puppeteer', (I) => {
   I.amOnPage('/iframe');
   within({ frame: 'iframe' }, () => {
     I.fillField('rus', 'Updated');
@@ -45,7 +45,7 @@ Scenario('within on iframe without iframe navigation @WebDriverIO @nightmare @Pu
   I.dontSee('Sign in!');
 });
 
-Scenario('within on nested iframe without iframe navigation depth 2 @WebDriverIO @nightmare @Puppeteer', (I) => {
+Scenario('within on nested iframe without iframe navigation depth 2 @WebDriverIO @Nightmare @Puppeteer', (I) => {
   I.amOnPage('/iframe_nested');
   within({ frame: ['[name=wrapper]', '[name=content]'] }, () => {
     I.fillField('rus', 'Updated');
diff --git a/test/data/app/view/form/wait_enabled.php b/test/data/app/view/form/wait_enabled.php
deleted file mode 100644
index e788d574..00000000
--- a/test/data/app/view/form/wait_enabled.php
+++ /dev/null
@@ -1,21 +0,0 @@
-<html>
-<body>
-
-<input id="text" type="text" name="test" disabled="true" value="some text">
-
-<button id="button" type="button" name="button1" disabled="true" value="first" onclick="updateMessage('button was clicked')">A Button</button>
-
-<div id="message"></div>
-
-<script>
-  setTimeout(function () {
-    document.querySelector('#text').disabled = false;
-    document.querySelector('#button').disabled = false;
-  }, 1000);
-
-  function updateMessage(msg) {
-    document.querySelector('#message').textContent = msg;
-  }
-</script>
-</body>
-</html>
diff --git a/test/data/app/view/form/wait_num_elements.php b/test/data/app/view/form/wait_num_elements.php
deleted file mode 100644
index 9c7e3c6a..00000000
--- a/test/data/app/view/form/wait_num_elements.php
+++ /dev/null
@@ -1,26 +0,0 @@
-<html>
-
-<body>
-
-<div id="context"></div>
-<script>
-
-var newDiv = document.createElement("div");
-newDiv.innerHTML = '<h1 class="title">Hello</h1>';
-
-var newDiv2 = document.createElement("div");
-newDiv2.innerHTML = '<h2 class="title">World</h2>';
-
-setTimeout(function () {
-    document.getElementById('context').appendChild(newDiv);
-}, 1000);
-
-setTimeout(function () {
-    document.getElementById('context').appendChild(newDiv2);
-}, 1500);
-
-
-</script>
-
-</body>
-</html>
\ No newline at end of file
diff --git a/test/data/app/view/form/wait_value.php b/test/data/app/view/form/wait_value.php
deleted file mode 100644
index 314cdf4f..00000000
--- a/test/data/app/view/form/wait_value.php
+++ /dev/null
@@ -1,17 +0,0 @@
-<html>
-<body>
-
-<input id="text" class="inputbox" type="text" name="test" value="Grüße aus Hamburg">
-<input id="text2" class="inputbox" type="text" name="test2" value="Grüße aus London">
-
-<script>
-  setTimeout(function () {
-    document.querySelector('#text').value = 'Hello from Brisbane';
-  }, 2000);
-
-  setTimeout(function () {
-    document.querySelector('#text2').value = 'Hello from Brisbane';
-  }, 4000);
-</script>
-</body>
-</html>
diff --git a/test/data/helper.js b/test/data/helper.js
index 5ff87a20..d6eb338a 100644
--- a/test/data/helper.js
+++ b/test/data/helper.js
@@ -5,6 +5,14 @@ class MyHelper extends Helper {
     return 'hello world';
   }
 
+  _passed() {
+    console.log('Event:test.passed (helper)');
+  }
+
+  _failed() {
+    console.log('Event:test.failed (helper)');
+  }
+
   method2() {
     return 'hello another world';
   }
diff --git a/test/data/sandbox/codecept.testevents.js b/test/data/sandbox/codecept.testevents.js
new file mode 100644
index 00000000..5907d0ce
--- /dev/null
+++ b/test/data/sandbox/codecept.testevents.js
@@ -0,0 +1,19 @@
+const eventHandlers = require('./eventHandlers');
+require('../fake_driver');
+
+eventHandlers.setConsoleLogging(true);
+
+module.exports.config = {
+  tests: './*_test.testevents.js',
+  timeout: 10000,
+  output: './output',
+  helpers: {
+    FakeDriver: {
+      require: '../helper',
+    },
+  },
+  include: {},
+  bootstrap: false,
+  mocha: {},
+  name: 'sandbox',
+};
diff --git a/test/data/sandbox/eventHandlers.js b/test/data/sandbox/eventHandlers.js
new file mode 100644
index 00000000..32a5a672
--- /dev/null
+++ b/test/data/sandbox/eventHandlers.js
@@ -0,0 +1,47 @@
+const event = require('../../../lib').event;
+
+const eventTypes = [
+  // All Events
+  event.all.before,
+  event.all.result,
+  event.all.after,
+
+  // Suite events
+  event.suite.before,
+  event.suite.after,
+
+  // Test events
+  event.test.before,
+  event.test.started,
+  event.test.passed,
+  event.test.failed,
+  event.test.after,
+];
+
+let eventRecorder = [];
+let eventTypeCounter = {};
+const options = {
+  logToConsole: false,
+};
+
+const newEventHandler = (name) => {
+  event.dispatcher.on(name, () => {
+    eventRecorder.push(name);
+    eventTypeCounter[name] = (eventTypeCounter[name] || 0) + 1;
+    if (options.logToConsole) {
+      console.log(`Event:${name}`);
+    }
+  });
+};
+
+eventTypes.forEach(name => newEventHandler(name));
+
+module.exports = {
+  events: eventRecorder,
+  counter: eventTypeCounter,
+  clearEvents: () => {
+    eventRecorder = [];
+    eventTypeCounter = {};
+  },
+  setConsoleLogging: on => options.logToConsole = !!on,
+};
diff --git a/test/data/sandbox/testevents_test.testevents.js b/test/data/sandbox/testevents_test.testevents.js
new file mode 100644
index 00000000..4f589219
--- /dev/null
+++ b/test/data/sandbox/testevents_test.testevents.js
@@ -0,0 +1,65 @@
+Feature('Test hooks');
+
+BeforeSuite(() => {
+  console.log('I\'m simple BeforeSuite hook');
+});
+
+Before(() => {
+  console.log('I\'m simple Before hook');
+});
+
+After(() => {
+  console.log('I\'m simple After hook');
+});
+
+AfterSuite(() => {
+  console.log('I\'m simple AfterSuite hook');
+});
+
+BeforeSuite(function* (I) {
+  const text = yield I.stringWithHook('BeforeSuite');
+  console.log(text);
+});
+
+Before(function* (I) {
+  const text = yield I.stringWithHook('Before');
+  console.log(text);
+});
+
+After(function* (I) {
+  const text = yield I.stringWithHook('After');
+  console.log(text);
+});
+
+AfterSuite(function* (I) {
+  const text = yield I.stringWithHook('AfterSuite');
+  console.log(text);
+});
+
+BeforeSuite(async (I) => {
+  const text = await I.asyncStringWithHook('BeforeSuite');
+  console.log(text);
+});
+
+Before(async (I) => {
+  const text = await I.asyncStringWithHook('Before');
+  console.log(text);
+});
+
+After(async (I) => {
+  const text = await I.asyncStringWithHook('After');
+  console.log(text);
+});
+
+AfterSuite(async (I) => {
+  const text = await I.asyncStringWithHook('AfterSuite');
+  console.log(text);
+});
+
+Scenario('Simple test 1 @willpass', () => {
+  console.log('It\'s first test');
+});
+
+Scenario('Simple test 2 with error', () => {
+  assert.ok(false);
+});
diff --git a/test/helper/Puppeteer_test.js b/test/helper/Puppeteer_test.js
index 352c0eb5..12429c65 100644
--- a/test/helper/Puppeteer_test.js
+++ b/test/helper/Puppeteer_test.js
@@ -89,33 +89,6 @@ describe('Puppeteer', function () {
     });
   });
 
-  describe('#waitNumberOfVisibleElements', () => {
-    it('should wait for a specified number of elements on the page', () => I.amOnPage('/info')
-      .then(() => I.waitNumberOfVisibleElements('//div[@id = "grab-multiple"]//a', 3))
-      .then(() => I.waitNumberOfVisibleElements('//div[@id = "grab-multiple"]//a', 2, 0.1))
-      .then(() => {
-        throw Error('It should never get this far');
-      })
-      .catch((e) => {
-        e.message.should.include('The number of elements (//div[@id = "grab-multiple"]//a) is not 2 after 0.1 sec');
-      }));
-
-    it('should wait for a specified number of elements on the page using a css selector', () => I.amOnPage('/info')
-      .then(() => I.waitNumberOfVisibleElements('#grab-multiple > a', 3))
-      .then(() => I.waitNumberOfVisibleElements('#grab-multiple > a', 2, 0.1))
-      .then(() => {
-        throw Error('It should never get this far');
-      })
-      .catch((e) => {
-        e.message.should.include('The number of elements (#grab-multiple > a) is not 2 after 0.1 sec');
-      }));
-
-    it('should wait for a specified number of elements which are not yet attached to the DOM', () => I.amOnPage('/form/wait_num_elements')
-      .then(() => I.waitNumberOfVisibleElements('.title', 2, 3))
-      .then(() => I.see('Hello'))
-      .then(() => I.see('World')));
-  });
-
   describe('#moveCursorTo', () => {
     it('should trigger hover event', () => I.amOnPage('/form/hover')
       .then(() => I.moveCursorTo('#hover'))
@@ -450,55 +423,6 @@ describe('Puppeteer', function () {
       }));
   });
 
-  describe('#waitForEnabled', () => {
-    it('should wait for input text field to be enabled', () => I.amOnPage('/form/wait_enabled')
-      .then(() => I.waitForEnabled('#text', 2))
-      .then(() => I.fillField('#text', 'hello world'))
-      .then(() => I.seeInField('#text', 'hello world')));
-
-    it('should wait for input text field to be enabled by xpath', () => I.amOnPage('/form/wait_enabled')
-      .then(() => I.waitForEnabled("//*[@name = 'test']", 2))
-      .then(() => I.fillField('#text', 'hello world'))
-      .then(() => I.seeInField('#text', 'hello world')));
-
-    it('should wait for a button to be enabled', () => I.amOnPage('/form/wait_enabled')
-      .then(() => I.waitForEnabled('#text', 2))
-      .then(() => I.click('#button'))
-      .then(() => I.see('button was clicked', '#message')));
-  });
-
-  describe('#waitForValue', () => {
-    it('should wait for expected value for given locator', () => I.amOnPage('/info')
-      .then(() => I.waitForValue('//input[@name= "rus"]', 'Верно'))
-      .then(() => I.waitForValue('//input[@name= "rus"]', 'Верно3', 0.1))
-      .then(() => {
-        throw Error('It should never get this far');
-      })
-      .catch((e) => {
-        e.message.should.include('element (//input[@name= "rus"]) is not in DOM or there is no element(//input[@name= "rus"]) with value "Верно3" after 0.1 sec');
-      }));
-
-    it('should wait for expected value for given css locator', () => I.amOnPage('/form/wait_value')
-      .then(() => I.seeInField('#text', 'Hamburg'))
-      .then(() => I.waitForValue('#text', 'Brisbane', 2.5))
-      .then(() => I.seeInField('#text', 'Brisbane')));
-
-    it('should wait for expected value for given xpath locator', () => I.amOnPage('/form/wait_value')
-      .then(() => I.seeInField('#text', 'Hamburg'))
-      .then(() => I.waitForValue('//input[@value = "Grüße aus Hamburg"]', 'Brisbane', 2.5))
-      .then(() => I.seeInField('#text', 'Brisbane')));
-
-    it('should only wait for one of the matching elements to contain the value given xpath locator', () => I.amOnPage('/form/wait_value')
-      .then(() => I.waitForValue('//input[@type = "text"]', 'Brisbane', 4))
-      .then(() => I.seeInField('#text', 'Brisbane'))
-      .then(() => I.seeInField('#text2', 'London')));
-
-    it('should only wait for one of the matching elements to contain the value given css locator', () => I.amOnPage('/form/wait_value')
-      .then(() => I.waitForValue('.inputbox', 'Brisbane', 4))
-      .then(() => I.seeInField('#text', 'Brisbane'))
-      .then(() => I.seeInField('#text2', 'London')));
-  });
-
   describe('#seeCssPropertiesOnElements', () => {
     it('should check css property for given element', () => I.amOnPage('/info')
       .then(() => I.seeCssPropertiesOnElements('h3', {
diff --git a/test/helper/WebDriverIO_test.js b/test/helper/WebDriverIO_test.js
index 5029e7d7..d8d3d486 100644
--- a/test/helper/WebDriverIO_test.js
+++ b/test/helper/WebDriverIO_test.js
@@ -210,23 +210,6 @@ describe('WebDriverIO', function () {
       }));
   });
 
-  describe('#waitForEnabled', () => {
-    it('should wait for input text field to be enabled', () => wd.amOnPage('/form/wait_enabled')
-      .then(() => wd.waitForEnabled('#text', 2))
-      .then(() => wd.fillField('#text', 'hello world'))
-      .then(() => wd.seeInField('#text', 'hello world')));
-
-    it('should wait for input text field to be enabled by xpath', () => wd.amOnPage('/form/wait_enabled')
-      .then(() => wd.waitForEnabled("//*[@name = 'test']", 2))
-      .then(() => wd.fillField('#text', 'hello world'))
-      .then(() => wd.seeInField('#text', 'hello world')));
-
-    it('should wait for a button to be enabled', () => wd.amOnPage('/form/wait_enabled')
-      .then(() => wd.waitForEnabled('#text', 2))
-      .then(() => wd.click('#button'))
-      .then(() => wd.see('button was clicked')));
-  });
-
   describe('#grabCssPropertyFrom', () => {
     it('should grab css property for given element', () => wd.amOnPage('/form/doubleclick')
       .then(() => wd.grabCssPropertyFrom('#block', 'height'))
@@ -319,59 +302,18 @@ describe('WebDriverIO', function () {
     it('should wait for expected value for given locator', () => wd.amOnPage('/info')
       .then(() => wd.waitForValue('//input[@name= "rus"]', 'Верно'))
       .then(() => wd.waitForValue('//input[@name= "rus"]', 'Верно3', 0.1))
-      .then(() => {
-        throw Error('It should never get this far');
-      })
       .catch((e) => {
-        e.message.should.include('element (//input[@name= "rus"]) is not in DOM or there is no element(//input[@name= "rus"]) with value "Верно3" after 0.1 sec');
+        assert.equal(e.message, 'element (//input[@name= "rus"]) is not in DOM or there is no element(//input[@name= "rus"]) with value "Верно3" after 0.1 sec');
       }));
-
-    it('should wait for expected value for given css locator', () => wd.amOnPage('/form/wait_value')
-      .then(() => wd.seeInField('#text', 'Hamburg'))
-      .then(() => wd.waitForValue('#text', 'Brisbane', 2.5))
-      .then(() => wd.seeInField('#text', 'Brisbane')));
-
-    it('should wait for expected value for given xpath locator', () => wd.amOnPage('/form/wait_value')
-      .then(() => wd.seeInField('#text', 'Hamburg'))
-      .then(() => wd.waitForValue('//input[@value = "Grüße aus Hamburg"]', 'Brisbane', 2.5))
-      .then(() => wd.seeInField('#text', 'Brisbane')));
-
-    it('should only wait for one of the matching elements to contain the value given xpath locator', () => wd.amOnPage('/form/wait_value')
-      .then(() => wd.waitForValue('//input[@type = "text"]', 'Brisbane', 4))
-      .then(() => wd.seeInField('#text', 'Brisbane'))
-      .then(() => wd.seeInField('#text2', 'London')));
-
-    it('should only wait for one of the matching elements to contain the value given css locator', () => wd.amOnPage('/form/wait_value')
-      .then(() => wd.waitForValue('.inputbox', 'Brisbane', 4))
-      .then(() => wd.seeInField('#text', 'Brisbane'))
-      .then(() => wd.seeInField('#text2', 'London')));
   });
 
   describe('#waitNumberOfVisibleElements', () => {
     it('should wait for a specified number of elements on the page', () => wd.amOnPage('/info')
       .then(() => wd.waitNumberOfVisibleElements('//div[@id = "grab-multiple"]//a', 3))
       .then(() => wd.waitNumberOfVisibleElements('//div[@id = "grab-multiple"]//a', 2, 0.1))
-      .then(() => {
-        throw Error('It should never get this far');
-      })
       .catch((e) => {
-        e.message.should.include('The number of elements (//div[@id = "grab-multiple"]//a) is not 2 after 0.1 sec');
+        assert.equal(e.message, 'The number of elements //div[@id = "grab-multiple"]//a is not 2 after 0.1 sec');
       }));
-
-    it('should wait for a specified number of elements on the page using a css selector', () => wd.amOnPage('/info')
-      .then(() => wd.waitNumberOfVisibleElements('#grab-multiple > a', 3))
-      .then(() => wd.waitNumberOfVisibleElements('#grab-multiple > a', 2, 0.1))
-      .then(() => {
-        throw Error('It should never get this far');
-      })
-      .catch((e) => {
-        e.message.should.include('The number of elements (#grab-multiple > a) is not 2 after 0.1 sec');
-      }));
-
-    it('should wait for a specified number of elements which are not yet attached to the DOM', () => wd.amOnPage('/form/wait_num_elements')
-      .then(() => wd.waitNumberOfVisibleElements('.title', 2, 3))
-      .then(() => wd.see('Hello'))
-      .then(() => wd.see('World')));
   });
 
   describe('#moveCursorTo', () => {
diff --git a/test/runner/codecept_test.js b/test/runner/codecept_test.js
index ce3948d6..edf3dd1c 100644
--- a/test/runner/codecept_test.js
+++ b/test/runner/codecept_test.js
@@ -2,6 +2,8 @@ const expect = require('chai').expect;
 const assert = require('assert');
 const path = require('path');
 const exec = require('child_process').exec;
+const event = require('../../lib').event;
+const eventHandlers = require('../data/sandbox/eventHandlers');
 
 const runner = path.join(__dirname, '/../../bin/codecept.js');
 const codecept_dir = path.join(__dirname, '/../data/sandbox');
@@ -156,3 +158,61 @@ describe('CodeceptJS Runner', () => {
     });
   });
 });
+
+describe('Codeceptjs Events', () => {
+  it('should fire events with only passing tests', (done) => {
+    exec(`${codecept_run_config('codecept.testevents.js')} --grep @willpass`, (err, stdout) => {
+      assert(!err);
+      const eventMessages = stdout.split('\n')
+        .filter(text => text.startsWith('Event:'))
+        .map(text => text.replace(/^Event:/i, ''));
+
+      expect(eventMessages).to.deep.equal([
+        event.all.before,
+        event.suite.before,
+        event.test.before,
+        event.test.started,
+        event.test.passed,
+        `${event.test.passed} (helper)`,
+        event.test.after,
+        event.suite.after,
+        event.all.result,
+        event.all.after,
+      ]);
+      done();
+    });
+  });
+
+  it('should fire events with passing and failing tests', (done) => {
+    exec(codecept_run_config('codecept.testevents.js'), (err, stdout) => {
+      assert(err);
+      const eventMessages = stdout.split('\n')
+        .filter(text => text.startsWith('Event:'))
+        .map(text => text.replace(/^Event:/i, ''));
+
+      expect(eventMessages).to.deep.equal([
+        event.all.before,
+        event.suite.before,
+
+        // Test 1 (should pass)
+        event.test.before,
+        event.test.started,
+        event.test.passed,
+        `${event.test.passed} (helper)`,
+        event.test.after,
+
+        // Test 2 (should fail)
+        event.test.before,
+        event.test.started,
+        event.test.failed,
+        `${event.test.failed} (helper)`,
+        event.test.after,
+
+        event.suite.after,
+        event.all.result,
+        event.all.after,
+      ]);
+      done();
+    });
+  });
+});
diff --git a/test/runner/interface_test.js b/test/runner/interface_test.js
index 7ef68395..89db6ebb 100644
--- a/test/runner/interface_test.js
+++ b/test/runner/interface_test.js
@@ -87,6 +87,7 @@ describe('CodeceptJS Interface', () => {
         '[1] Queued | return result',
         '[1] Queued | fire test.passed',
         '[1] Queued | finish test',
+        '[1] Queued | hook FileSystem._passed()',
         '[1] Queued | hook FileSystem._after()',
         '[1] Queued | hook FileSystem._afterSuite()',
         '[1] Queued | hook FileSystem._finishTest()',
diff --git a/test/runner/list_test.js b/test/runner/list_test.js
index 46400193..3d8aaa7c 100644
--- a/test/runner/list_test.js
+++ b/test/runner/list_test.js
@@ -30,19 +30,4 @@ describe('list/def commands', () => {
       done();
     });
   });
-
-  it('def should create definition file given a config file', (done) => {
-    try {
-      require('fs').unlinkSync(`${codecept_dir}/steps.d.ts`);
-    } catch (e) {
-      // continue regardless of error
-    }
-    exec(`${runner} def --config ${codecept_dir}/codecept.ddt.json`, (err, stdout, stderr) => {
-      stdout.should.include('Definitions were generated in steps.d.ts');
-      stdout.should.include('<reference path="./steps.d.ts" />');
-      require('fs').existsSync(`${codecept_dir}/steps.d.ts`).should.be.ok;
-      assert(!err);
-      done();
-    });
-  });
 });