commercetools-8.x-1.2-alpha1/tests/src/Nightwatch/Lib/getBeforeAfterFunctions.js

tests/src/Nightwatch/Lib/getBeforeAfterFunctions.js
const { EventEmitter } = require('events');
const path = require('path');
const { execSync } = require('child_process');

// A commit message marker to disable the updatedb check rollback.
// If you produced a change that is not compatible with the updatedb check,
// add this marker to the commit message to prevent the rollback to this commit.
const commitMessageDisableUpdatedbCheckRollback =
  '[ct-disable-updatedb-check-rollback]';

// A flag to check if the before function finished successfully.
let isBeforeFunctionFinishedSuccessfully;

// Stores the check updates mode status.
let isCheckUpdatesAllowed;

// Stores the original commit id and branch name to revert the codebase.
let pipelineCommitId;
let pipelineCommitBranch;

const mainModulePath = path.resolve(__dirname, '../../../..');

function getModuleDirByTestFile(testFile) {
  const relativePath = path.relative(mainModulePath, testFile);
  const pathSegments = relativePath.split(path.sep);
  let moduleDir = mainModulePath;
  const moduleIndex = pathSegments.lastIndexOf('modules') ?? 0;
  if (moduleIndex !== -1) {
    moduleDir +=
      path.sep + pathSegments.slice(0, moduleIndex + 2).join(path.sep);
  }
  return moduleDir;
}

let drushRunAvailable;

/**
 * Returns the before and after functions for the test.
 * @param {string} testFile
 *   The path to the test file to auto detect the relevant installing profile.
 * @param {object} options
 *   Additional options:
 *   - feature: string, the feature name to install, used as the prefix to the
 *     profile name.
 *   - checkUpdates: boolean, if true the updates check is enabled.
 *   - skipDrupalLogsErrorCheck: boolean, if true the Drupal logs error check
 *     is skipped.
 * @return {object}
 *   The before and after functions, and the generated test tags.
 */
module.exports = function getBeforeAfterFunctions(testFile, options = {}) {
  const moduleDir = getModuleDirByTestFile(testFile);
  // In the CI we have the different name of the main module directory.
  // A workaround to set the name manually for the main module.
  const moduleName =
    moduleDir === mainModulePath
      ? 'commercetools'
      : moduleDir.split(path.sep).pop();

  const testName = `nightwatch::${path.relative(mainModulePath, testFile)}`;
  const thSettings = {
    name: testName,
    context: options.context ? `${moduleName}:${options.context}` : undefined,
  };
  const featurePart = options.feature ? `${options.feature}_` : '';
  const profile = `${moduleName}_${featurePart}test_profile`;

  function checkIsCheckUpdatesAllowed() {
    if (options.checkUpdates !== true) {
      return false;
    }

    if (process.env.CI) {
      // We need to output to console here.
      // eslint-disable-next-line no-console
      console.log(
        'Hook Update checker: the CI environment detected, the updates check mode enabled.',
      );
      return true;
    }

    // Force enable check updates if explicitly enabled.
    if (process.env.CT_CHECK_UPDATES) {
      // We need to output to console here.
      // eslint-disable-next-line no-console
      console.log(
        'Hook Update checker: the check updates mode is enabled by the CT_CHECK_UPDATES environment variable.',
      );

      // A special check for non-CI environments to not alter uncommitted changes.
      if (!process.env.CI) {
        try {
          const result = execSync(`cd ${moduleDir} && git diff --exit-code`);
          // We need to output to console here.
          // eslint-disable-next-line no-console
          console.log(
            `${result.toString()}Hook Update checker: all git changes committed, the updates check mode enabled.`,
          );
          return true;
        } catch {
          // We need to output to console here.
          // eslint-disable-next-line no-console
          console.log(
            'Hook Update checker: the current git contain changes, the updates check mode disabled.',
          );
          return false;
        }
      }
    } else {
      // We need to output to console here.
      // eslint-disable-next-line no-console
      console.log(
        'Hook Update checker: the check updates mode is disabled because no CI environment is detected. You can enable it by setting the environment variable CT_CHECK_UPDATES=1',
      );
    }
  }

  function revertModuleVersion() {
    // @todo Implement switching back to the target branch for MR pipelines.
    const jumpBackCommitsMax = 8;

    // Get the current commit id to store the git state in the pipeline.
    pipelineCommitId = execSync(`cd ${moduleDir} && git rev-parse HEAD`)
      .toString()
      .trim();
    // We need to output to console here.
    // eslint-disable-next-line no-console
    console.log(`\n\nThe current commit id: ${pipelineCommitId}`);

    // Get the current branch or commit id.
    const currentBranchName = execSync(
      `cd ${moduleDir} && git rev-parse --abbrev-ref HEAD`,
    )
      .toString()
      .trim();
    if (currentBranchName !== 'HEAD') {
      pipelineCommitBranch = currentBranchName;
      // We need to output to console here.
      // eslint-disable-next-line no-console
      console.log(`\n\nThe current branch: ${pipelineCommitBranch}`);
    }

    // Revert the codebase to the previous version.
    // We need to output to console here.
    // eslint-disable-next-line no-console
    console.log(
      `Parsing last ${jumpBackCommitsMax} commits to check the right reverting point, commits log:`,
    );
    const lastCommitsData = execSync(
      `cd ${moduleDir} && git log --oneline --no-abbrev-commit -n ${jumpBackCommitsMax + 1}`,
    );
    // We need to output to console here.
    // eslint-disable-next-line no-console
    console.log(lastCommitsData.toString());
    const listCommits = lastCommitsData.toString().trim().split('\n');
    let jumpBackCommitId = null;
    let jumpBackCommitDelta = 0;
    for (let i = 0; i < listCommits.length; i++) {
      const [, commitId, commitMessage] = listCommits[i].split(/(\w+)\s(.+)/);
      if (commitMessage.includes(commitMessageDisableUpdatedbCheckRollback)) {
        // We need to output to console here.
        // eslint-disable-next-line no-console
        console.log(
          `Stopping before the commit ${commitId} because of the "${commitMessageDisableUpdatedbCheckRollback}" tag.`,
        );
        break;
      }
      jumpBackCommitId = commitId;
      jumpBackCommitDelta = i;
    }

    if (!jumpBackCommitId || jumpBackCommitDelta < 1) {
      // We need to output to console here.
      // eslint-disable-next-line no-console
      console.log(
        'The last commit contains a marker [ct-skip-updatedb-check] - switching back is omitted.',
      );
      return;
    }
    // We need to output to console here.
    // eslint-disable-next-line no-console
    console.log(
      `Reverting the codebase to a previous version by jumping ${jumpBackCommitDelta} commits back to ${jumpBackCommitId}.`,
    );
    const result = execSync(
      `cd ${moduleDir} && git checkout -f ${jumpBackCommitId}`,
    );
    // We need to output to console here.
    // eslint-disable-next-line no-console
    console.log(result.toString());
  }

  function resetToLastVersion() {
    // Revert the codebase to the original state.
    // We need to output to console here.
    // eslint-disable-next-line no-console
    console.log('\n\nReverting the codebase to the original state:');
    const result = execSync(
      `cd ${moduleDir} && git checkout -f ${pipelineCommitBranch ?? pipelineCommitId} -f`,
    );
    // We need to output to console here.
    // eslint-disable-next-line no-console
    console.log(result.toString());
  }

  function runUpdatedb() {
    if (!drushRunAvailable) {
      // We need to output to console here.
      // eslint-disable-next-line no-console
      console.warn(
        'Skipping the Drupal logs check because the environment missing the "HTTP_USER_AGENT=simpletest*" variable. Apply a patch from https://www.drupal.org/project/drupal/issues/3504204',
      );
      return;
    }

    try {
      // Apply updates using drush.
      // We need to output to console here.
      // eslint-disable-next-line no-console
      console.log(`Applying updates using "drush updatedb" command:`);

      // We need to output to console here.
      // eslint-disable-next-line no-console
      console.log(execSync('drush updatedb -y').toString());
    } catch (error) {
      throw new Error(
        'Failed to apply updates using drush updatedb, the test script is stopped.',
      );
    }
  }

  // For debugging you can comment this line to temporary disable all tests in
  // the pipeline and put the tag manually on a specific test to debug it.
  let testTags = options.testTags ?? ['commercetools', moduleName];

  // Set a custom flag for tests with check updates, if this check is enabled.
  if (checkIsCheckUpdatesAllowed()) {
    testTags = ['commercetools-non_parallel'];
  }

  return {
    '@tags': typeof testTags !== 'undefined' ? testTags : [],

    before(browser) {
      isBeforeFunctionFinishedSuccessfully = false;
      // Increase max listeners for this long running test - a workaround for
      // the issue https://github.com/nightwatchjs/nightwatch/issues/408
      EventEmitter.defaultMaxListeners = 100;
      isCheckUpdatesAllowed = checkIsCheckUpdatesAllowed();

      // Set a container for the commercetools global variables.
      browser.globals.ct = {};
      if (options.testingModuleName) {
        browser.globals.ct.testingModuleName = options.testingModuleName;
      }

      browser.window.setSize(1080, 2600);

      browser
        .captureBrowserConsoleLogs((event) => {
          // We need to output to console here.
          // eslint-disable-next-line no-console
          console.log(
            'Browser Console message:',
            event.type,
            event.timestamp,
            event.args[0].value,
          );
        })
        .perform(() => {
          if (isCheckUpdatesAllowed) {
            // We need to output to console here.
            // eslint-disable-next-line no-console
            console.log('Executing pre-install check update actions');
            revertModuleVersion();
          }
        })
        .perform(() => {
          // We need to output to console here.
          // eslint-disable-next-line no-console
          console.log('Installing the profile:', profile);
        })
        .drupalInstall({ installProfile: profile })
        .perform(() => {
          // We need to output to console here.
          // eslint-disable-next-line no-console
          console.log(`The profile '${profile}' installed.`);
          // @todo Remove when https://www.drupal.org/project/drupal/issues/3504204
          // is resolved.
          drushRunAvailable = (process.env.HTTP_USER_AGENT ?? '').includes(
            'simpletest',
          );
        })
        .perform(() => {
          if (isCheckUpdatesAllowed) {
            // We need to output to console here.
            // eslint-disable-next-line no-console
            console.log('Executing post-install check update actions');
            resetToLastVersion();
            runUpdatedb();
          }
        })
        // Set the current test name for the HTTP mock assets.
        .testHelpersHttpMockSetSettings(thSettings)
        .perform(() => {
          if (
            process.env.CT_API_MOCK_MODE === 'append' ||
            process.env.CT_API_MOCK_MODE === 'store'
          ) {
            // We need to output to console here.
            // eslint-disable-next-line no-console
            console.log(
              'Using the real API mode with the credentials from the environment variables.',
            );
            browser.thSetEnvs({
              CT_API_MOCK_MODE: process.env.CT_API_MOCK_MODE,
            });
          }
          if (process.env.CT_LOG_STORED_RESPONSES_FILE) {
            // We need to output to console here.
            // eslint-disable-next-line no-console
            console.log('Enabled the logging assets usage mode.');
            browser.thSetEnvs({
              CT_LOG_STORED_RESPONSES_FILE:
                process.env.CT_LOG_STORED_RESPONSES_FILE,
            });
          }
          isBeforeFunctionFinishedSuccessfully = true;
        });
    },

    beforeEach(browser) {
      if (!isBeforeFunctionFinishedSuccessfully) {
        // This shows the error in the specific test output area.
        // We need to output to console here.
        // eslint-disable-next-line no-console
        console.error(
          'Skipping the test because the function before() has not finished successfully.',
        );
        if (isCheckUpdatesAllowed) {
          // We need to output to console here.
          // eslint-disable-next-line no-console
          console.log('Reverting the git state back');
          resetToLastVersion(browser);
        }
        // This prevents the test to be executed.
        throw new Error(
          'The test file aborted because of the failure in the function before().',
        );
      }
    },

    after(browser) {
      let logs;
      if (drushRunAvailable) {
        logs = execSync(
          'COLUMNS=1024 drush watchdog-show --count=100',
        ).toString();
        // We need to output to console here.
        // eslint-disable-next-line no-console
        console.log(`Drupal logger output (last 100 messages):\n${logs}`);
      } else {
        // We need to output to console here.
        // eslint-disable-next-line no-console
        console.warn(
          'Skipping the Drupal logs check because the environment missing the HTTP_USER_AGENT=simpletest variable. Apply a patch from https://www.drupal.org/project/drupal/issues/3504204',
        );
      }

      browser.execute(
        () => {
          return JSON.parse(
            sessionStorage.getItem('js_testing_log_test.errors') || '[]',
          );
        },
        [],
        ({ value: errors = [] }) => {
          if (Array.isArray(errors) && errors.length) {
            console.error('JS errors:', JSON.stringify(errors, null, 2));
            // throw new Error('Errors found in the Browser console logs.');
            browser.assert.strictEqual(
              errors.length,
              0,
              `No JS errors expected (found ${errors.length}).`,
            );
          }
        },
      );

      browser.drupalUninstall();

      if (drushRunAvailable) {
        if (!options.skipDrupalLogsErrorCheck) {
          if (logs.search('Error') !== -1) {
            throw new Error('Errors found in the Drupal logs.');
          }
        }
      }
      // Reset max listeners to the node.js default once the test is complete.
      EventEmitter.defaultMaxListeners = 10;
    },
  };
};

Главная | Обратная связь

drupal hosting | друпал хостинг | it patrol .inc