<NME> base-scraper-with-browser.js
<BEF> ADDFILE
<MSG> Merge branch 'master' into txn_status

<DFF> @@ -0,0 +1,109 @@
+import puppeteer from 'puppeteer';
+
+import { BaseScraper } from './base-scraper';
+import { SCRAPE_PROGRESS_TYPES, LOGIN_RESULT, GENERAL_ERROR } from '../constants';
+import { waitForNavigation, getCurrentUrl } from '../helpers/navigation';
+import { waitUntilElementFound, fillInput, clickButton } from '../helpers/elements-interactions';
+
+const VIEWPORT_WIDTH = 1024;
+const VIEWPORT_HEIGHT = 768;
+
+function getKeyByValue(object, value) {
+  return Object.keys(object).find(key => object[key].includes(value));
+}
+
+function handleLoginResult(scraper, loginResult) {
+  switch (loginResult) {
+    case LOGIN_RESULT.SUCCESS:
+      scraper.emitProgress(SCRAPE_PROGRESS_TYPES.LOGIN_SUCCESS);
+      return { success: true };
+    case LOGIN_RESULT.INVALID_PASSWORD:
+      scraper.emitProgress(SCRAPE_PROGRESS_TYPES.LOGIN_FAILED);
+      return {
+        success: false,
+        errorType: loginResult,
+      };
+    case LOGIN_RESULT.CHANGE_PASSWORD:
+      scraper.emitProgress(SCRAPE_PROGRESS_TYPES.CHANGE_PASSWORD);
+      return {
+        success: false,
+        errorType: loginResult,
+      };
+    default:
+      throw new Error(`unexpected login result "${loginResult}"`);
+  }
+}
+
+function createGeneralError() {
+  return {
+    success: false,
+    errorType: GENERAL_ERROR,
+  };
+}
+
+class BaseScraperWithBrowser extends BaseScraper {
+  async initialize() {
+    this.emitProgress(SCRAPE_PROGRESS_TYPES.INITIALIZING);
+
+    let env = null;
+    if (this.options.verbose) {
+      env = Object.assign({ DEBUG: '*' }, process.env);
+    }
+    this.browser = await puppeteer.launch({ env, headless: !this.options.showBrowser });
+    this.page = await this.browser.newPage();
+    await this.page.setViewport({
+      width: VIEWPORT_WIDTH,
+      height: VIEWPORT_HEIGHT,
+    });
+  }
+
+  getLoginOptions() {
+    throw new Error(`getLoginOptions() is not created in ${this.options.companyId}`);
+  }
+
+  async fillInputs(fields) {
+    const modified = [...fields];
+    const input = modified.shift();
+    await fillInput(this.page, input.selector, input.value);
+    if (modified.length) {
+      return this.fillInputs(modified);
+    }
+    return null;
+  }
+
+  async login(credentials) {
+    if (!credentials) {
+      return createGeneralError();
+    }
+
+    const loginOptions = this.getLoginOptions(credentials);
+
+    await this.page.goto(loginOptions.loginUrl);
+    if (loginOptions.checkReadiness) {
+      await loginOptions.checkReadiness();
+    } else {
+      await waitUntilElementFound(this.page, loginOptions.submitButtonSelector);
+    }
+
+    await this.fillInputs(loginOptions.fields);
+    await clickButton(this.page, loginOptions.submitButtonSelector);
+    this.emitProgress(SCRAPE_PROGRESS_TYPES.LOGGING_IN);
+
+    if (loginOptions.postAction) {
+      await loginOptions.postAction();
+    } else {
+      await waitForNavigation(this.page);
+    }
+
+    const current = await getCurrentUrl(this.page, true);
+    const loginResult = getKeyByValue(loginOptions.possibleResults, current);
+    return handleLoginResult(this, loginResult);
+  }
+
+  async terminate() {
+    this.emitProgress(SCRAPE_PROGRESS_TYPES.TERMINATING);
+    await this.browser.close();
+  }
+}
+
+export { BaseScraperWithBrowser, LOGIN_RESULT };

Merge branch 'master' into txn_status