text: /*
   * Licensed to Elasticsearch B.V. under one or more contributor
   * license agreements. See the NOTICE file distributed with
   * this work for additional information regarding copyright
   * ownership. Elasticsearch B.V. licenses this file to you under
   * the Apache License, Version 2.0 (the "License"); you may
   * not use this file except in compliance with the License.
   * You may obtain a copy of the License at
   *
   *    http://www.apache.org/licenses/LICENSE-2.0
   *
   * Unless required by applicable law or agreed to in writing,
   * software distributed under the License is distributed on an
   * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
   * KIND, either express or implied.  See the License for the
   * specific language governing permissions and limitations
   * under the License.
   */
  
  import { delay } from 'bluebird';
  import expect from '@kbn/expect';
  
  import getUrl from '../../../src/test_utils/get_url';
  
  export function CommonPageProvider({ getService, getPageObjects }) {
    const log = getService('log');
    const config = getService('config');
    const browser = getService('browser');
    const retry = getService('retry');
    const find = getService('find');
    const globalNav = getService('globalNav');
    const testSubjects = getService('testSubjects');
    const kibanaServer = getService('kibanaServer');
    const PageObjects = getPageObjects(['shield']);
  
    const defaultTryTimeout = config.get('timeouts.try');
    const defaultFindTimeout = config.get('timeouts.find');
  
    class CommonPage {
  
      static async navigateToUrlAndHandleAlert(url, shouldAcceptAlert) {
        log.debug('Navigate to: ' + url);
        try {
          await browser.get(url);
        } catch(navigationError) {
          log.debug('Error navigating to url');
          const alert = await browser.getAlert();
          if (alert && alert.accept) {
            if (shouldAcceptAlert) {
              log.debug('Should accept alert');
              try {
                await alert.accept();
              } catch(alertException) {
                log.debug('Error accepting alert');
                throw alertException;
              }
            } else {
              log.debug('Will not accept alert');
              throw navigationError;
            }
          } else {
            throw navigationError;
          }
        }
      }
  
      getHostPort() {
        return getUrl.baseUrl(config.get('servers.kibana'));
      }
  
      getEsHostPort() {
        return getUrl.baseUrl(config.get('servers.elasticsearch'));
      }
  
      /**
       * @param {string} appName As defined in the apps config
       * @param {string} subUrl The route after the hash (#)
       */
      async navigateToUrl(appName, subUrl, {
        basePath = '',
        ensureCurrentUrl = true,
        shouldLoginIfPrompted = true,
        shouldAcceptAlert = true
      } = {}) {
        // we onlt use the pathname from the appConfig and use the subUrl as the hash
        const appConfig = {
          pathname: `${basePath}${config.get(['apps', appName]).pathname}`,
          hash: `${appName}/${subUrl}`,
        };
  
        const appUrl = getUrl.noAuth(config.get('servers.kibana'), appConfig);
        await retry.try(async () => {
          await CommonPage.navigateToUrlAndHandleAlert(appUrl, shouldAcceptAlert);
          const currentUrl = shouldLoginIfPrompted ? await this.loginIfPrompted(appUrl) : await browser.getCurrentUrl();
  
          if (ensureCurrentUrl && !currentUrl.includes(appUrl)) {
            throw new Error(`expected ${currentUrl}.includes(${appUrl})`);
          }
        });
      }
  
      /**
       * @param {string} appName As defined in the apps config
       * @param {string} hash The route after the hash (#)
       */
      async navigateToActualUrl(appName, hash, {
        basePath = '',
        ensureCurrentUrl = true,
        shouldLoginIfPrompted = true
      } = {}) {
        // we only use the apps config to get the application path
        const appConfig = {
          pathname: `${basePath}${config.get(['apps', appName]).pathname}`,
          hash,
        };
  
        const appUrl = getUrl.noAuth(config.get('servers.kibana'), appConfig);
        await retry.try(async () => {
          log.debug(`navigateToActualUrl ${appUrl}`);
          await browser.get(appUrl);
  
          const currentUrl = shouldLoginIfPrompted ? await this.loginIfPrompted(appUrl) : await browser.getCurrentUrl();
  
          if (ensureCurrentUrl && !currentUrl.includes(appUrl)) {
            throw new Error(`expected ${currentUrl}.includes(${appUrl})`);
          }
        });
      }
  
  
      async loginIfPrompted(appUrl) {
        let currentUrl = await browser.getCurrentUrl();
        log.debug(`currentUrl = ${currentUrl}\n    appUrl = ${appUrl}`);
        await find.byCssSelector('[data-test-subj="kibanaChrome"]', defaultTryTimeout * 2);
        const loginPage = currentUrl.includes('/login');
        const wantedLoginPage = appUrl.includes('/login') || appUrl.includes('/logout');
  
        if (loginPage && !wantedLoginPage) {
          log.debug(`Found login page.  Logging in with username = ${config.get('servers.kibana.username')}`);
          await PageObjects.shield.login(
            config.get('servers.kibana.username'),
            config.get('servers.kibana.password')
          );
          await find.byCssSelector('[data-test-subj="kibanaChrome"] nav:not(.ng-hide)', 20000);
          await browser.get(appUrl);
          currentUrl = await browser.getCurrentUrl();
          log.debug(`Finished login process currentUrl = ${currentUrl}`);
        }
        return currentUrl;
      }
  
      navigateToApp(appName, { basePath = '', shouldLoginIfPrompted = true, shouldAcceptAlert = true, hash = '' } = {}) {
        const self = this;
        const appConfig = config.get(['apps', appName]);
        const appUrl = getUrl.noAuth(config.get('servers.kibana'), {
          pathname: `${basePath}${appConfig.pathname}`,
          hash: hash || appConfig.hash,
        });
        log.debug('navigating to ' + appName + ' url: ' + appUrl);
  
        function navigateTo(url) {
          return retry.try(function () {
            // since we're using hash URLs, always reload first to force re-render
            return kibanaServer.uiSettings.getDefaultIndex()
              .then(async function () {
                return await CommonPage.navigateToUrlAndHandleAlert(url, shouldAcceptAlert);
              })
              .then(function () {
                return self.sleep(700);
              })
              .then(function () {
                log.debug('returned from get, calling refresh');
                return browser.refresh();
              })
              .then(async function () {
                const currentUrl = shouldLoginIfPrompted ? await self.loginIfPrompted(appUrl) : browser.getCurrentUrl();
  
                if (currentUrl.includes('app/kibana')) {
                  await testSubjects.find('kibanaChrome');
                }
              })
              .then(async function () {
  
                const currentUrl = (await browser.getCurrentUrl()).replace(/\/\/\w+:\w+@/, '//');
                const maxAdditionalLengthOnNavUrl = 230;
                // On several test failures at the end of the TileMap test we try to navigate back to
                // Visualize so we can create the next Vertical Bar Chart, but we can see from the
                // logging and the screenshot that it's still on the TileMap page. Why didn't the "get"
                // with a new timestamped URL go? I thought that sleep(700) between the get and the
                // refresh would solve the problem but didn't seem to always work.
                // So this hack fails the navSuccessful check if the currentUrl doesn't match the
                // appUrl plus up to 230 other chars.
                // Navigating to Settings when there is a default index pattern has a URL length of 196
                // (from debug output). Some other tabs may also be long. But a rather simple configured
                // visualization is about 1000 chars long. So at least we catch that case.
  
                // Browsers don't show the ':port' if it's 80 or 443 so we have to
                // remove that part so we can get a match in the tests.
                const navSuccessful = new RegExp(appUrl.replace(':80/', '/').replace(':443/', '/')
                  + '.{0,' + maxAdditionalLengthOnNavUrl + '}$')
                  .test(currentUrl);
  
                if (!navSuccessful) {
                  const msg = 'App failed to load: ' + appName +
                    ' in ' + defaultFindTimeout + 'ms' +
                    ' appUrl = ' + appUrl +
                    ' currentUrl = ' + currentUrl;
                  log.debug(msg);
                  throw new Error(msg);
                }
  
                return currentUrl;
              });
          });
        }
  
        return retry.tryForTime(defaultTryTimeout * 3, () => {
          return navigateTo(appUrl)
            .then(function (currentUrl) {
              let lastUrl = currentUrl;
              return retry.try(function () {
                // give the app time to update the URL
                return self.sleep(501)
                  .then(function () {
                    return browser.getCurrentUrl();
                  })
                  .then(function (currentUrl) {
                    log.debug('in navigateTo url = ' + currentUrl);
                    if (lastUrl !== currentUrl) {
                      lastUrl = currentUrl;
                      throw new Error('URL changed, waiting for it to settle');
                    }
                  });
              });
            })
            .then(async () => {
              if (appName === 'status_page') return;
              if (await testSubjects.exists('statusPageContainer')) {
                throw new Error('Navigation ended up at the status page.');
              }
            });
        });
      }
  
      async sleep(sleepMilliseconds) {
        log.debug('... sleep(' + sleepMilliseconds + ') start');
        await delay(sleepMilliseconds);
        log.debug('... sleep(' + sleepMilliseconds + ') end');
      }
  
      createErrorHandler(testObj) {
        const testName = (testObj.parent) ? [testObj.parent.name, testObj.name].join('_') : testObj.name;
        return error => {
          const now = Date.now();
          const fileName = `failure_${now}_${testName}`;
  
          return this.saveScreenshot(fileName, true)
            .then(function () {
              throw error;
            });
        };
      }
  
      async waitUntilUrlIncludes(path) {
        await retry.try(async () => {
          const url = await browser.getCurrentUrl();
          if (!url.includes(path)) {
            throw new Error('Url not found');
          }
        });
      }
  
      async getSharedItemTitleAndDescription() {
        const cssSelector = '[data-shared-item][data-title][data-description]';
        const element = await find.byCssSelector(cssSelector);
  
        return {
          title: await element.getAttribute('data-title'),
          description: await element.getAttribute('data-description')
        };
      }
  
      /**
       * Makes sure the modal overlay is not showing, tries a few times in case it is in the process of hiding.
       */
      async ensureModalOverlayHidden() {
        return retry.try(async () => {
          const shown = await testSubjects.exists('confirmModalTitleText');
          if (shown) {
            throw new Error('Modal overlay is showing');
          }
        });
      }
  
      async clickConfirmOnModal() {
        log.debug('Clicking modal confirm');
        // make sure this data-test-subj 'confirmModalTitleText' exists because we're going to wait for it to be gone later
        await testSubjects.exists('confirmModalTitleText');
        await testSubjects.click('confirmModalConfirmButton');
        await this.ensureModalOverlayHidden();
      }
  
      async pressEnterKey() {
        await browser.pressKeys(browser.keys.ENTER);
      }
  
      // pass in true if your test will show multiple modals
      // in succession
      async clickCancelOnModal(overlayWillStay = false) {
        log.debug('Clicking modal cancel');
        await testSubjects.click('confirmModalCancelButton');
        if (!overlayWillStay) {
          await this.ensureModalOverlayHidden();
        }
      }
  
      async expectConfirmModalOpenState(state) {
        if (typeof state !== 'boolean') {
          throw new Error('pass true or false to expectConfirmModalOpenState()');
        }
  
        log.debug(`expectConfirmModalOpenState(${state})`);
  
        // we use retry here instead of a simple .exists() check because the modal
        // fades in/out, which takes time, and we really only care that at some point
        // the modal is either open or closed
        await retry.try(async () => {
          const actualState = await testSubjects.exists('confirmModalCancelButton');
          expect(actualState).to.be(state);
        });
      }
  
      async isChromeVisible() {
        const globalNavShown = await globalNav.exists();
        const topNavShown = await testSubjects.exists('top-nav');
        return globalNavShown && topNavShown;
      }
  
      async waitForTopNavToBeVisible() {
        await retry.try(async () => {
          const isNavVisible = await testSubjects.exists('top-nav');
          if (!isNavVisible) {
            throw new Error('Local nav not visible yet');
          }
        });
      }
  
      async closeToast() {
        let toast;
        await retry.try(async () => {
          toast = await find.byCssSelector('.euiToast');
          if (!toast) {
            throw new Error('Toast is not visible yet');
          }
        });
        await browser.moveMouseTo(toast);
        const title = await (await find.byCssSelector('.euiToastHeader__title')).getVisibleText();
        log.debug(title);
        await find.clickByCssSelector('.euiToast__closeButton');
        return title;
      }
  
      async clearAllToasts() {
        const toasts = await find.allByCssSelector('.euiToast');
        for (const toastElement of toasts) {
          try {
            await browser.moveMouseTo(toastElement);
            const closeBtn = await toastElement.findByCssSelector('.euiToast__closeButton');
            await closeBtn.click();
          } catch (err) {
            // ignore errors, toast clear themselves after timeout
          }
        }
      }
  
      async getBodyText() {
        if (await find.existsByCssSelector('a[id=rawdata-tab]', 10000)) {
          // Firefox has 3 tabs and requires navigation to see Raw output
          await find.clickByCssSelector('a[id=rawdata-tab]');
        }
        const msgElements = await find.allByCssSelector('body pre');
        if (msgElements.length > 0) {
          return await msgElements[0].getVisibleText();
        } else {
          // Sometimes Firefox renders Timelion page without tabs and with div#json
          const jsonElement = await find.byCssSelector('body div#json');
          return await jsonElement.getVisibleText();
        }
      }
    }
  
    return new CommonPage();
  }
  