"use strict";

Object.defineProperty(exports, "__esModule", {
  value: true
});
exports.closeBrowser = closeBrowser;
exports.getBrowser = getBrowser;
exports.loadStoriesFromBrowser = loadStoriesFromBrowser;
exports.switchStory = switchStory;
exports.takeScreenshot = takeScreenshot;
exports.updateStorybookGlobals = updateStorybookGlobals;
var _coreEvents = require("@storybook/core-events");
var _chalk = _interopRequireDefault(require("chalk"));
var _http = _interopRequireDefault(require("http"));
var _https = _interopRequireDefault(require("https"));
var _loglevel = require("loglevel");
var _loglevelPluginPrefix = _interopRequireDefault(require("loglevel-plugin-prefix"));
var _os = require("os");
var _pngjs = require("pngjs");
var _seleniumWebdriver = require("selenium-webdriver");
var _capabilities = require("selenium-webdriver/lib/capabilities");
var _types = require("../../types");
var _logger = require("../logger");
var _messages = require("../messages");
var _utils = require("../utils");
function _interopRequireDefault(e) { return e && e.__esModule ? e : { default: e }; }
// import { Options as IeOptions } from 'selenium-webdriver/ie';
// import { Options as EdgeOptions } from 'selenium-webdriver/edge';
// import { Options as ChromeOptions } from 'selenium-webdriver/chrome';
// import { Options as SafariOptions } from 'selenium-webdriver/safari';
// import { Options as FirefoxOptions } from 'selenium-webdriver/firefox';

// type UnPromise<P> = P extends Promise<infer T> ? T : never;

const storybookRootID = 'storybook-root';
const DOCKER_INTERNAL = 'host.docker.internal';
let browserLogger = _logger.logger;
let browserName = '';
let browser = null;
// let context: UnPromise<ReturnType<typeof BrowsingContext>> | null = null;
let creeveyServerHost = null;
function getSessionData(grid, sessionId = '') {
  const gridUrl = new URL(grid);
  gridUrl.pathname = `/host/${sessionId}`;
  return new Promise((resolve, reject) => (gridUrl.protocol == 'https:' ? _https.default : _http.default).get(gridUrl.toString(), res => {
    if (res.statusCode !== 200) {
      return reject(new Error(`Couldn't get session data for ${sessionId}. Status code: ${res.statusCode ?? 'Unknown'}`));
    }
    let data = '';
    res.setEncoding('utf8');
    res.on('data', chunk => data += chunk);
    res.on('end', () => {
      try {
        resolve(JSON.parse(data));
      } catch (error) {
        reject(new Error(`Couldn't get session data for ${sessionId}. ${error instanceof Error ? error.stack ?? error.message : error}`));
      }
    });
  }));
}
function getAddresses() {
  // TODO Check if docker is used
  return [DOCKER_INTERNAL].concat(...Object.values((0, _os.networkInterfaces)()).filter(_types.isDefined).map(network => network.filter(info => info.family == 'IPv4').map(info => info.address)));
}
async function resolveStorybookUrl(storybookUrl, checkUrl) {
  browserLogger.debug('Resolving storybook url');
  const addresses = getAddresses();
  for (const ip of addresses) {
    const resolvedUrl = storybookUrl.replace(_utils.LOCALHOST_REGEXP, ip);
    browserLogger.debug(`Checking storybook availability on ${_chalk.default.magenta(resolvedUrl)}`);
    if (await checkUrl(resolvedUrl)) {
      browserLogger.debug(`Resolved storybook url ${_chalk.default.magenta(resolvedUrl)}`);
      return resolvedUrl;
    }
  }
  const error = new Error('Please specify `storybookUrl` with IP address that accessible from remote browser');
  error.name = 'ResolveUrlError';
  throw error;
}
async function openUrlAndWaitForPageSource(browser, url, predicate) {
  let source = '';
  await browser.get(url);
  do {
    try {
      source = await browser.getPageSource();
    } catch (_) {
      // NOTE: Firefox can raise exception "curContainer.frame.document.documentElement is null"
    }
  } while (predicate(source));
  return source;
}
function getUrlChecker(browser) {
  return async url => {
    try {
      // NOTE: Before trying a new url, reset the current one
      browserLogger.debug(`Opening ${_chalk.default.magenta('about:blank')} page`);
      await openUrlAndWaitForPageSource(browser, 'about:blank', source => !source.includes('<body></body>'));
      browserLogger.debug(`Opening ${_chalk.default.magenta(url)} and checking the page source`);
      const source = await openUrlAndWaitForPageSource(browser, url,
      // NOTE: IE11 can return only `head` without body
      source => source.length == 0 || !/<body([^>]*>).+<\/body>/s.test(source));
      // NOTE: This is the most optimal way to check if we in storybook or not
      // We don't use any page load strategies except `NONE`
      // because other add significant delay and some of them don't work in earlier chrome versions
      // Browsers always load page successful even it's failed
      // So we just check `root` element
      browserLogger.debug(`Checking ${_chalk.default.cyan(`#${storybookRootID}`)} existence on ${_chalk.default.magenta(url)}`);
      return source.includes(`id="${storybookRootID}"`);
    } catch (error) {
      return false;
    }
  };
}
async function waitForStorybook(browser) {
  browserLogger.debug('Waiting for `setStories` event to make sure that storybook is initiated');
  let wait = true;
  let isTimeout = false;
  const initiateTimeout = setTimeout(() => {
    wait = false;
    isTimeout = true;
  }, 60000);
  while (wait !== false) {
    try {
      wait = await browser.executeScript(function (SET_GLOBALS) {
        if (typeof window.__STORYBOOK_ADDONS_CHANNEL__ == 'undefined') return true;
        if (window.__STORYBOOK_ADDONS_CHANNEL__.last(SET_GLOBALS) == undefined) return true;
        return false;
      }, _coreEvents.SET_GLOBALS);
    } catch (e) {
      browserLogger.debug('An error has been caught during the script: ', e);
    }
    if (!wait) clearTimeout(initiateTimeout);
  }
  if (isTimeout) throw new Error('Failed to wait `setStories` event');
}
async function resetMousePosition(browser) {
  var _await$browser$getCap, _await$browser$getCap2;
  browserLogger.debug('Resetting mouse position to the top-left corner');
  const browserName = (await browser.getCapabilities()).getBrowserName();
  const [browserVersion] = ((_await$browser$getCap = (await browser.getCapabilities()).getBrowserVersion()) === null || _await$browser$getCap === void 0 ? void 0 : _await$browser$getCap.split('.')) ?? ((_await$browser$getCap2 = (await browser.getCapabilities()).get('version')) === null || _await$browser$getCap2 === void 0 ? void 0 : _await$browser$getCap2.split('.')) ?? [];

  // NOTE Reset mouse position to support keweb selenium grid browser versions
  if (browserName == 'chrome' && browserVersion == '70') {
    const {
      top,
      left,
      width,
      height
    } = await browser.executeScript(function () {
      const bodyRect = document.body.getBoundingClientRect();
      return {
        top: bodyRect.top,
        left: bodyRect.left,
        width: bodyRect.width,
        height: bodyRect.height
      };
    });
    // NOTE Bridge mode doesn't support `Origin.VIEWPORT`, move mouse relative
    await browser.actions({
      bridge: true
    }).move({
      origin: browser.findElement(_seleniumWebdriver.By.css('body')),
      x: Math.ceil(-1 * width / 2) - left,
      y: Math.ceil(-1 * height / 2) - top
    }).perform();
  } else if (browserName == 'firefox' && browserVersion == '61') {
    // NOTE Firefox for some reason moving by 0 x 0 move cursor in bottom left corner :sad:
    await browser.actions().move({
      origin: _seleniumWebdriver.Origin.VIEWPORT,
      x: 0,
      y: 1
    }).perform();
  } else {
    // NOTE IE don't emit move events until force window focus or connect by RDP on virtual machine
    await browser.actions().move({
      origin: _seleniumWebdriver.Origin.VIEWPORT,
      x: 0,
      y: 0
    }).perform();
  }
}
async function resizeViewport(browser, viewport) {
  const windowRect = await browser.manage().window().getRect();
  const {
    innerWidth,
    innerHeight
  } = await browser.executeScript(function () {
    return {
      innerWidth: window.innerWidth,
      innerHeight: window.innerHeight
    };
  });
  browserLogger.debug(`Resizing viewport from ${innerWidth}x${innerHeight} to ${viewport.width}x${viewport.height}`);
  const dWidth = windowRect.width - innerWidth;
  const dHeight = windowRect.height - innerHeight;
  await browser.manage().window().setRect({
    width: viewport.width + dWidth,
    height: viewport.height + dHeight
  });
}
const getScrollBarWidth = (() => {
  let scrollBarWidth = null;
  return async browser => {
    if (scrollBarWidth != null) return Promise.resolve(scrollBarWidth);
    scrollBarWidth = await browser.executeScript(function () {
      // eslint-disable-next-line no-var
      var div = document.createElement('div');
      div.innerHTML = 'a'; // NOTE: In IE clientWidth is 0 if this div is empty.
      div.style.overflowY = 'scroll';
      document.body.appendChild(div);
      // eslint-disable-next-line no-var
      var widthDiff = div.offsetWidth - div.clientWidth;
      document.body.removeChild(div);
      return widthDiff;
    });
    return scrollBarWidth;
  };
})();

// NOTE Firefox and Safari take viewport screenshot without scrollbars
async function hasScrollBar(browser) {
  var _await$browser$getCap3;
  const browserName = (await browser.getCapabilities()).getBrowserName();
  const [browserVersion] = ((_await$browser$getCap3 = (await browser.getCapabilities()).getBrowserVersion()) === null || _await$browser$getCap3 === void 0 ? void 0 : _await$browser$getCap3.split('.')) ?? [];
  return browserName != 'Safari' &&
  // NOTE This need to work with keweb selenium grid
  !(browserName == 'firefox' && browserVersion == '61');
}
async function takeCompositeScreenshot(browser, windowRect, elementRect) {
  const screens = [];
  const isScreenshotWithoutScrollBar = !(await hasScrollBar(browser));
  const scrollBarWidth = await getScrollBarWidth(browser);
  // NOTE Sometimes viewport has been scrolled somewhere
  const normalizedElementRect = {
    left: elementRect.left - windowRect.left,
    right: elementRect.left + elementRect.width - windowRect.left,
    top: elementRect.top - windowRect.top,
    bottom: elementRect.top + elementRect.height - windowRect.top
  };
  const isFitHorizontally = windowRect.width >= elementRect.width + normalizedElementRect.left;
  const isFitVertically = windowRect.height >= elementRect.height + normalizedElementRect.top;
  const viewportWidth = windowRect.width - (isFitVertically ? 0 : scrollBarWidth);
  const viewportHeight = windowRect.height - (isFitHorizontally ? 0 : scrollBarWidth);
  const cols = Math.ceil(elementRect.width / viewportWidth);
  const rows = Math.ceil(elementRect.height / viewportHeight);
  const xOffset = Math.round(isFitHorizontally ? normalizedElementRect.left : Math.max(0, cols * viewportWidth - elementRect.width));
  const yOffset = Math.round(isFitVertically ? normalizedElementRect.top : Math.max(0, rows * viewportHeight - elementRect.height));
  for (let row = 0; row < rows; row += 1) {
    for (let col = 0; col < cols; col += 1) {
      const dx = Math.min(viewportWidth * col + normalizedElementRect.left, Math.max(0, normalizedElementRect.right - viewportWidth));
      const dy = Math.min(viewportHeight * row + normalizedElementRect.top, Math.max(0, normalizedElementRect.bottom - viewportHeight));
      await browser.executeScript(function (x, y) {
        window.scrollTo(x, y);
      }, dx, dy);
      screens.push(await browser.takeScreenshot());
    }
  }
  const images = screens.map(s => Buffer.from(s, 'base64')).map(b => _pngjs.PNG.sync.read(b));
  const compositeImage = new _pngjs.PNG({
    width: Math.round(elementRect.width),
    height: Math.round(elementRect.height)
  });
  for (let y = 0; y < compositeImage.height; y += 1) {
    for (let x = 0; x < compositeImage.width; x += 1) {
      const col = Math.floor(x / viewportWidth);
      const row = Math.floor(y / viewportHeight);
      const isLastCol = cols - col == 1;
      const isLastRow = rows - row == 1;
      const scrollOffset = isFitVertically || isScreenshotWithoutScrollBar ? 0 : scrollBarWidth;
      const i = (y * compositeImage.width + x) * 4;
      const j =
      // NOTE compositeImage(x, y) => image(x, y)
      (y % viewportHeight * (viewportWidth + scrollOffset) + x % viewportWidth) * 4 + (
      // NOTE Offset for last row/col image
      isLastRow ? yOffset * (viewportWidth + scrollOffset) * 4 : 0) + (isLastCol ? xOffset * 4 : 0);
      const image = images[row * cols + col];
      compositeImage.data[i + 0] = image.data[j + 0];
      compositeImage.data[i + 1] = image.data[j + 1];
      compositeImage.data[i + 2] = image.data[j + 2];
      compositeImage.data[i + 3] = image.data[j + 3];
    }
  }
  return _pngjs.PNG.sync.write(compositeImage).toString('base64');
}
async function takeScreenshot(browser, captureElement, ignoreElements) {
  let screenshot;
  const ignoreStyles = await insertIgnoreStyles(browser, ignoreElements);
  try {
    if (!captureElement) {
      if (browserLogger.getLevel() <= _loglevel.levels.DEBUG) {
        const {
          innerWidth,
          innerHeight
        } = await browser.executeScript(function () {
          return {
            innerWidth: window.innerWidth,
            innerHeight: window.innerHeight
          };
        });
        browserLogger.debug(`Viewport size is: ${innerWidth}x${innerHeight}`);
      }
      browserLogger.debug('Capturing viewport screenshot');
      screenshot = await browser.takeScreenshot();
      browserLogger.debug('Viewport screenshot is captured');
    } else {
      browserLogger.debug(`Checking is element ${_chalk.default.cyan(captureElement)} fit into viewport`);
      const rects = await browser.executeScript(function (selector) {
        window.scrollTo(0, 0); // TODO Maybe we should remove same code from `resetMousePosition`
        // eslint-disable-next-line no-var
        var element = document.querySelector(selector);
        if (!element) return;

        // eslint-disable-next-line no-var
        var elementRect = element.getBoundingClientRect();
        return {
          elementRect: {
            top: elementRect.top,
            left: elementRect.left,
            width: elementRect.width,
            height: elementRect.height
          },
          windowRect: {
            top: Math.round(window.scrollY || window.pageYOffset),
            left: Math.round(window.scrollX || window.pageXOffset),
            width: window.innerWidth,
            height: window.innerHeight
          }
        };
      }, captureElement);
      const {
        elementRect,
        windowRect
      } = rects ?? {};
      if (!elementRect || !windowRect) throw new Error(`Couldn't find element with selector: '${captureElement}'`);
      const isFitIntoViewport = elementRect.width + elementRect.left <= windowRect.width && elementRect.height + elementRect.top <= windowRect.height;
      if (isFitIntoViewport) browserLogger.debug(`Capturing ${_chalk.default.cyan(captureElement)}`);else browserLogger.debug(`Capturing composite screenshot image of ${_chalk.default.cyan(captureElement)}`);

      // const element = await browser.findElement(By.css(captureElement));
      // screenshot = isFitIntoViewport
      //   ? context
      //     ? await context.captureElementScreenshot(await element.getId())
      //     : await browser.findElement(By.css(captureElement)).takeScreenshot()
      //   : // TODO pointer-events: none, need to research
      //     await takeCompositeScreenshot(browser, windowRect, elementRect);
      screenshot = isFitIntoViewport ? await browser.findElement(_seleniumWebdriver.By.css(captureElement)).takeScreenshot() :
      // TODO pointer-events: none, need to research
      await takeCompositeScreenshot(browser, windowRect, elementRect);
      browserLogger.debug(`${_chalk.default.cyan(captureElement)} is captured`);
    }
  } finally {
    await removeIgnoreStyles(browser, ignoreStyles);
  }
  return screenshot;
}
async function selectStory(browser, {
  id,
  kind,
  name
}, waitForReady = false) {
  browserLogger.debug(`Triggering 'SetCurrentStory' event with storyId ${_chalk.default.magenta(id)}`);
  const result = await browser.executeAsyncScript(function (id, kind, name, shouldWaitForReady, callback) {
    if (typeof window.__CREEVEY_SELECT_STORY__ == 'undefined') {
      return callback(["Creevey can't switch story. This may happened if forget to add `creevey` addon to your storybook config, or storybook not loaded in browser due syntax error."]);
    }
    void window.__CREEVEY_SELECT_STORY__(id, kind, name, shouldWaitForReady, callback);
  }, id, kind, name, waitForReady);
  const [errorMessage, isCaptureCalled = false] = result ?? [];
  if (errorMessage) throw new Error(errorMessage);
  return isCaptureCalled;
}
async function updateStorybookGlobals(browser, globals) {
  browserLogger.debug('Applying storybook globals');
  await browser.executeScript(function (globals) {
    window.__CREEVEY_UPDATE_GLOBALS__(globals);
  }, globals);
}
function appendIframePath(url) {
  return `${url.replace(/\/$/, '')}/iframe.html`;
}
async function openStorybookPage(browser, storybookUrl, resolver) {
  if (!_utils.LOCALHOST_REGEXP.test(storybookUrl)) {
    return browser === null || browser === void 0 ? void 0 : browser.get(appendIframePath(storybookUrl));
  }
  try {
    if (resolver) {
      browserLogger.debug('Resolving storybook url with custom resolver');
      const resolvedUrl = await resolver();
      browserLogger.debug(`Resolver storybook url ${resolvedUrl}`);
      return browser.get(appendIframePath(resolvedUrl));
    }
    // NOTE: getUrlChecker already calls `browser.get` so we don't need another one
    return void (await resolveStorybookUrl(appendIframePath(storybookUrl), getUrlChecker(browser)));
  } catch (error) {
    browserLogger.error('Failed to resolve storybook URL', error instanceof Error ? error.message : '');
    throw error;
  }
}
async function resolveCreeveyHost(browser, port) {
  if (creeveyServerHost != null) return creeveyServerHost;
  const addresses = getAddresses();
  creeveyServerHost = await browser.executeAsyncScript(function (hosts, port, callback) {
    void Promise.all(hosts.map(function (host) {
      return Promise.race([
      // eslint-disable-next-line @typescript-eslint/restrict-plus-operands
      fetch('http://' + host + ':' + port + '/ping').then(function (response) {
        return response.text();
      }), new Promise((_resolve, reject) => {
        setTimeout(reject, 5000);
      })]).then(function (pong) {
        return pong == 'pong' ? host : null;
      }).catch(function () {
        return null;
      });
    })).then(function (hosts) {
      callback(hosts.find(function (host) {
        return host != null;
      }));
    });
  }, addresses, port);
  if (creeveyServerHost == null) throw new Error("Can't reach creevey server from a browser");
  return creeveyServerHost;
}
async function loadStoriesFromBrowser() {
  if (!browser) throw new Error("Can't get stories from browser if webdriver isn't connected");
  const stories = await browser.executeAsyncScript(function (callback) {
    void window.__CREEVEY_GET_STORIES__().then(callback);
  });
  if (!stories) throw new Error("Can't get stories, it seems creevey or storybook API isn't available");
  return stories;
}
async function getBrowser(config, options) {
  if (browser) return browser;
  browserName = options.browser;
  const browserConfig = config.browsers[browserName];
  const {
    gridUrl = config.gridUrl,
    storybookUrl: address = config.storybookUrl,
    limit,
    viewport,
    _storybookGlobals,
    ...userCapabilities
  } = browserConfig;
  void limit;
  const realAddress = address;

  // TODO Define some capabilities explicitly and define typings
  const capabilities = new _seleniumWebdriver.Capabilities({
    ...userCapabilities,
    pageLoadStrategy: _capabilities.PageLoadStrategy.EAGER
  });
  (0, _messages.subscribeOn)('shutdown', () => {
    var _browser;
    (_browser = browser) === null || _browser === void 0 || _browser.quit().finally(() =>
    // eslint-disable-next-line no-process-exit
    process.exit());
    browser = null;
  });
  try {
    var _await$browser$getSes;
    const url = new URL(gridUrl);
    url.username = url.username ? '********' : '';
    url.password = url.password ? '********' : '';
    browserLogger.debug(`(${browserName}) Connecting to Selenium ${_chalk.default.magenta(url.toString())}`);
    const prefs = new _seleniumWebdriver.logging.Preferences();
    if (options.trace) {
      for (const type of Object.values(_seleniumWebdriver.logging.Type)) {
        prefs.setLevel(type, _seleniumWebdriver.logging.Level.ALL);
      }
    }

    // const ie = new IeOptions();
    // const edge = new EdgeOptions();
    // const chrome = new ChromeOptions();
    // const safari = new SafariOptions();
    // const firefox = new FirefoxOptions();
    // edge.enableBidi();
    // chrome.enableBidi();
    // firefox.enableBidi();

    browser = await new _seleniumWebdriver.Builder()
    // .setIeOptions(ie)
    // .setEdgeOptions(edge)
    // .setChromeOptions(chrome)
    // .setSafariOptions(safari)
    // .setFirefoxOptions(firefox)
    .usingServer(gridUrl).withCapabilities(capabilities).setLoggingPrefs(prefs) // NOTE: Should go last
    .build();

    // const id = await browser.getWindowHandle();
    // context = await BrowsingContext(browser, { browsingContextId: id });

    const sessionId = (_await$browser$getSes = await browser.getSession()) === null || _await$browser$getSes === void 0 ? void 0 : _await$browser$getSes.getId();
    let browserHost = '';
    try {
      const {
        Name
      } = await getSessionData(gridUrl, sessionId);
      if (typeof Name == 'string') browserHost = Name;
    } catch (_) {
      /* noop */
    }
    browserLogger.debug(`(${browserName}) Connected successful with ${[_chalk.default.green(browserHost), _chalk.default.magenta(sessionId)].filter(Boolean).join(':')}`);
    browserLogger = (0, _loglevel.getLogger)(sessionId);
    _loglevelPluginPrefix.default.apply(browserLogger, {
      format(level) {
        const levelColor = _logger.colors[level.toUpperCase()];
        return `[${browserName}:${_chalk.default.gray(sessionId)}] ${levelColor(level)} =>`;
      }
    });
    await (0, _utils.runSequence)([() => {
      var _browser2;
      return (_browser2 = browser) === null || _browser2 === void 0 ? void 0 : _browser2.manage().setTimeouts({
        pageLoad: 10000,
        script: 60000
      });
    }, () => viewport && browser && resizeViewport(browser, viewport), () => browser && openStorybookPage(browser, realAddress, config.resolveStorybookUrl), () => browser && waitForStorybook(browser)], () => !_utils.isShuttingDown.current);
  } catch (originalError) {
    var _browser4;
    if (_utils.isShuttingDown.current) {
      var _browser3;
      (_browser3 = browser) === null || _browser3 === void 0 || _browser3.quit().catch(_types.noop);
      browser = null;
      return null;
    }
    if (originalError instanceof Error && originalError.name == 'ResolveUrlError') throw originalError;
    const error = new Error(`Can't load storybook root page by URL ${(await ((_browser4 = browser) === null || _browser4 === void 0 ? void 0 : _browser4.getCurrentUrl())) ?? realAddress}`);
    if (originalError instanceof Error) error.stack = originalError.stack;
    throw error;
  }
  if (_storybookGlobals) {
    await updateStorybookGlobals(browser, _storybookGlobals);
  }
  const creeveyHost = await resolveCreeveyHost(browser, options.port);
  await browser.executeScript(function (workerId, creeveyHost, creeveyPort) {
    window.__CREEVEY_WORKER_ID__ = workerId;
    window.__CREEVEY_SERVER_HOST__ = creeveyHost;
    window.__CREEVEY_SERVER_PORT__ = creeveyPort;
  }, process.pid, creeveyHost, options.port);
  return browser;
}
async function updateStoryArgs(browser, story, updatedArgs) {
  await browser.executeAsyncScript(function (storyId, updatedArgs, UPDATE_STORY_ARGS, STORY_RENDERED, callback) {
    window.__STORYBOOK_ADDONS_CHANNEL__.once(STORY_RENDERED, callback);
    window.__STORYBOOK_ADDONS_CHANNEL__.emit(UPDATE_STORY_ARGS, {
      storyId,
      updatedArgs
    });
  }, story.id, updatedArgs, _coreEvents.UPDATE_STORY_ARGS, _coreEvents.STORY_RENDERED);
}
async function closeBrowser() {
  if (!browser) return;
  try {
    await browser.quit();
  } finally {
    browser = null;
  }
}
async function switchStory() {
  var _this$currentTest;
  let testOrSuite = this.currentTest;
  if (!testOrSuite) throw new Error("Can't switch story, because test context doesn't have 'currentTest' field");
  this.testScope.length = 0;
  this.screenshots.length = 0;
  this.testScope.push(this.browserName);
  while ((_testOrSuite = testOrSuite) !== null && _testOrSuite !== void 0 && _testOrSuite.title) {
    var _testOrSuite;
    this.testScope.push(testOrSuite.title);
    testOrSuite = testOrSuite.parent;
  }
  const story = (_this$currentTest = this.currentTest) === null || _this$currentTest === void 0 || (_this$currentTest = _this$currentTest.ctx) === null || _this$currentTest === void 0 ? void 0 : _this$currentTest.story;
  if (!story) throw new Error(`Current test '${this.testScope.join('/')}' context doesn't have 'story' field`);
  const {
    id,
    kind,
    name,
    parameters
  } = story;
  const {
    captureElement = `#${storybookRootID}`,
    waitForReady,
    ignoreElements
  } = parameters.creevey ?? {};
  browserLogger.debug(`Switching to story ${_chalk.default.cyan(kind)}/${_chalk.default.cyan(name)} by id ${_chalk.default.magenta(id)}`);
  if (captureElement) Object.defineProperty(this, 'captureElement', {
    enumerable: true,
    configurable: true,
    get: () => this.browser.findElement(_seleniumWebdriver.By.css(captureElement))
  });else Reflect.deleteProperty(this, 'captureElement');
  this.takeScreenshot = () => takeScreenshot(this.browser, captureElement, ignoreElements);
  this.updateStoryArgs = updatedArgs => updateStoryArgs(this.browser, story, updatedArgs);
  this.testScope.reverse();
  let storyPlayResolver;
  let waitForComplete = new Promise(resolve => storyPlayResolver = resolve);
  const unsubscribe = (0, _messages.subscribeOn)('stories', message => {
    if (message.type != 'capture') return;
    const {
      payload = {},
      payload: {
        imageName
      } = {}
    } = message;
    void takeScreenshot(this.browser, payload.captureElement ?? captureElement, payload.ignoreElements ?? ignoreElements).then(screenshot => {
      this.screenshots.push({
        imageName,
        screenshot
      });
      void this.browser.executeAsyncScript(function (callback) {
        window.__CREEVEY_HAS_PLAY_COMPLETED_YET__(callback);
      }).then(isCompleted => storyPlayResolver(isCompleted));
      (0, _messages.emitStoriesMessage)({
        type: 'capture'
      });
    });
  });
  await resetMousePosition(this.browser);
  const isCaptureCalled = await selectStory(this.browser, {
    id,
    kind,
    name
  }, waitForReady);
  if (isCaptureCalled) {
    while (!(await waitForComplete)) {
      waitForComplete = new Promise(resolve => storyPlayResolver = resolve);
    }
  }
  unsubscribe();
  browserLogger.debug(`Story ${_chalk.default.magenta(id)} ready for capturing`);
}
async function insertIgnoreStyles(browser, ignoreElements) {
  const ignoreSelectors = Array.prototype.concat(ignoreElements).filter(Boolean);
  if (!ignoreSelectors.length) return null;
  browserLogger.debug('Hiding ignored elements before capturing');
  return await browser.executeScript(function (ignoreSelectors) {
    return window.__CREEVEY_INSERT_IGNORE_STYLES__(ignoreSelectors);
  }, ignoreSelectors);
}
async function removeIgnoreStyles(browser, ignoreStyles) {
  if (ignoreStyles) {
    browserLogger.debug('Revert hiding ignored elements');
    await browser.executeScript(function (ignoreStyles) {
      window.__CREEVEY_REMOVE_IGNORE_STYLES__(ignoreStyles);
    }, ignoreStyles);
  }
}