qa-shadow-report
Version:
npm package that prints formatted test reports into a google sheet or csv file
372 lines (338 loc) • 11 kB
JavaScript
#!/usr/bin/env node
import fs from 'fs';
import { execSync } from 'child_process';
import readline from 'readline';
import chalk from 'chalk';
import path from 'path';
import { fileURLToPath } from 'url';
import { dirname } from 'path';
import { isProjectConfigured } from './configuredStatus.js';
const __filename = fileURLToPath(import.meta.url);
const __dirname = dirname(__filename);
// Get the directory name of the current script
const configFileName = 'shadowReportConfig.js';
/**
* Finds the root directory of the project by looking for package.json.
* @param {string} startPath - The path to start searching from.
* @returns {string|null} The path to the project root or null if not found.
*/
const findProjectRoot = (startPath) => {
let currentDir = startPath;
while (currentDir !== path.parse(currentDir).root) {
const packageJsonPath = path.join(currentDir, 'package.json');
if (fs.existsSync(packageJsonPath)) {
const nodeModulesPath = path.join(currentDir, 'node_modules');
// Check if the current directory is inside a node_modules directory
if (!currentDir.includes(path.sep + 'node_modules' + path.sep)) {
return currentDir;
}
}
currentDir = path.dirname(currentDir);
}
return null;
};
const projectRootPath = findProjectRoot(__dirname);
if (!projectRootPath) {
console.error('Error: Could not determine the project root path.');
process.exit(1);
}
const configFilePath = path.join(projectRootPath, configFileName);
const isConfiguredFilePath = path.join(__dirname, 'configuredStatus.js');
const isConfigured = isProjectConfigured();
const packages = ['mochawesome', 'mochawesome-merge'];
const setupLink =
'https://www.npmjs.com/package/qa-shadow-report#sheets-setup-guide';
const rl = readline.createInterface({
input: process.stdin,
output: process.stdout,
});
const promptUser = (message, options, callback) => {
rl.question(chalk.blue(message), (answer) => {
answer = answer.trim().toLowerCase();
if (options.includes(answer)) {
callback(answer);
} else {
confirmExit();
}
});
};
const confirmExit = () => {
rl.question(
chalk.yellow('Would you like to exit setup? [y/n]: '),
(answer) => {
if (answer === 'n') {
startSetup();
} else {
console.info(
chalk.green('Exiting setup.'),
chalk.yellow(
` Check the setup guide information on dependencies ${setupLink}`
)
);
updateIsConfiguredFile(true);
rl.close();
process.exit(0);
}
}
);
};
/**
* Creates the configuration file with default values.
* @param {string} configPath - Path to the configuration file.
* @param {Object} config - Configuration object.
*/
const createConfigFile = () => {
console.info(
chalk.blue('Checking if config file exists at path:'),
chalk.green(` '${configFilePath}'`)
);
if (fs.existsSync(configFilePath)) {
console.info(
chalk.yellow('Config file'),
chalk.green(` '${configFileName}'`),
chalk.yellow(' already exists.')
);
return;
} else {
const defaultConfigContent = `
// Sample Configuration File: Adjust values below to match your project's setup
// TypeScript type definition (will be ignored in JavaScript)
/** @type {{
teamNames: string[];
testTypes: string[];
testCategories: string[];
googleSpreadsheetId: string;
googleKeyFilePath: string;
testData: string;
csvDownloadsPath: string;
weeklySummaryStartDay: string;
}} */
const config = {
// Uncomment the relevant teams or add your own in the Describe block or It block:
// describe('[oregano] Unit test our math functions', () => {
// context('math', () => {
// it('can add numbers [C2452][smoke]', () => {
teamNames: [
//'oregano',
//'wilkins',
//'canonicus',
],
// (default list) Uncomment the relevant test types or add your own by modifying your folder structure [framework]/ui/1-getting-started/todo.cy.js:
testTypes: [
// 'api',
// 'ui',
// 'unit',
// 'integration',
// 'endToEnd',
// 'performance',
// 'security',
// 'database',
// 'accessibility',
// 'mobile',
],
// (default list) Uncomment the relevant test categories or add your own in the Describe block or It block:
// describe('[oregano] Unit test our math functions', () => {
// context('math', () => {
// it('can add numbers [C2452][smoke]', () => {
testCategories: [
// 'smoke',
// 'regression',
// 'sanity',
// 'exploratory',
// 'functional',
// 'load',
// 'stress',
// 'usability',
// 'compatibility',
// 'alpha',
// 'beta',
],
// Google Spreadsheet URL: Replace with either the spreadsheet URL or an environment variable.
googleSpreadsheetUrl: '', // Add your Google Spreadsheet URL here
// Path to Google credentials file (service account JSON file): Replace with the file path or use an environment variable.
googleKeyFilePath: './googleCredentials.json',
// Path to test data results file in the format generated by your test runner. Replace with the path or use an environment variable.
testData: './path-to-test-results/output.json',
// Directory for saving CSV downloads (optional). Uncomment and replace with your preferred path or use an environment variable.
csvDownloadsPath: './downloads',
// Weekly summary settings (optional). To activate, set a weekly summary start day, summary will include the start day and the following 6 days (7 days total).
weeklySummaryStartDay: 'Monday',
};
// Universal export that works with both CommonJS and ES Modules
if (typeof module !== 'undefined' && module.exports) {
module.exports = config;
}
if (typeof exports !== 'undefined') {
exports.default = config;
}
if (typeof window !== 'undefined') {
window.shadowReportConfig = config;
}
export default config;
`;
fs.writeFileSync(configFilePath, defaultConfigContent, {
encoding: 'utf-8',
});
console.info(
chalk.blue(`Config file created at: `),
chalk.green(`${projectRootPath}/${configFileName}`)
);
console.info(
chalk.yellow(`Please update the `),
chalk.green(`testData `),
chalk.yellow(`sourcepath in `),
chalk.green(`${configFileName} `),
chalk.yellow(`to match your project's setup.`)
);
}
};
const installPackages = (manager) => {
try {
if (manager === 'npm') {
console.info(
chalk.green(`Installing packages with npm: ${packages.join(', ')}`)
);
execSync(`npm install --save-dev ${packages.join(' ')}`, {
stdio: 'inherit',
});
} else if (manager === 'yarn') {
console.info(
chalk.green(`Installing packages with yarn: ${packages.join(', ')}`)
);
execSync(`yarn add --dev ${packages.join(' ')}`, { stdio: 'inherit' });
}
console.info(chalk.green('Packages installed successfully.'));
} catch (error) {
console.error(chalk.red('Failed to install packages:'), error);
}
};
const handlePostInstallTasks = (framework) => {
promptUser(
`A config file is required for qa-shadow-report would you like us to create one now at path ${chalk.green(
configFilePath
)}? ${chalk.green('[y/n]')}: `,
['y', 'n'],
(create) => {
if (create === 'y') {
createConfigFile();
proceedWithFrameworkSpecificInstructions(framework);
} else {
console.info(
chalk.green('Skipping config file creation.'),
chalk.yellow(
` Check the setup guide information on dependencies ${setupLink}`
)
);
proceedWithFrameworkSpecificInstructions(framework);
}
}
);
};
const proceedWithFrameworkSpecificInstructions = (framework) => {
if (framework === 'pw') {
console.info(
chalk.yellow('Please ensure'),
chalk.green("'playwright.config.js'"),
chalk.yellow(' is updated to use the JSON reporter,'),
chalk.green(
" reporter: [['json', { outputFile: 'test-results/output.json' }]]"
)
);
finalizeSetup();
} else if (framework === 'cy') {
promptUser(
`Would you like to install ${chalk.green(
packages.join(', ')
)} for Cypress test reporting with Yarn or NPM, or skip this step (skip)? ${chalk.green(
'[yarn/npm/skip]'
)}: `,
['yarn', 'npm', 'skip'],
(installer) => {
if (['yarn', 'npm'].includes(installer)) {
installPackages(installer);
} else {
console.info(
chalk.green('Skipping dependency installation.'),
chalk.yellow(
` Check the setup guide information on dependencies ${setupLink}`
)
);
}
finalizeSetup();
}
);
}
};
const updateIsConfiguredFile = (isConfigured) => {
try {
const content = fs.readFileSync(isConfiguredFilePath, 'utf8');
const searchValue = isConfigured ? 'return false;' : 'return true;';
const replaceValue = isConfigured ? 'return true;' : 'return false;';
const updatedContent = content.replace(searchValue, replaceValue);
fs.writeFileSync(isConfiguredFilePath, updatedContent, 'utf8');
} catch (err) {
console.error(
chalk.red('Failed to update the configuredStatus file:', err)
);
}
};
const finalizeSetup = () => {
console.info(chalk.green('qa-shadow-report setup complete!'));
updateIsConfiguredFile(true);
rl.close();
};
const confirmReconfigure = () => {
if (isConfigured) {
promptUser(
`You already ran configuration, would you like to continue with configuration? ${chalk.green(
'[y/n]'
)}: `,
['y', 'n'],
(answer) => {
if (answer === 'y') {
updateIsConfiguredFile(false);
startSetup();
} else {
confirmExit();
}
}
);
} else {
startSetup();
}
};
const startSetup = () => {
promptUser(
`Are you using Playwright (pw) or Cypress (cy)? ${chalk.green(
'[pw/cy]'
)}: `,
['pw', 'cy'],
(framework) => {
if (['pw', 'cy'].includes(framework)) {
handlePostInstallTasks(framework);
} else {
confirmExit();
}
}
);
};
try {
confirmReconfigure();
} catch (error) {
console.error(
chalk.red(
`An error occurred during the post-installation script. Check the setup guide information on dependencies ${setupLink}`
),
error
);
process.exit(1);
}
// Export functions for testing
export {
findProjectRoot,
createConfigFile,
installPackages,
handlePostInstallTasks,
proceedWithFrameworkSpecificInstructions,
startSetup,
};