Repository: Codeception/CodeceptJS
Pull request #1644 (2019-05-07 15:07:12)

Update ts Locator definitions
Updated `ILocator` definition (not founded use of `value` and removed it, added `id`)

Simplify TS definitions for functions with `locator`.

Fixed tests for parser.
Added tests for locator constructor (not necessary, but why not).

Need some TS developers review for errors, style and improvements

codacy-bot: 
![Codacy](https://app.codacy.com/assets/images/favicon.png) Here is an overview of what got changed by this pull request:


```diff

Clones added
============
- test/unit/locator_test.js  2
         
```


See the complete overview on [Codacy](https://app.codacy.com/app/DavertMik/CodeceptJS/pullRequest?prid=3550136&bid=12629213)
        
LukoyanovE: maybe `ilocator` should be changed to something like:
```
type ILocator =
  { id: string } |
  { xpath: string } |
  { css: string } |
  { name: string } |
  { frame: string } |
  { android: string } |
  { ios: string } |
  { react: string, props?: {}, state?: {} }
```
DavertMik: I'm approving, waiting for others to review.
codacy-bot: 
![Codacy](https://app.codacy.com/assets/images/favicon.png) Here is an overview of what got changed by this pull request:


```diff

Clones added
============
- test/unit/locator_test.js  2
         
```


See the complete overview on [Codacy](https://app.codacy.com/app/DavertMik/CodeceptJS/pullRequest?prid=3550136&bid=12629213)
        

Patch:
diff --git a/lib/command/definitions.js b/lib/command/definitions.js
index fdfd5eea..ef68d1d7 100644
--- a/lib/command/definitions.js
+++ b/lib/command/definitions.js
@@ -29,15 +29,17 @@ declare class ScenarioConfig {
 }
 
 interface ILocator {
+  id?: string;
   xpath?: string;
   css?: string;
   name?: string;
-  value?: string;
   frame?: string;
   android?: string;
   ios?: string;
 }
 
+type LocatorOrString = string | ILocator | Locator;
+
 declare class Helper {
   /** Abstract method to provide required config options */
   static _config(): any;
@@ -78,28 +80,22 @@ declare class Helper {
   debugSection(section: string, msg: string): void
 }
 
-declare class Locator implements ILocator {
-  xpath?: string;
-  css?: string;
-  name?: string;
-  value?: string;
-  frame?: string;
-  android?: string;
-  ios?: string;
+declare class Locator {
+  constructor(locator: LocatorOrString, defaultType?: string);
 
-  or(locator:string): Locator;
-  find(locator:string): Locator;
-  withChild(locator:string): Locator;
-  find(locator:string): Locator;
-  at(position:number): Locator;
+  or(locator: LocatorOrString): Locator;
+  find(locator: LocatorOrString): Locator;
+  withChild(locator: LocatorOrString): Locator;
+  withDescendant(locator: LocatorOrString): Locator;
+  at(position: number): Locator;
   first(): Locator;
   last(): Locator;
-  inside(locator:string): Locator;
-  before(locator:string): Locator;
-  after(locator:string): Locator;
-  withText(locator:string): Locator;
-  withAttr(locator:object): Locator;
-  as(locator:string): Locator;
+  inside(locator: LocatorOrString): Locator;
+  before(locator: LocatorOrString): Locator;
+  after(locator: LocatorOrString): Locator;
+  withText(text: string): Locator;
+  withAttr(attrs: object): Locator;
+  as(output: string): Locator;
 }
 
 
@@ -123,14 +119,10 @@ declare function BeforeSuite(callback: ICodeceptCallback): void;
 declare function After(callback: ICodeceptCallback): void;
 declare function AfterSuite(callback: ICodeceptCallback): void;
 
-declare function locate(selector: string): Locator;
-declare function locate(selector: ILocator): Locator;
-declare function within(selector: string, callback: Function): Promise<any>;
-declare function within(selector: ILocator, callback: Function): Promise<any>;
+declare function locate(selector: LocatorOrString): Locator;
+declare function within(selector: LocatorOrString, callback: Function): Promise<any>;
 declare function session(selector: string, callback: Function): Promise<any>;
-declare function session(selector: ILocator, callback: Function): Promise<any>;
 declare function session(selector: string, config: any, callback: Function): Promise<any>;
-declare function session(selector: ILocator, config: any, callback: Function): Promise<any>;
 declare function pause(): void;
 
 declare const codeceptjs: any;
diff --git a/lib/helper/WebDriver.js b/lib/helper/WebDriver.js
index c71bd795..f6bea0f7 100644
--- a/lib/helper/WebDriver.js
+++ b/lib/helper/WebDriver.js
@@ -353,7 +353,7 @@ class WebDriver extends Helper {
     super(config);
     webdriverio = requireg('webdriverio');
     if (webdriverio.VERSION && webdriverio.VERSION.indexOf('4') === 0) {
-      throw new Error('This helper is compatible with "webdriverio@5". Please upgrade webdriverio to v5+ or use WebDriverIO helper instead');
+      throw new Error(`This helper is compatible with "webdriverio@5". Current version: ${webdriverio.VERSION}. Please upgrade webdriverio to v5+ or use WebDriverIO helper instead`);
     }
     // set defaults
     this.root = webRoot;
@@ -606,10 +606,19 @@ class WebDriver extends Helper {
    * this.helpers['WebDriver']._locate({name: 'password'}).then //...
    * ```
    *
+   *
    * @param locator element located by CSS|XPath|strict locator.
    */
   async _locate(locator, smartWait = false) {
     if (require('../store').debugMode) smartWait = false;
+
+    // special locator type for React
+    if (locator.react) {
+      const els = await this.browser.react$$(locator.react, locator.props || undefined, locator.state || undefined);
+      this.debugSection('Elements', `Found ${els.length} react components`);
+      return els;
+    }
+
     if (!this.options.smartWait || !smartWait) {
       const els = await this.$$(withStrictLocator(locator));
       return els;
@@ -1092,8 +1101,8 @@ class WebDriver extends Helper {
    * * *Appium*: supported
    */
   async seeElement(locator) {
-    const res = await this._locate(withStrictLocator(locator), true);
-    assertElementExists(res);
+    const res = await this._locate(locator, true);
+    assertElementExists(res, locator);
     const selected = await forEachAsync(res, async el => el.isDisplayed());
     return truth(`elements of ${locator}`, 'to be seen').assert(selected);
   }
@@ -1105,7 +1114,7 @@ class WebDriver extends Helper {
    * * *Appium*: supported
    */
   async dontSeeElement(locator) {
-    const res = await this._locate(withStrictLocator(locator), false);
+    const res = await this._locate(locator, false);
     if (!res || res.length === 0) {
       return truth(`elements of ${locator}`, 'to be seen').negate(false);
     }
@@ -2193,7 +2202,7 @@ async function filterAsync(array, callback) {
 
 async function findClickable(locator, locateFn) {
   locator = new Locator(locator);
-  if (!locator.isFuzzy()) return locateFn(locator.simplify(), true);
+  if (!locator.isFuzzy()) return locateFn(locator, true);
 
   let els;
   const literal = xpathLocator.literal(locator.value);
@@ -2213,7 +2222,7 @@ async function findClickable(locator, locateFn) {
 
 async function findFields(locator) {
   locator = new Locator(locator);
-  if (!locator.isFuzzy()) return this._locate(locator.simplify(), true);
+  if (!locator.isFuzzy()) return this._locate(locator, true);
 
   const literal = xpathLocator.literal(locator.value);
   let els = await this._locate(Locator.field.byText(literal));
@@ -2313,7 +2322,7 @@ async function proceedSeeCheckbox(assertType, field) {
 async function findCheckable(locator, locateFn) {
   let els;
   locator = new Locator(locator);
-  if (!locator.isFuzzy()) return locateFn(locator.simplify(), true);
+  if (!locator.isFuzzy()) return locateFn(locator, true);
 
   const literal = xpathLocator.literal(locator.value);
   els = await locateFn(Locator.checkable.byText(literal));
@@ -2362,12 +2371,13 @@ function getElementId(el) {
 
 function prepareLocateFn(context) {
   if (!context) return this._locate.bind(this);
-  let el;
   return (l) => {
     l = new Locator(l, 'css');
-    if (el) return this.browser.findElementsFromElement(el, l.type, l.value);
     return this._locate(context, true).then(async (res) => {
       assertElementExists(res, context, 'Context element');
+      if (l.react) {
+        return res[0].react$$(l.react, l.props || undefined);
+      }
       return res[0].$$(l.simplify());
     });
   };
diff --git a/lib/parser.js b/lib/parser.js
index 99eaa446..f466dad8 100644
--- a/lib/parser.js
+++ b/lib/parser.js
@@ -60,7 +60,7 @@ function guessParamTypes(paramName) {
     case 'field':
     case 'context':
     case 'select':
-      return ['ILocator', 'string'];
+      return ['LocatorOrString'];
     case 'num':
     case 'sec':
     case 'width':
diff --git a/package.json b/package.json
index 036e69c4..2e3fce4a 100644
--- a/package.json
+++ b/package.json
@@ -49,7 +49,7 @@
     "fn-args": "^4.0.0",
     "gherkin": "^5.1.0",
     "glob": "^6.0.1",
-    "inquirer": "^6.2.2",
+    "inquirer": "^6.3.1",
     "js-beautify": "^1.9.1",
     "lodash.merge": "^4.6.1",
     "mkdirp": "^0.5.1",
@@ -61,36 +61,36 @@
   },
   "devDependencies": {
     "@types/inquirer": "^0.0.35",
-    "@types/node": "^8.10.45",
-    "@wdio/sauce-service": "^5.7.8",
-    "@wdio/selenium-standalone-service": "^5.7.8",
-    "@wdio/utils": "^5.7.8",
+    "@types/node": "^8.10.48",
+    "@wdio/sauce-service": "^5.8.0",
+    "@wdio/selenium-standalone-service": "^5.8.0",
+    "@wdio/utils": "^5.8.0",
     "chai": "^3.4.1",
     "chai-as-promised": "^5.2.0",
     "co-mocha": "^1.2",
     "documentation": "^8.1.2",
     "eslint": "^4.17.0",
     "eslint-config-airbnb-base": "^12.1.0",
-    "eslint-plugin-import": "^2.16.0",
+    "eslint-plugin-import": "^2.17.2",
     "eslint-plugin-mocha": "^5.3.0",
     "faker": "^4.1.0",
-    "gulp": "^4.0.0",
+    "gulp": "^4.0.1",
     "gulp-append-prepend": "^1.0.8",
     "gulp-documentation": "^3.2.1",
     "gulp-mustache": "^2.2.0",
     "husky": "^1.2.1",
     "json-server": "^0.10.1",
-    "nightmare": "^3.0.0",
+    "nightmare": "^3.0.2",
     "nyc": "^11.9.0",
     "protractor": "^5.4.1",
-    "puppeteer": "^1.14.0",
+    "puppeteer": "^1.15.0",
     "rosie": "^1.6.0",
     "sinon": "^1.17.2",
     "sinon-chai": "^2.14.0",
     "typescript": "^2.9.2",
     "unirest": "^0.5.1",
     "wdio-docker-service": "^1.5.0",
-    "webdriverio": "^5.7.0",
+    "webdriverio": "^5.8.0",
     "xmldom": "^0.1.27",
     "xpath": "0.0.27"
   },
diff --git a/typings/index.d.ts b/typings/index.d.ts
index ce5fc642..85a76d30 100644
--- a/typings/index.d.ts
+++ b/typings/index.d.ts
@@ -36,6 +36,8 @@ declare module NodeJS {
     session: any;
     DataTable: any;
     locate: Locator,
+    inject: any,
+    secret: any,
     by: any;
 
     // Used by Protractor helper


Test patch:
diff --git a/examples/absolutenet_test.js b/examples/absolutenet_test.js
deleted file mode 100644
index b4dc65ee..00000000
--- a/examples/absolutenet_test.js
+++ /dev/null
@@ -1,9 +0,0 @@
-Feature('Testing Begins');
-
-// test with some demo params
-Scenario('ANI testing', { id: 123, user_id: 1235 }, function* (I) {
-  I.amOnPage('http://www.absolutenet.com/');
-  const title = yield I.grabTitle();
-  // console.info(title);
-  I.see('bogus text that is not there'); // this should give an error and not success.
-});
diff --git a/examples/react_test.js b/examples/react_test.js
new file mode 100644
index 00000000..47e10c3d
--- /dev/null
+++ b/examples/react_test.js
@@ -0,0 +1,11 @@
+Feature('React Apps');
+
+Scenario('try react app', (I) => {
+  I.amOnPage('https://ahfarmer.github.io/calculator/');
+  I.click('7');
+  I.seeElement({ react: 't', props: { name: '5' } });
+  I.click('button', { react: 't', props: { name: '2' } });
+  I.click('button', { react: 't', props: { name: '+' } });
+  I.click('button', { react: 't', props: { name: '9' } });
+  I.seeElement({ react: 't', props: { value: '81' } });
+});
diff --git a/test/unit/locator_test.js b/test/unit/locator_test.js
index e4b774e5..f068f5e2 100644
--- a/test/unit/locator_test.js
+++ b/test/unit/locator_test.js
@@ -61,6 +61,64 @@ describe('Locator', () => {
     doc = new Dom().parseFromString(xml);
   });
 
+  describe('constructor', () => {
+    describe('with string argument', () => {
+      it('should create css locator', () => {
+        const l = new Locator('#foo');
+        expect(l.type).to.equal('css');
+        expect(l.value).to.equal('#foo');
+        expect(l.toString()).to.equal('#foo');
+      });
+
+      it('should create xpath locator', () => {
+        const l = new Locator('//foo[@bar="baz"]/*');
+        expect(l.type).to.equal('xpath');
+        expect(l.value).to.equal('//foo[@bar="baz"]/*');
+        expect(l.toString()).to.equal('//foo[@bar="baz"]/*');
+      });
+
+      it('should create fuzzy locator', () => {
+        const l = new Locator('foo');
+        expect(l.type).to.equal('fuzzy');
+        expect(l.value).to.equal('foo');
+        expect(l.toString()).to.equal('foo');
+      });
+
+      it('should create described custom default type locator', () => {
+        const l = new Locator('foo', 'defaultLocator');
+        expect(l.type).to.equal('defaultLocator');
+        expect(l.value).to.equal('foo');
+        expect(l.toString()).to.equal('foo');
+      });
+    });
+
+    describe('with object argument', () => {
+      it('should create id locator', () => {
+        const l = new Locator({ id: 'foo' });
+        expect(l.type).to.equal('id');
+        expect(l.value).to.equal('foo');
+        expect(l.toString()).to.equal('{id: foo}');
+      });
+
+      it('should create described custom locator', () => {
+        const l = new Locator({ customLocator: '=foo' });
+        expect(l.type).to.equal('customLocator');
+        expect(l.value).to.equal('=foo');
+        expect(l.toString()).to.equal('{customLocator: =foo}');
+      });
+    });
+
+    describe('with Locator object argument', () => {
+      it('should create id locator', () => {
+        const l = new Locator(new Locator({ id: 'foo' }));
+        expect(l).to.eql(new Locator({ id: 'foo' }));
+        expect(l.type).to.equal('id');
+        expect(l.value).to.equal('foo');
+        expect(l.toString()).to.equal('{id: foo}');
+      });
+    });
+  });
+
   it('should transform CSS to xpath', () => {
     const l = new Locator('p > #user', 'css');
     const nodes = xpath.select(l.toXPath(), doc);
diff --git a/test/unit/parser_test.js b/test/unit/parser_test.js
index 5b6c22aa..1a87fe8c 100644
--- a/test/unit/parser_test.js
+++ b/test/unit/parser_test.js
@@ -28,30 +28,22 @@ describe('parser', () => {
   describe('#toTypeDef', () => {
     it('should transform function to TS types', () => {
       const res = parser.toTypeDef(obj.method1);
-      expect(res).to.include('    method1(locator: ILocator, sec: number) : void');
-      expect(res).to.include('    method1(locator: string, sec: number) : void');
+      expect(res).to.include('    method1(locator: LocatorOrString, sec: number) : void');
     });
 
     it('should transform function to TS types', () => {
       const res = parser.toTypeDef(obj.method2);
-      expect(res).to.include('method2(locator: ILocator, value: string, sec: number) : void');
-      expect(res).to.include('method2(locator: string, value: string, sec: number) : void');
+      expect(res).to.include('method2(locator: LocatorOrString, value: string, sec: number) : void');
     });
 
     it('should transform function to TS types', () => {
       const res = parser.toTypeDef(obj.method3);
-      expect(res).to.include('method3(locator: ILocator, context: ILocator) : void');
-      expect(res).to.include('method3(locator: ILocator, context: string) : void');
-      expect(res).to.include('method3(locator: string, context: ILocator) : void');
-      expect(res).to.include('method3(locator: string, context: string) : void');
+      expect(res).to.include('method3(locator: LocatorOrString, context: LocatorOrString) : void');
     });
 
     it('should transform function to TS types', () => {
       const res = parser.toTypeDef(obj.method4);
-      expect(res).to.include('method4(locator: ILocator, context: ILocator) : void');
-      expect(res).to.include('method4(locator: ILocator, context: string) : void');
-      expect(res).to.include('method4(locator: string, context: ILocator) : void');
-      expect(res).to.include('method4(locator: string, context: string) : void');
+      expect(res).to.include('method4(locator: LocatorOrString, context: LocatorOrString) : void');
     });
   });
 });