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;
},
};
};
