This is an automated email from the ASF dual-hosted git repository.

acote pushed a commit to branch master
in repository https://gitbox.apache.org/repos/asf/couchdb-fauxton.git


The following commit(s) were added to refs/heads/master by this push:
     new d3affac  Fix flaky Nightwatch tests (#1248)
d3affac is described below

commit d3affaca0310a0cb59c5b5e0cf05d7b9720d58d3
Author: Antonio Maranhao <[email protected]>
AuthorDate: Wed Apr 29 14:06:22 2020 -0400

    Fix flaky Nightwatch tests (#1248)
    
    * Fix flaky Nightwatch test
    
    * Update ConfirmModal
    
    * Update CI
    
    Co-authored-by: Alexis Côté <[email protected]>
---
 .../components/components/stringeditmodal.js       | 17 ++++++++--
 app/addons/cors/__tests__/components.test.js       |  6 ++++
 .../__tests__/doc-editor.components.test.js        | 20 +++++++++++
 .../doc-editor/components/DocEditorScreen.js       |  2 +-
 .../documents/tests/nightwatch/deletesDocuments.js |  1 +
 .../documents/tests/nightwatch/viewDelete.js       |  3 +-
 app/addons/fauxton/components.js                   | 23 +++++++------
 .../search/tests/nightwatch/deleteSearchIndex.js   |  1 +
 bin/docker-up-and-check.sh                         |  4 +--
 docker/dc.selenium-debug.yml                       |  2 +-
 docker/dc.selenium.yml                             |  3 +-
 .../custom-commands/clickWhenVisible.js            |  1 -
 test/nightwatch_tests/helpers/helpers.js           | 39 ++++++++++++++++++++--
 test/nightwatch_tests/nightwatch.json.underscore   |  2 ++
 14 files changed, 99 insertions(+), 25 deletions(-)

diff --git a/app/addons/components/components/stringeditmodal.js 
b/app/addons/components/components/stringeditmodal.js
index 0c699be..e0950de 100644
--- a/app/addons/components/components/stringeditmodal.js
+++ b/app/addons/components/components/stringeditmodal.js
@@ -36,6 +36,10 @@ export class StringEditModal extends React.Component {
     onSave () { }
   };
 
+  state = {
+    editorInitialized: false
+  };
+
   initAceEditor = (dom_node) => {
     this.editor = ace.edit(dom_node);
     this.editor.$blockScrolling = Infinity; // suppresses an Ace editor error
@@ -44,6 +48,9 @@ export class StringEditModal extends React.Component {
     this.editor.setTheme('ace/theme/idle_fingers');
     const val = Helpers.parseJSON(this.props.value);
     this.editor.setValue(val, -1);
+    if (!this.state.editorInitialized) {
+      this.setState({ editorInitialized: true });
+    }
   };
 
   closeModal = () => {
@@ -55,6 +62,12 @@ export class StringEditModal extends React.Component {
   };
 
   render() {
+    const { editorInitialized } = this.state;
+    const saveBt = editorInitialized ? (
+      <button id="string-edit-save-btn" onClick={this.save} className="btn 
btn-primary save">
+        <i className="fonticon-circle-check"></i> Modify Text
+      </button>
+    ) : null;
     return (
       <Modal className="string-editor-modal" show={this.props.visible} 
onHide={this.closeModal}>
         <Modal.Header closeButton={true}>
@@ -68,9 +81,7 @@ export class StringEditModal extends React.Component {
         </Modal.Body>
         <Modal.Footer>
           <a className="cancel-link" onClick={this.closeModal}>Cancel</a>
-          <button id="string-edit-save-btn" onClick={this.save} className="btn 
btn-primary save">
-            <i className="fonticon-circle-check"></i> Modify Text
-          </button>
+          { saveBt }
         </Modal.Footer>
       </Modal>
     );
diff --git a/app/addons/cors/__tests__/components.test.js 
b/app/addons/cors/__tests__/components.test.js
index 9c5d935..b26368c 100644
--- a/app/addons/cors/__tests__/components.test.js
+++ b/app/addons/cors/__tests__/components.test.js
@@ -39,6 +39,7 @@ describe('CORS Components', () => {
         saveCORS={sinon.stub()}
         showDeleteDomainConfirmation={sinon.stub()}
         fetchAndLoadCORSOptions={sinon.stub()}
+        hideDeleteDomainConfirmation={sinon.stub()}
       />);
 
       wrapper.find('.enable-disable.btn').simulate('click');
@@ -56,6 +57,7 @@ describe('CORS Components', () => {
         saveCORS={sinon.stub()}
         showDeleteDomainConfirmation={sinon.stub()}
         fetchAndLoadCORSOptions={sinon.stub()}
+        hideDeleteDomainConfirmation={sinon.stub()}
       />);
       wrapper.find('.enable-disable.btn').simulate('click');
       sinon.assert.notCalled(spy);
@@ -72,6 +74,7 @@ describe('CORS Components', () => {
         saveCORS={sinon.stub()}
         showDeleteDomainConfirmation={sinon.stub()}
         fetchAndLoadCORSOptions={sinon.stub()}
+        hideDeleteDomainConfirmation={sinon.stub()}
       />);
       wrapper.find('input').at(0).simulate('change', { target: { checked: 
true, value: 'all' } });
       expect(spy.calledOnce).toBeTruthy();
@@ -88,6 +91,7 @@ describe('CORS Components', () => {
         saveCORS={sinon.stub()}
         showDeleteDomainConfirmation={sinon.stub()}
         fetchAndLoadCORSOptions={sinon.stub()}
+        hideDeleteDomainConfirmation={sinon.stub()}
       />);
       wrapper.find('input').at(0).simulate('change', { target: { checked: 
true, value: 'all' } });
       expect(spy.calledOnce).toBeFalsy();
@@ -102,6 +106,7 @@ describe('CORS Components', () => {
         saveCORS={sinon.stub()}
         showDeleteDomainConfirmation={sinon.stub()}
         fetchAndLoadCORSOptions={sinon.stub()}
+        hideDeleteDomainConfirmation={sinon.stub()}
       />);
 
       expect(wrapper.find('.loading-lines').exists()).toBeTruthy();
@@ -116,6 +121,7 @@ describe('CORS Components', () => {
         saveCORS={sinon.stub()}
         showDeleteDomainConfirmation={sinon.stub()}
         fetchAndLoadCORSOptions={sinon.stub()}
+        hideDeleteDomainConfirmation={sinon.stub()}
       />);
 
       expect(wrapper.find('.loading-lines').exists()).toBe(false);
diff --git 
a/app/addons/documents/doc-editor/__tests__/doc-editor.components.test.js 
b/app/addons/documents/doc-editor/__tests__/doc-editor.components.test.js
index 313bc64..5602ebb 100644
--- a/app/addons/documents/doc-editor/__tests__/doc-editor.components.test.js
+++ b/app/addons/documents/doc-editor/__tests__/doc-editor.components.test.js
@@ -25,6 +25,7 @@ import { mount } from 'enzyme';
 import thunk from 'redux-thunk';
 import { Provider } from 'react-redux';
 import { createStore, applyMiddleware, combineReducers } from 'redux';
+import actiontypes from '../actiontypes';
 import docEditorReducer from '../reducers';
 
 import '../../base';
@@ -215,8 +216,25 @@ describe('DocEditorContainer', () => {
     combineReducers({ docEditor: docEditorReducer, databases: databasesReducer 
}),
     applyMiddleware(...middlewares)
   );
+  const loadDummyDocAction = {
+    type: actiontypes.DOC_LOADED,
+    options: {
+      doc: {
+        get: () => {},
+        unset: () => {},
+        hasChanged: () => false,
+      }
+    }
+  };
+
+  afterEach(() => {
+    store.dispatch({
+      type: actiontypes.RESET_DOC
+    });
+  });
 
   it('clicking Delete button shows the confirmation modal', () => {
+    store.dispatch(loadDummyDocAction);
     const wrapper = mount(
       <Provider store={store}>
         <DocEditorContainer
@@ -230,6 +248,7 @@ describe('DocEditorContainer', () => {
   });
 
   it('clicking Upload button shows the upload dialog', () => {
+    store.dispatch(loadDummyDocAction);
     const wrapper = mount(
       <Provider store={store}>
         <DocEditorContainer
@@ -243,6 +262,7 @@ describe('DocEditorContainer', () => {
   });
 
   it('clicking Clone button shows the clone doc dialog', () => {
+    store.dispatch(loadDummyDocAction);
     const wrapper = mount(
       <Provider store={store}>
         <DocEditorContainer
diff --git a/app/addons/documents/doc-editor/components/DocEditorScreen.js 
b/app/addons/documents/doc-editor/components/DocEditorScreen.js
index 8edd153..f8f661f 100644
--- a/app/addons/documents/doc-editor/components/DocEditorScreen.js
+++ b/app/addons/documents/doc-editor/components/DocEditorScreen.js
@@ -144,7 +144,7 @@ export default class DocEditorScreen extends 
React.Component {
   };
 
   getButtonRow = () => {
-    if (this.props.isNewDoc) {
+    if (this.props.isNewDoc || this.props.doc === null) {
       return false;
     }
     return (
diff --git a/app/addons/documents/tests/nightwatch/deletesDocuments.js 
b/app/addons/documents/tests/nightwatch/deletesDocuments.js
index ec34cba..6d0372b 100644
--- a/app/addons/documents/tests/nightwatch/deletesDocuments.js
+++ b/app/addons/documents/tests/nightwatch/deletesDocuments.js
@@ -151,6 +151,7 @@ module.exports = {
       .clickWhenVisible('#doc-editor-actions-panel button[title="Delete"]')
       .waitForElementVisible('.confirmation-modal', waitTime, false)
       .clickWhenVisible('.confirmation-modal button.btn.btn-primary')
+      .waitForElementNotPresent('.confirmation-modal button.btn-primary', 
waitTime, true)
       .waitForElementPresent('.jump-to-doc', waitTime, false)
 
       //check raw JSON
diff --git a/app/addons/documents/tests/nightwatch/viewDelete.js 
b/app/addons/documents/tests/nightwatch/viewDelete.js
index 335e38e..dcc148f 100644
--- a/app/addons/documents/tests/nightwatch/viewDelete.js
+++ b/app/addons/documents/tests/nightwatch/viewDelete.js
@@ -33,9 +33,8 @@ module.exports = {
 
       .clickWhenVisible('.index-list .active span', waitTime, true)
       .clickWhenVisible('.popover-content .fonticon-trash', waitTime, true)
-      .waitForElementVisible('.confirmation-modal button.btn-primary', 
waitTime, true)
       .clickWhenVisible('.confirmation-modal button.btn-primary', waitTime, 
true)
-
+      .waitForElementNotPresent('.confirmation-modal button.btn-primary', 
waitTime, true)
 
       // now wait for the sidebar to have removed the design doc
       .waitForElementNotPresent('#testdesigndoc', waitTime, true)
diff --git a/app/addons/fauxton/components.js b/app/addons/fauxton/components.js
index e33cf73..025d845 100644
--- a/app/addons/fauxton/components.js
+++ b/app/addons/fauxton/components.js
@@ -164,9 +164,7 @@ class ConfirmationModal extends React.Component {
     title: 'Please confirm',
     text: '',
     successButtonLabel: 'Okay',
-    buttonClass: 'btn-primary',
-    onClose: function () { },
-    onSubmit: function () { }
+    buttonClass: 'btn-primary'
   };
 
   close = (e) => {
@@ -177,12 +175,19 @@ class ConfirmationModal extends React.Component {
   };
 
   render() {
-    var content = <p>{this.props.text}</p>;
+    let content = <p>{this.props.text}</p>;
     if (!_.isString(this.props.text)) {
       content = this.props.text;
     }
-    var btnClasses = 'btn ' + this.props.buttonClass;
-
+    const btnClasses = 'btn ' + this.props.buttonClass;
+    const closeButton = this.props.onClose ? (
+      <a href="#" data-bypass="true" className="cancel-link" 
onClick={this.close}>Cancel</a>
+    ) : null;
+    const submitButton = this.props.onSubmit ? (
+      <button className={btnClasses} onClick={this.props.onSubmit}>
+        <i className="fonticon-ok-circled"></i> {this.props.successButtonLabel}
+      </button>
+    ) : null;
     return (
       <Modal dialogClassName="confirmation-modal" show={this.props.visible} 
onHide={this.close}>
         <Modal.Header closeButton={true}>
@@ -192,10 +197,8 @@ class ConfirmationModal extends React.Component {
           {content}
         </Modal.Body>
         <Modal.Footer>
-          <a href="#" data-bypass="true" className="cancel-link" 
onClick={this.close}>Cancel</a>
-          <button className={btnClasses} onClick={this.props.onSubmit}>
-            <i className="fonticon-ok-circled"></i> 
{this.props.successButtonLabel}
-          </button>
+          { closeButton }
+          { submitButton }
         </Modal.Footer>
       </Modal>
     );
diff --git a/app/addons/search/tests/nightwatch/deleteSearchIndex.js 
b/app/addons/search/tests/nightwatch/deleteSearchIndex.js
index 3ec44fa..b8e4a03 100644
--- a/app/addons/search/tests/nightwatch/deleteSearchIndex.js
+++ b/app/addons/search/tests/nightwatch/deleteSearchIndex.js
@@ -74,6 +74,7 @@ module.exports = {
       .clickWhenVisible('.popover-content .fonticon-trash', waitTime, true)
       .waitForElementVisible('div.confirmation-modal', waitTime, false)
       .clickWhenVisible('.confirmation-modal button.btn.btn-primary')
+      .waitForElementNotPresent('.confirmation-modal button.btn.btn-primary', 
waitTime, true)
 
       // just assert the search indexes section has been removed, but the 
design doc still exists
       .waitForElementNotPresent('#nav-design-function-testdesigndocindexes', 
waitTime, true)
diff --git a/bin/docker-up-and-check.sh b/bin/docker-up-and-check.sh
index 7d69859..27f99c7 100755
--- a/bin/docker-up-and-check.sh
+++ b/bin/docker-up-and-check.sh
@@ -36,7 +36,7 @@ start_containters_and_check() {
       echo "=============================="
       return 1
     fi
-    if [[ $logs == *"Developers cluster is set up"* ]]; then
+    if [[ $logs == *"Apache CouchDB has started on"* ]]; then
       echo "Docker containers are up"
       echo "========================"
       docker logs couchdb
@@ -59,4 +59,4 @@ then
 fi
 
 stop_containters
-exit 2
\ No newline at end of file
+exit 2
diff --git a/docker/dc.selenium-debug.yml b/docker/dc.selenium-debug.yml
index b819f25..45eac62 100644
--- a/docker/dc.selenium-debug.yml
+++ b/docker/dc.selenium-debug.yml
@@ -5,7 +5,7 @@ services:
       file: './dc.selenium.yml'
       service: selenium
     container_name: selenium
-    image: selenium/standalone-chrome-debug:3.11.0
+    image: selenium/standalone-chrome-debug:3.13
     ports:
       - "4444:4444"
       - "5900:5900"
diff --git a/docker/dc.selenium.yml b/docker/dc.selenium.yml
index 9ff1e63..6c2f033 100644
--- a/docker/dc.selenium.yml
+++ b/docker/dc.selenium.yml
@@ -2,10 +2,9 @@ version: '2'
 services:
   selenium:
     container_name: selenium
-    image: selenium/standalone-chrome:3.11.0
+    image: selenium/standalone-chrome:3.13
     ports:
       - "4444:4444"
-
   couchdb:
     container_name: couchdb
     image: ${COUCHDB_IMAGE}
diff --git a/test/nightwatch_tests/custom-commands/clickWhenVisible.js 
b/test/nightwatch_tests/custom-commands/clickWhenVisible.js
index 2964e76..052169a 100644
--- a/test/nightwatch_tests/custom-commands/clickWhenVisible.js
+++ b/test/nightwatch_tests/custom-commands/clickWhenVisible.js
@@ -20,7 +20,6 @@ exports.command = function (element, waitTime) {
 
   this
     .waitForElementPresent(element, waitTime, false)
-    .execute('document.querySelector("' + element + '").scrollIntoView();')
     .waitForElementVisible(element, waitTime, false)
     .click(element);
 
diff --git a/test/nightwatch_tests/helpers/helpers.js 
b/test/nightwatch_tests/helpers/helpers.js
index 78e91ae..f2391ef 100644
--- a/test/nightwatch_tests/helpers/helpers.js
+++ b/test/nightwatch_tests/helpers/helpers.js
@@ -69,15 +69,48 @@ module.exports = {
   },
 
   afterEach: function (browser, done) {
+    // Delete test database
     var nano = 
module.exports.getNanoInstance(browser.globals.test_settings.db_url),
       database = module.exports.testDatabaseName;
 
     console.log('nano cleaning up', database);
-    nano.db.destroy(database).catch(err => {
+    var destroyDbProm = nano.db.destroy(database).catch(err => {
       if (err && err.message !== 'Database does not exist.') {
-        console.log('Error in cleaning up ' + database, err.message);
+        console.warn('Error in cleaning up ' + database, err.message);
       }
-    }).then(() => {
+    });
+
+    // Prints the browser's console logs in case it's a failure
+    var promGetLog = Promise.resolve();
+    if (browser && browser.sessionId && browser.currentTest && 
browser.currentTest.results) {
+      var res = browser.currentTest.results;
+      if (res.errors > 0 || res.failed > 0) {
+        promGetLog = new Promise((resolve, reject) => {
+          try {
+            browser.getLog('browser', (logEntriesArray) => {
+              // !! IMPORTANT: Ends the session since the Nightwatch settings 
have "end_session_on_fail: false"
+              try {
+                browser.end();
+              } catch (e) {}
+              resolve(logEntriesArray);
+            });
+          } catch (err) {
+            reject(err);
+          }
+        }).catch(err => {
+          console.warn('Failed to fetch browser logs', err);
+        }).then(logEntriesArray => {
+          if (logEntriesArray) {
+            console.warn('Browser logs for failed test:');
+            logEntriesArray.forEach(function(log) {
+              console.warn('   [' + log.level + '] ' + ' : ' + log.message);
+            });
+          }
+        });
+      }
+    }
+
+    Promise.all([promGetLog, destroyDbProm]).then(() => {
       done();
     });
   }
diff --git a/test/nightwatch_tests/nightwatch.json.underscore 
b/test/nightwatch_tests/nightwatch.json.underscore
index 68cf887..524e0e7 100644
--- a/test/nightwatch_tests/nightwatch.json.underscore
+++ b/test/nightwatch_tests/nightwatch.json.underscore
@@ -26,6 +26,8 @@
       "password": "<%- password %>",
       "launch_url": "http://<%- fauxton_host %>:<%- fauxton_port %>",
       "db_url": "<%- db_protocol %>://<%- fauxton_username %>:<%- password 
%>@<%- db_host %>:<%- db_port %>",
+      "end_session_on_fail": false,
+      "skip_testcases_on_fail": true,
       "selenium_host" : "127.0.0.1",
       "selenium_port" : "<%- selenium_port %>",
       "silent" : true,

Reply via email to