UNPKG

dce-selenium

Version:

Selenium library to simplify testing and automatically snapshot the DOM.

360 lines (303 loc) 11.1 kB
const fs = require('fs'); const path = require('path'); // Add drivers to path require('chromedriver'); require('geckodriver'); const buildDriver = require('./buildDriver'); const surroundWithBuffer = require('./surroundWithBuffer'); const consoleLog = require('./consoleLog'); // Try to import custom commands const cwd = process.env.PWD; let customCommands; try { /* eslint-disable import/no-dynamic-require */ /* eslint-disable global-require */ customCommands = require(path.join(cwd, 'test', 'selenium', 'commands')); } catch (err) { customCommands = {}; } /*------------------------------------------------------------------------*/ /* Printing Helpers */ /*------------------------------------------------------------------------*/ /* eslint-disable no-console */ const W = process.stdout.columns; /*------------------------------------------------------------------------*/ /* Build driver */ /*------------------------------------------------------------------------*/ const printWithBorder = (toPrint, error) => { const str = ( typeof toPrint === 'object' ? JSON.stringify(toPrint) : String(toPrint) ); const naturalLines = str.split('\n'); const indentChar = (error ? '' : '> '); const noIndentChar = (error ? '' : ' '); const buffer = (error ? 4 : 6); const startCode = (error ? '\x1b[31m' : ''); const endCode = (error ? '\x1b[0m' : ''); naturalLines.forEach((naturalLine) => { const lines = []; let i = 0; while (i < naturalLine.length) { lines.push( naturalLine.substring(i, Math.min(i + W - buffer, naturalLine.length)) ); i += W - buffer; } lines.forEach((line, index) => { const lineToPrint = `\u2551${startCode} ${index === 0 ? indentChar : noIndentChar}${line}${' '.repeat(W - buffer - line.length)} ${endCode}\u2551`; consoleLog(lineToPrint); }); }); }; let prevDriver; const getDriver = async () => { // Kill previous driver if (prevDriver) { await prevDriver.quit(); } // Build a driver const driver = buildDriver(printWithBorder); prevDriver = driver; // Add in custom commands Object.keys(customCommands).forEach((commandName) => { // Don't allow custom commands to overwrite existing ones if (driver[commandName]) { console.log(`\nCould not create custom command "${commandName}"!`); console.log('Reason: function already exists (no overwrite allowed)'); driver.quit(); process.exit(0); } // Add custom command driver[commandName] = customCommands[commandName].bind(driver); }); await driver.webdriver.get('about:blank'); return driver; }; /*------------------------------------------------------------------------*/ /* Keep track of describe level */ /*------------------------------------------------------------------------*/ // Name describe calls using letters const LETTERS = 'ABCDEFGHIJKLMNOPQRSTUVWXYZ'.split(''); // Keep nested list of describes (so snapshots can mirror describe structure in // folders) const describeIndices = [-1]; const describeLevels = []; global.describeBeforeEachMap = {}; global.describeAfterEachMap = {}; // Create a new describe function that keeps track of describe level const originalDescribe = describe; const genDescribe = (name) => { return async (title, test) => { // Get describe index (and increment) const index = describeIndices.pop() + 1; describeIndices.push(index); // Create a letter marker based on the describe index const marker = ( index < LETTERS.length ? LETTERS[index] : LETTERS[LETTERS.length - 1] + (index - LETTERS.length) ); // Add new level or titles and indices const newTitle = `${marker} - ${title}`; // Call original describe function /* eslint-disable prefer-arrow-callback */ /* eslint-disable func-names */ const describeToCall = (name ? originalDescribe[name] : originalDescribe); await describeToCall(newTitle, async function (...args) { // Add level of title and index // eslint-disable-next-line no-undef before(() => { describeLevels.push(newTitle); describeIndices.push(-1); global.describeTitle = newTitle; }); // Remove level of title and index // eslint-disable-next-line no-undef after(() => { global.describeTitle = null; describeIndices.pop(); describeLevels.pop(); }); // Create beforeEachS, afterEachS functions const beforeEachS = (handler) => { global.describeBeforeEachMap[newTitle] = handler; }; const afterEachS = (handler) => { global.describeAfterEachMap[newTitle] = handler; }; // Add containing tests return test(beforeEachS, afterEachS, ...args); }); }; }; global.describeS = genDescribe(); global.describeS.skip = genDescribe('skip'); global.describeS.only = genDescribe('only'); /*------------------------------------------------------------------------*/ /* Create itS */ /*------------------------------------------------------------------------*/ const setup = () => { // Create itS function for mocha testing let testIndex = 0; // Gen itS sub-function const genIt = (name) => { return (title, a, b) => { let test; let timeout; if (b) { test = b; timeout = a; } else { test = a; timeout = 45000; } // Increment test number testIndex += 1; const thisTestIndex = testIndex; // Call normal "it" function /* eslint-disable func-names */ const newTitle = `${thisTestIndex} - ${title}`; // eslint-disable-next-line no-undef (name ? it[name] : it)(newTitle, async function () { // Add initial timeout that allows enough time to create a window this.timeout(600000); // 1 minute // Keep track of setup time so we can add that to timeout const testStartTime = Date.now(); // Update global names (for snapshot naming) global.testTitle = newTitle; global.describeFolder = ( describeLevels.length > 0 ? describeLevels.join('/') : null ); // Create a new driver const driver = await getDriver(); // Printing functions const logStart = (actualTitle = newTitle) => { consoleLog('\u2554' + '\u2550'.repeat(W - 2) + '\u2557'); consoleLog(surroundWithBuffer(actualTitle, '\u2551')); }; const logEnd = () => { consoleLog('\u255A' + '\u2550'.repeat(W - 2) + '\u255D'); }; // Before each const title = global.describeTitle; const beforeEachFunction = global.describeBeforeEachMap[title]; if (beforeEachFunction) { logStart('Before Each'); await beforeEachFunction(driver); logEnd(); } // Log start of test logStart(); // Find setup time const setupTime = Date.now() - testStartTime; // Set default timeout to very large this.timeout(timeout + setupTime); // Run the test let error; try { // Test await test(driver); } catch (err) { let { message } = err; const driverDead = (err.name === 'NoSuchSessionError'); // ^ also happens when a timeout occurred if (driverDead) { // Timeout occurred message = `Either test timed out at ${timeout}ms (${timeout + setupTime}ms with setup) or we could not get a valid browser session (another automated window is still open or driver not set up correctly)`; error = new Error(message); } else { error = err; } // Print error consoleLog(surroundWithBuffer('Test Failed! Error message:', '\u2551')); printWithBorder(message, true); consoleLog(surroundWithBuffer('See full stacktrace after test results', '\u2551')); } // Log end of test logEnd(); // After each const afterEachFunction = global.describeAfterEachMap[title]; if (afterEachFunction) { logStart('After Each'); await afterEachFunction(driver); logEnd(); } // Clean up driver session await driver.quit(); if (error) { throw error; } }); }; }; // Buid itS global.itS = genIt(); global.itS.only = genIt('only'); global.itS.skip = genIt('skip'); }; /*------------------------------------------------------------------------*/ /* Initialization Function */ /*------------------------------------------------------------------------*/ // Get timestamp of current date const startTime = new Date(); const replaceAll = (str, search, replacement) => { return str.replace(new RegExp(search, 'g'), replacement); }; const timestamp = `${startTime.toLocaleDateString()} ${replaceAll(startTime.toLocaleTimeString(), ':', '-')}`; const init = (config = {}) => { // Get browser and snapshot name from arguments let browser = 'chrome'; let headless = false; let snapshotName = timestamp; for (let i = 0; i < process.argv.length; i++) { const arg = process.argv[i]; if (arg === '--chrome') { browser = 'chrome'; } else if (arg === '--safari') { browser = 'safari'; } else if (arg === '--safari-stp') { browser = 'safari-technology-preview'; } else if (arg === '--firefox') { browser = 'firefox'; } else if (arg === '--headless-chrome') { browser = 'chrome'; headless = true; } else if (arg.startsWith('--snapshot-title ')) { snapshotName = arg.replace('--snapshot-title ', ''); } } // Set globals global.dceSeleniumConfig = { headless, browser, snapshotName, browserDescription: ( headless ? `Headless ${browser.charAt(0).toUpperCase()}${browser.substring(1)}` : `${browser.charAt(0).toUpperCase()}${browser.substring(1)}` ), defaultHost: config.defaultHost || null, dontUseHTTPS: config.dontUseHTTPS, noSnapshots: config.noSnapshots, }; // Initialize driver and tests setup(); }; /*------------------------------------------------------------------------*/ /* Initialize by Config File */ /*------------------------------------------------------------------------*/ // Read in config let config; try { const configPath = path.join(cwd, 'test', 'selenium', 'config.json'); config = JSON.parse(fs.readFileSync(configPath, 'utf-8')); } catch (err) { config = {}; } // Initialize with defaults just in case no call by programmer init(config); module.exports = init;