Repository: Codeception/CodeceptJS
Pull request #1481 (2019-02-01 13:37:59)

Webdriver: grabHTMLFrom returns array of innerHTML from the set of matched elements
Before 2.0, grabHTMLFrom returned an array of strings with innerHTML of the set of matched elements. After 2.0, it returns the outerHTML of the first element. That it returns the outerHTML is a bug as documentation says that it returns innerHTML and other helpers also return innerHTML. 

That it returns an array instead of just the first element is similar to the usage of getHTML of webdriverio, where you have a $(selector).getHTML(false) to return innerHTML for a set of matches. And it is very useful when you need to test multiple elements (e.g. "ul > li"). And if you only need the first result, you can simply use destruction (like "const [html] = await I.grabHTMLFrom(...);" ). Last but not least, an upgrade to 2.0 would not break the grabHTMLFrom function. 


Patch:
diff --git a/lib/command/interactive.js b/lib/command/interactive.js
index 5f321525..478e9e54 100644
--- a/lib/command/interactive.js
+++ b/lib/command/interactive.js
@@ -23,8 +23,13 @@ module.exports = function (path, options) {
 
     output.print('String interactive shell for current suite...');
     recorder.start();
-    event.emit(event.suite.before, {});
-    event.emit(event.test.before);
+    event.emit(event.suite.before, {
+      fullTitle: () => 'Interactive Shell',
+      tests: [],
+    });
+    event.emit(event.test.before, {
+      title: '',
+    });
     require('../pause')();
     recorder.add(() => event.emit(event.test.after));
     recorder.add(() => event.emit(event.suite.after, {}));
diff --git a/lib/helper/Nightmare.js b/lib/helper/Nightmare.js
index f95be1aa..27e6cb04 100644
--- a/lib/helper/Nightmare.js
+++ b/lib/helper/Nightmare.js
@@ -401,7 +401,7 @@ class Nightmare extends Helper {
    * ```
    */
   async amOnPage(url, headers = null) {
-    if (url.indexOf('http') !== 0) {
+    if (!(/^\w+\:\/\//.test(url))) {
       url = urlResolve(this.options.url, url);
     }
     const currentUrl = await this.browser.url();
diff --git a/lib/helper/Protractor.js b/lib/helper/Protractor.js
index 3fa9749f..cdadaa90 100644
--- a/lib/helper/Protractor.js
+++ b/lib/helper/Protractor.js
@@ -427,7 +427,7 @@ class Protractor extends Helper {
    * {{> ../webapi/amOnPage }}
    */
   async amOnPage(url) {
-    if (url.indexOf('http') !== 0) {
+    if (!(/^\w+\:\/\//.test(url))) {
       url = this.options.url + url;
     }
     const res = await this.browser.get(url);
diff --git a/lib/helper/Puppeteer.js b/lib/helper/Puppeteer.js
index 98451ab8..5c8c5ff9 100644
--- a/lib/helper/Puppeteer.js
+++ b/lib/helper/Puppeteer.js
@@ -494,7 +494,7 @@ class Puppeteer extends Helper {
    * {{> ../webapi/amOnPage }}
    */
   async amOnPage(url) {
-    if (url.indexOf('http') !== 0) {
+    if (!(/^\w+\:\/\//.test(url))) {
       url = this.options.url + url;
     }
     await this.page.goto(url, { waitUntil: this.options.waitForNavigation });
diff --git a/lib/helper/WebDriver.js b/lib/helper/WebDriver.js
index 573bad4a..a4809f90 100644
--- a/lib/helper/WebDriver.js
+++ b/lib/helper/WebDriver.js
@@ -777,10 +777,9 @@ class WebDriver extends Helper {
    * * *Appium*: supported only for web testing
    */
   async grabHTMLFrom(locator) {
-    const res = await this._locate(locator, true);
-    assertElementExists(res, locator);
-    const elem = usingFirstElement(res);
-    const html = await elem.getHTML();
+    const elems = await this._locate(locator, true);
+    assertElementExists(elems, locator);
+    const html = Promise.all(elems.map(async elem => elem.getHTML(false)));
     this.debugSection('Grab', html);
     return html;
   }
diff --git a/lib/interfaces/scenarioConfig.js b/lib/interfaces/scenarioConfig.js
index 0892749a..568ab7d6 100644
--- a/lib/interfaces/scenarioConfig.js
+++ b/lib/interfaces/scenarioConfig.js
@@ -83,6 +83,16 @@ class ScenarioConfig {
     this.test.title = `${this.test.title.trim()} ${tagName}`;
     return this;
   }
+
+  /**
+   * Dynamically injects dependencies, see https://codecept.io/pageobjects/#dynamic-injection
+   * @param {*} dependencies
+   */
+  injectDependencies(dependencies) {
+    Object.keys(dependencies).forEach((key) => {
+      this.test.inject[key] = dependencies[key];
+    });
+  }
 }
 
 module.exports = ScenarioConfig;
diff --git a/lib/plugin/stepByStepReport.js b/lib/plugin/stepByStepReport.js
index 506323a4..919a8091 100644
--- a/lib/plugin/stepByStepReport.js
+++ b/lib/plugin/stepByStepReport.js
@@ -11,6 +11,7 @@ const { template, clearString, deleteDir } = require('../utils');
 
 const supportedHelpers = [
   'WebDriverIO',
+  'WebDriver',
   'Protractor',
   'Appium',
   'Nightmare',
@@ -172,7 +173,7 @@ module.exports = function (config) {
 
       slideHtml += template(templates.slides, {
         image: i,
-        caption: step.toString(),
+        caption: step.toString().replace(/\[\d{2}m/g, ''), // remove ANSI escape sequence
         isActive: stepNum ? '' : 'active',
         isError: step.status === 'failed' ? 'error' : '',
       });
diff --git a/lib/ui.js b/lib/ui.js
index 2a263190..e76c3ba0 100644
--- a/lib/ui.js
+++ b/lib/ui.js
@@ -46,11 +46,6 @@ module.exports = function (suite) {
       }
       const test = new Test(title, fn);
       test.fullTitle = () => `${suite.title}: ${test.title}`;
-      test.injectDependencies = (deps) => {
-        Object.keys(deps).forEach((key) => {
-          test.inject[key] = deps[key];
-        });
-      };
 
       test.tags = (suite.tags || []).concat(title.match(/(\@[a-zA-Z0-9-_]+)/g)); // match tags from title
       test.file = file;


Test patch:
diff --git a/test/data/sandbox/page_slider.html b/test/data/sandbox/page_slider.html
new file mode 100644
index 00000000..ecd35da0
--- /dev/null
+++ b/test/data/sandbox/page_slider.html
@@ -0,0 +1,35 @@
+<!-- Example based oncode from https://www.w3schools.com/howto/howto_js_rangeslider.asp -->
+
+<div id="slidecontainer">
+  <input
+    type="range"
+    min="1"
+    max="100"
+    value="50"
+    class="slider"
+    id="mySlider"
+  />
+</div>
+<div><span>Value:</span> <span id="demo"></span></div>
+
+<script>
+  var slider = document.getElementById("mySlider");
+  var output = document.getElementById("demo");
+  output.innerHTML = slider.value;
+
+  slider.oninput = function() {
+    output.innerHTML = this.value;
+  };
+</script>
+
+<style>
+  #slidecontainer {
+    width: 100%;
+  }
+  .slider {
+    width: 100%;
+    height: 25px;
+    background: #d3d3d3;
+    outline: none;
+  }
+</style>
diff --git a/test/helper/Puppeteer_test.js b/test/helper/Puppeteer_test.js
index cf9b887a..e591f9dc 100644
--- a/test/helper/Puppeteer_test.js
+++ b/test/helper/Puppeteer_test.js
@@ -550,7 +550,7 @@ describe('Puppeteer', function () {
 
   describe('#dragSlider', () => {
     it('should drag scrubber to given position', async () => {
-      await I.amOnPage('https://www.w3schools.com/howto/howto_js_rangeslider.asp');
+      await I.amOnPage(`file://${path.resolve(__dirname, '../data/sandbox/page_slider.html')}`);
       await I.seeElementInDOM('#slidecontainer input');
       const before = await I.grabValueFrom('#slidecontainer input');
       await I.dragSlider('#slidecontainer input', 20);
diff --git a/test/helper/WebDriver_test.js b/test/helper/WebDriver_test.js
index ea8ba300..2f5d6be9 100644
--- a/test/helper/WebDriver_test.js
+++ b/test/helper/WebDriver_test.js
@@ -167,13 +167,13 @@ describe('WebDriver', function () {
       .then(() => wd.grabSource())
       .then(source => assert.notEqual(source.indexOf('<title>TestEd Beta 2.0</title>'), -1, 'Source html should be retrieved')));
 
-    it('should grab the source for element', () => wd.amOnPage('/')
+    it('should grab the innerHTML for an element', () => wd.amOnPage('/')
       .then(() => wd.grabHTMLFrom('#area1'))
-      .then(source => assert.equal(
+      .then(source => assert.deepEqual(
         source,
-        `<div id="area1" qa-id="test">
+        [`
     <a href="/form/file" qa-id="test" qa-link="test"> Test Link </a>
-</div>`,
+`],
       )));
   });
 
diff --git a/test/unit/ui_test.js b/test/unit/ui_test.js
index 4e591907..4552970a 100644
--- a/test/unit/ui_test.js
+++ b/test/unit/ui_test.js
@@ -110,5 +110,11 @@ describe('ui', () => {
       scenarioConfig.tag('@user');
       scenarioConfig.test.tags.should.include('@user');
     });
+
+    it('should dynamically inject dependencies', () => {
+      scenarioConfig = context.Scenario('scenario');
+      scenarioConfig.injectDependencies({ Data: 'data' });
+      assert.equal(scenarioConfig.test.inject.Data, 'data');
+    });
   });
 });