Repository: Codeception/CodeceptJS
Pull request #1508 (2019-02-19 12:17:09)

Restore original window size when taking full size screenshot
`I.saveScreenshot(filename, fullPage)` with `fullPage = true` takes full page screenshot, but doesn't preserve or restore original windowsize. 
This PR will fix it.

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
============
- lib/helper/WebDriver.js  1
         
```


See the complete overview on [Codacy](https://app.codacy.com/app/DavertMik/CodeceptJS/pullRequest?prid=3128078&bid=11423854)
        
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
============
- lib/helper/WebDriver.js  1
         
```


See the complete overview on [Codacy](https://app.codacy.com/app/DavertMik/CodeceptJS/pullRequest?prid=3128078&bid=11423854)
        
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
============
- lib/helper/WebDriver.js  1
         
```


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

Patch:
diff --git a/docs/locators.md b/docs/locators.md
index 9704f6b5..c1e9c7b8 100644
--- a/docs/locators.md
+++ b/docs/locators.md
@@ -137,6 +137,15 @@ Finds an element which contains a child element provided:
 locate('form').withChild('select');
 ```
 
+#### withDescendant
+
+Finds an element which contains a descendant element provided:
+
+```js
+// finds form with <select> which is the descendant it
+locate('form').withDescendant('select');
+```
+
 #### withText
 
 Finds element containing a text
diff --git a/docs/translation.md b/docs/translation.md
index 61a2df57..df250129 100644
--- a/docs/translation.md
+++ b/docs/translation.md
@@ -146,3 +146,43 @@ Scenario('成功提交表單', (我) => {
     我.在當前網址中看不到('/documentation')
 });
 ```
+
+## Japanese
+
+Add to config
+
+```json
+  "translation": "ja-JP"
+```
+
+Now you can write test like this:
+
+```js
+Scenario('ログインできる', (私は) => {
+    私は.ページを移動する('/login');
+    私は.フィールドに入力する("Eメール", "foo@example.com");
+    私は.フィールドに入力する("パスワード", "p@ssword");
+    私は.クリックする('ログイン');
+    私は.待つ(1);
+    私は.URLに含まれるか確認する('/home');
+});
+```
+
+## Using your own translation file
+
+Create translation file like this:
+
+```js
+module.exports = {
+  I: '',
+  actions: {
+    click: 'Klicken',
+    wait: 'Wartenn',
+  }
+```
+
+And add the file path to your config
+
+```json
+    "translation": "./path/to/your/translation.js"
+```
diff --git a/lib/helper/WebDriver.js b/lib/helper/WebDriver.js
index 081570fa..8a7e8405 100644
--- a/lib/helper/WebDriver.js
+++ b/lib/helper/WebDriver.js
@@ -99,6 +99,45 @@ const webRoot = 'body';
  * }
  * ```
  *
+ * ### Internet Explorer
+ * Additional configuration params can be used from [IE options](https://seleniumhq.github.io/selenium/docs/api/rb/Selenium/WebDriver/IE/Options.html)
+ *
+ * ```json
+ * {
+ *    "helpers": {
+ *      "WebDriver" : {
+ *        "url": "http://localhost",
+ *        "browser": "internet explorer",
+ *        "desiredCapabilities": {
+ *          "ieOptions": {
+ *            "ie.browserCommandLineSwitches": "-private",
+ *            "ie.usePerProcessProxy": true,
+ *            "ie.ensureCleanSession": true,
+ *          }
+ *        }
+ *      }
+ *    }
+ * }
+ * ```
+ *
+ * ### Selenoid Options
+ *
+ * ```json
+ * {
+ *    "helpers": {
+ *      "WebDriver" : {
+ *        "url": "http://localhost",
+ *        "browser": "chrome",
+ *        "desiredCapabilities": {
+ *          "selenoidOptions": {
+ *            "enableVNC": true,
+ *          }
+ *        }
+ *      }
+ *    }
+ * }
+ * ```
+ *
  * ### Connect through proxy
  *
  * CodeceptJS also provides flexible options when you want to execute tests to Selenium servers through proxy. You will
@@ -284,6 +323,14 @@ class WebDriver extends Helper {
       config.capabilities['moz:firefoxOptions'] = config.capabilities.firefoxOptions;
       delete config.capabilities.firefoxOptions;
     }
+    if (config.capabilities.ieOptions) {
+      config.capabilities['se:ieOptions'] = config.capabilities.ieOptions;
+      delete config.capabilities.ieOptions;
+    }
+    if (config.capabilities.selenoidOptions) {
+      config.capabilities['selenoid:options'] = config.capabilities.selenoidOptions;
+      delete config.capabilities.selenoidOptions;
+    }
 
     config.waitForTimeout /= 1000; // convert to seconds
 
@@ -1244,7 +1291,7 @@ class WebDriver extends Helper {
       const elem = usingFirstElement(res);
       const elementId = getElementId(elem);
       if (this.browser.isMobile) return this.browser.touchScroll(elementId, offsetX, offsetY);
-      const location = await this.browser.getElementLocation(elementId);
+      const location = await elem.getLocation();
       assertElementExists(location, 'Failed to receive', 'location');
       /* eslint-disable prefer-arrow-callback */
       return this.browser.execute(function (x, y) { return window.scrollTo(x, y); }, location.x + offsetX, location.y + offsetY);
@@ -1286,6 +1333,8 @@ class WebDriver extends Helper {
     }
 
     /* eslint-disable prefer-arrow-callback, comma-dangle, prefer-const */
+    const originalWindowSize = await this.browser.getWindowSize();
+
     let { width, height } = await this.browser.execute(function () {
       return {
         height: document.body.scrollHeight,
@@ -1298,7 +1347,9 @@ class WebDriver extends Helper {
 
     await this.browser.setWindowSize(width, height);
     this.debug(`Screenshot has been saved to ${outputFile}, size: ${width}x${height}`);
-    return this.browser.saveScreenshot(outputFile);
+    const buffer = await this.browser.saveScreenshot(outputFile);
+    await this.browser.setWindowSize(originalWindowSize.width, originalWindowSize.height);
+    return buffer;
   }
 
 
diff --git a/lib/locator.js b/lib/locator.js
index afdf2fea..8629feaa 100644
--- a/lib/locator.js
+++ b/lib/locator.js
@@ -116,7 +116,12 @@ class Locator {
   }
 
   withChild(locator) {
-    const xpath = sprintf('%s[//%s]', this.toXPath(), removePrefix((new Locator(locator, 'css')).toXPath()));
+    const xpath = sprintf('%s//child::%s', this.toXPath(), removePrefix((new Locator(locator, 'css')).toXPath()));
+    return new Locator({ xpath });
+  }
+
+  withDescendant(locator) {
+    const xpath = sprintf('%s//descendant::%s', this.toXPath(), removePrefix((new Locator(locator, 'css')).toXPath()));
     return new Locator({ xpath });
   }
 
diff --git a/translations/index.js b/translations/index.js
index 405658d6..f5285e69 100644
--- a/translations/index.js
+++ b/translations/index.js
@@ -4,3 +4,4 @@ exports['it-IT'] = require('./it-IT');
 exports['pl-PL'] = require('./pl-PL');
 exports['zh-CN'] = require('./zh-CN');
 exports['zh-TW'] = require('./zh-TW');
+exports['ja-JP'] = require('./ja-JP');
diff --git a/translations/ja-JP.js b/translations/ja-JP.js
new file mode 100644
index 00000000..c8fd1005
--- /dev/null
+++ b/translations/ja-JP.js
@@ -0,0 +1,55 @@
+module.exports = {
+  I: '私は',
+  actions: {
+    amOutsideAngularApp: 'Angularの外に出る',
+    amInsideAngularApp: 'Angularの中に入る',
+    waitForElement: '要素を待つ',
+    waitForClickable: 'クリック可能になるまで待つ',
+    waitForVisible: '要素が見えるようになるまで待つ',
+    waitForText: 'テキストが表示されるまで待つ',
+    moveTo: '移動する',
+    refresh: '更新する',
+    haveModule: 'モジュールを持っている',
+    resetModule: 'モジュールをリセットする',
+    amOnPage: 'ページを移動する',
+    click: 'クリックする',
+    doubleClick: 'ダブルクリックする',
+    see: 'テキストがあるか確認する',
+    dontSee: 'テキストがないことを確認する',
+    selectOption: 'オプションを選ぶ',
+    fillField: 'フィールドに入力する',
+    pressKey: 'キー入力する',
+    triggerMouseEvent: 'マウスイベントを発火させる',
+    attachFile: 'ファイルを添付する',
+    seeInField: 'フィールドに文字が入っているか確認する',
+    dontSeeInField: 'フィールドに文字が入っていないか確認する',
+    appendField: 'フィールドに文字を追加する',
+    checkOption: 'オプションをチェックする',
+    seeCheckboxIsChecked: 'チェックされているか確認する',
+    dontSeeCheckboxIsChecked: 'チェックされていないことを確認する',
+    grabTextFrom: 'テキストを取得する',
+    grabValueFrom: '入力値を取得する',
+    grabAttributeFrom: '要素を取得する',
+    seeInTitle: 'タイトルに文字が含まれるか確認する',
+    dontSeeInTitle: 'タイトルに文字が含まれないことを確認する',
+    grabTitle: 'タイトルを取得する',
+    seeElement: '要素があるか確認する',
+    dontSeeElement: '要素がないことを確認する',
+    seeInSource: 'ソースにあるか確認する',
+    dontSeeInSource: 'ソースにないことを確認する',
+    executeScript: 'スクリプトを実行する',
+    executeAsyncScript: '非同期スクリプトを実行する',
+    seeInCurrentUrl: 'URLに含まれるか確認する',
+    dontSeeInCurrentUrl: 'URLに含まれないことを確認する',
+    seeCurrentUrlEquals: 'URLが等しいか確認する',
+    dontSeeCurrentUrlEquals: 'URLが等しくないことを確認する',
+    saveScreenshot: 'スクリーンショットを保存する',
+    setCookie: 'Cookieをセットする',
+    clearCookie: 'Cookieをクリアする',
+    seeCookie: 'Cookieに含まれることを確認する',
+    dontSeeCookie: 'Cookieに含まれないことを確認する',
+    grabCookie: 'Cookieを取得する',
+    resizeWindow: 'ウィンドウをリサイズする',
+    wait: '待つ',
+  },
+};


Test patch:
diff --git a/test/unit/locator_test.js b/test/unit/locator_test.js
index b09fdeda..567abe88 100644
--- a/test/unit/locator_test.js
+++ b/test/unit/locator_test.js
@@ -44,6 +44,12 @@ const xml = `<body>
         <label for="remember" class="optional">Remember Me</label>
       </div>
     </div>
+    <div class="form-field">
+      <input name="name0" label="Выберите услугу" type="text" value=""/>
+    </div>
+    <div class="form-field">
+      <input name="name1" label="Выберите услугу" type="text" value=""/>
+    </div>
   </fieldset>
   <label>Hello<a href="#">Please click</a></label>
   </div>
@@ -106,6 +112,15 @@ describe('Locator', () => {
     expect(nodes[0].firstChild.data).to.eql('Please click', l.toXPath());
   });
 
+
+  it('should select child element by name', () => {
+    const l = Locator.build('.form-field')
+      .withDescendant(Locator.build('//input[@name="name1"]'));
+    const nodes = xpath.select(l.toXPath(), doc);
+
+    expect(nodes).to.have.length(1, l.toXPath());
+  });
+
   it('should select element by siblings', () => {
     const l = Locator.build('//table')
       .withChild('td')