UNPKG

codeceptjs

Version:

Supercharged End 2 End Testing Framework for NodeJS

240 lines (209 loc) 6.93 kB
const colors = require('chalk') const readline = require('readline') const ora = require('ora-classic') const debug = require('debug')('codeceptjs:pause') const container = require('./container') const history = require('./history') const store = require('./store') const aiAssistant = require('./ai') const recorder = require('./recorder') const event = require('./event') const output = require('./output') const { methodsOfObject, searchWithFusejs } = require('./utils') // npm install colors let rl let nextStep let finish let next let registeredVariables = {} /** * Pauses test execution and starts interactive shell * @param {Object<string, *>} [passedObject] */ const pause = function (passedObject = {}) { if (store.dryRun) return next = false // add listener to all next steps to provide next() functionality event.dispatcher.on(event.step.after, () => { recorder.add('Start next pause session', () => { // test already finished, nothing to pause if (!store.currentTest) return if (!next) return return pauseSession() }) }) event.dispatcher.on(event.test.finished, () => { finish() recorder.session.restore('pause') rl.close() history.save() }) recorder.add('Start new session', () => pauseSession(passedObject)) } function pauseSession(passedObject = {}) { registeredVariables = passedObject recorder.session.start('pause') if (!next) { let vars = Object.keys(registeredVariables).join(', ') if (vars) vars = `(vars: ${vars})` output.print(colors.yellow(' Interactive shell started')) output.print(colors.yellow(' Use JavaScript syntax to try steps in action')) output.print(colors.yellow(` - Press ${colors.bold('ENTER')} to run the next step`)) output.print(colors.yellow(` - Press ${colors.bold('TAB')} twice to see all available commands`)) output.print(colors.yellow(` - Type ${colors.bold('exit')} + Enter to exit the interactive shell`)) output.print(colors.yellow(` - Prefix ${colors.bold('=>')} to run js commands ${colors.bold(vars)}`)) if (aiAssistant.isEnabled) { output.print(colors.blue(` ${colors.bold('AI is enabled! (experimental)')} Write what you want and make AI run it`)) output.print(colors.blue(' Please note, only HTML fragments with interactive elements are sent to AI provider')) output.print(colors.blue(' Ideas: ask it to fill forms for you or to click')) } } rl = readline.createInterface({ input: process.stdin, output: process.stdout, terminal: true, completer, history: history.load(), historySize: 50, // Adjust the history size as needed }) store.onPause = true rl.on('line', parseInput) rl.on('close', () => { if (!next) console.log('Exiting interactive shell....') store.onPause = false }) return new Promise(resolve => { finish = resolve return askForStep() }) } async function parseInput(cmd) { rl.pause() next = false recorder.session.start('pause') if (cmd === '') next = true if (!cmd || cmd === 'resume' || cmd === 'exit') { finish() recorder.session.restore('pause') rl.close() history.save() return nextStep() } for (const k of Object.keys(registeredVariables)) { eval(`var ${k} = registeredVariables['${k}'];`) } let executeCommand = Promise.resolve() const getCmd = () => { debug('Command:', cmd) return cmd } let isCustomCommand = false let lastError = null let isAiCommand = false let $res try { const locate = global.locate // enable locate in this context const I = container.support('I') if (cmd.trim().startsWith('=>')) { isCustomCommand = true cmd = cmd.trim().substring(2, cmd.length) } else if (aiAssistant.isEnabled && cmd.trim() && !cmd.match(/^\w+\(/) && cmd.includes(' ')) { const currentOutputLevel = output.level() output.level(0) const res = I.grabSource() isAiCommand = true executeCommand = executeCommand.then(async () => { try { const html = await res await aiAssistant.setHtmlContext(html) } catch (err) { output.print(output.styles.error(' ERROR '), "Can't get HTML context", err.stack) return } finally { output.level(currentOutputLevel) } const spinner = ora('Processing AI request...').start() cmd = await aiAssistant.writeSteps(cmd) spinner.stop() output.print('') output.print(colors.blue(aiAssistant.getResponse())) output.print('') return cmd }) } else { cmd = `I.${cmd}` } executeCommand = executeCommand .then(async () => { const cmd = getCmd() if (!cmd) return return eval(cmd) }) .catch(err => { debug(err) if (isAiCommand) return if (!lastError) output.print(output.styles.error(' ERROR '), err.message) debug(err.stack) lastError = err.message }) const val = await executeCommand if (isCustomCommand) { if (val !== undefined) console.log('Result', '$res=', val) $res = val } if (cmd?.startsWith('I.see') || cmd?.startsWith('I.dontSee')) { output.print(output.styles.success(' OK '), cmd) } if (cmd?.startsWith('I.grab')) { try { output.print(output.styles.debug(JSON.stringify(val, null, 2))) } catch (err) { output.print(output.styles.error(' ERROR '), 'Failed to stringify result:', err.message) output.print(output.styles.error(' RAW VALUE '), String(val)) } } history.push(cmd) // add command to history when successful } catch (err) { if (!lastError) output.print(output.styles.error(' ERROR '), err.message) lastError = err.message } recorder.session.catch(err => { const msg = err.cliMessage ? err.cliMessage() : err.message // pop latest command from history because it failed history.pop() if (isAiCommand) return if (!lastError) output.print(output.styles.error(' FAIL '), msg) lastError = err.message }) recorder.add('ask for next step', askForStep) nextStep() } function askForStep() { return new Promise(resolve => { nextStep = resolve rl.setPrompt(' I.', 3) rl.resume() rl.prompt([false]) }) } function completer(line) { const I = container.support('I') const completions = methodsOfObject(I) // If no input, return all completions if (!line) { return [completions, line] } // Search using Fuse.js const searchResults = searchWithFusejs(completions, line, { threshold: 0.3, distance: 100, minMatchCharLength: 1, }) const hits = searchResults.map(result => result.item) return [hits, line] } function registerVariable(name, value) { registeredVariables[name] = value } module.exports = pause module.exports.registerVariable = registerVariable