From 925467b27d18567c7f6cec9b1302a85a61c75b1d Mon Sep 17 00:00:00 2001 From: DavertMik Date: Tue, 7 Jan 2025 05:38:44 +0200 Subject: [PATCH] better view for pause mode --- lib/helper/Playwright.js | 37 +++--- lib/pause.js | 258 +++++++++++++++++++-------------------- lib/store.js | 3 +- 3 files changed, 149 insertions(+), 149 deletions(-) diff --git a/lib/helper/Playwright.js b/lib/helper/Playwright.js index 94ab68958..bdc2729e7 100644 --- a/lib/helper/Playwright.js +++ b/lib/helper/Playwright.js @@ -1255,28 +1255,14 @@ class Playwright extends Helper { if (store.debugMode) { const previewElements = els.slice(0, 3) - let htmls = await Promise.all( - previewElements.map(async el => { - const html = await el.evaluate(node => node.outerHTML) - return ( - html - .replace(/\n/g, '') - .replace(/\s+/g, ' ') - .substring(0, 100 / previewElements.length) - .trim() + '...' - ) - }), - ) - if (els.length > 3) { - htmls.push('...') - } - + let htmls = await Promise.all(previewElements.map(el => elToString(el, previewElements.length))) + if (els.length > 3) htmls.push('...') if (els.length > 1) { this.debugSection(`Elements (${els.length})`, htmls.join('|').trim()) } else if (els.length === 1) { this.debugSection('Element', htmls.join('|').trim()) } else { - this.debug('No elements found') + this.debug(`No elements found by ${JSON.stringify(locator).slice(0, 50)}....`) } } @@ -1734,6 +1720,7 @@ class Playwright extends Helper { const el = els[0] await el.clear() + if (store.debugMode) this.debugSection('Focused', await elToString(el, 1)) await highlightActiveElement.call(this, el) @@ -3444,6 +3431,7 @@ async function proceedClick(locator, context = null, options = {}) { } await highlightActiveElement.call(this, els[0]) + if (store.debugMode) this.debugSection('Clicked', await elToString(els[0], 1)) /* using the force true options itself but instead dispatching a click @@ -3877,11 +3865,22 @@ async function saveTraceForContext(context, name) { } async function highlightActiveElement(element) { - if (this.options.highlightElement && global.debugMode) { + if ((this.options.highlightElement || store.onPause) && store.debugMode) { await element.evaluate(el => { const prevStyle = el.style.boxShadow - el.style.boxShadow = '0px 0px 4px 3px rgba(255, 0, 0, 0.7)' + el.style.boxShadow = '0px 0px 4px 3px rgba(147, 51, 234, 0.8)' // Bright purple that works on both dark/light modes setTimeout(() => (el.style.boxShadow = prevStyle), 2000) }) } } + +async function elToString(el, numberOfElements) { + const html = await el.evaluate(node => node.outerHTML) + return ( + html + .replace(/\n/g, '') + .replace(/\s+/g, ' ') + .substring(0, 100 / numberOfElements) + .trim() + '...' + ) +} diff --git a/lib/pause.js b/lib/pause.js index 7f5830480..aa666c013 100644 --- a/lib/pause.js +++ b/lib/pause.js @@ -1,59 +1,59 @@ -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 } = require('./utils'); +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 } = require('./utils') // npm install colors -let rl; -let nextStep; -let finish; -let next; -let registeredVariables = {}; +let rl +let nextStep +let finish +let next +let registeredVariables = {} /** * Pauses test execution and starts interactive shell * @param {Object} [passedObject] */ const pause = function (passedObject = {}) { - if (store.dryRun) return; + if (store.dryRun) return - next = false; + next = false // add listener to all next steps to provide next() functionality event.dispatcher.on(event.step.after, () => { recorder.add('Start next pause session', () => { - if (!next) return; - return pauseSession(); - }); - }); - recorder.add('Start new session', () => pauseSession(passedObject)); -}; + if (!next) return + return pauseSession() + }) + }) + recorder.add('Start new session', () => pauseSession(passedObject)) +} function pauseSession(passedObject = {}) { - registeredVariables = passedObject; - recorder.session.start('pause'); + registeredVariables = passedObject + recorder.session.start('pause') if (!next) { - let vars = Object.keys(registeredVariables).join(', '); - if (vars) vars = `(vars: ${vars})`; + 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)}`)); + 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')); + 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')) } } @@ -64,155 +64,155 @@ function pauseSession(passedObject = {}) { completer, history: history.load(), historySize: 50, // Adjust the history size as needed - }); - - rl.on('line', parseInput); + }) + store.onPause = true + rl.on('line', parseInput) rl.on('close', () => { - if (!next) console.log('Exiting interactive shell....'); - }); + store.onPause = false + if (!next) console.log('Exiting interactive shell....') + }) return new Promise(resolve => { - finish = resolve; + finish = resolve // eslint-disable-next-line - return askForStep(); - }); + return askForStep() + }) } /* eslint-disable */ async function parseInput(cmd) { - rl.pause(); - next = false; - recorder.session.start('pause'); - if (cmd === '') next = true; + rl.pause() + next = false + recorder.session.start('pause') + if (cmd === '') next = true if (!cmd || cmd === 'resume' || cmd === 'exit') { - finish(); - recorder.session.restore(); - rl.close(); - history.save(); - return nextStep(); + finish() + recorder.session.restore() + rl.close() + history.save() + return nextStep() } for (const k of Object.keys(registeredVariables)) { - eval(`var ${k} = registeredVariables['${k}'];`); + eval(`var ${k} = registeredVariables['${k}'];`) } - let executeCommand = Promise.resolve(); + 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 + debug('Command:', cmd) + return cmd + } - const I = container.support('I'); + 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); + 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; + 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); + const html = await res + await aiAssistant.setHtmlContext(html) } catch (err) { - output.print(output.styles.error(' ERROR '), "Can't get HTML context", err.stack); - return; + output.print(output.styles.error(' ERROR '), "Can't get HTML context", err.stack) + return } finally { - output.level(currentOutputLevel); + 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; - }); + 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}`; + cmd = `I.${cmd}` } executeCommand = executeCommand .then(async () => { - const cmd = getCmd(); - if (!cmd) return; - return eval(cmd); + 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); + debug(err) + if (isAiCommand) return + if (!lastError) output.print(output.styles.error(' ERROR '), err.message) + debug(err.stack) - lastError = err.message; - }); + lastError = err.message + }) - const val = await executeCommand; + const val = await executeCommand if (isCustomCommand) { - if (val !== undefined) console.log('Result', '$res=', val); - $res = val; + 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); + output.print(output.styles.success(' OK '), cmd) } if (cmd?.startsWith('I.grab')) { - output.print(output.styles.debug(val)); + output.print(output.styles.debug(val)) } - history.push(cmd); // add command to history when successful + history.push(cmd) // add command to history when successful } catch (err) { - if (!lastError) output.print(output.styles.error(' ERROR '), err.message); - lastError = err.message; + 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; + 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(); + 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() } /* eslint-enable */ function askForStep() { return new Promise(resolve => { - nextStep = resolve; - rl.setPrompt(' I.', 3); - rl.resume(); - rl.prompt([false]); - }); + nextStep = resolve + rl.setPrompt(' I.', 3) + rl.resume() + rl.prompt([false]) + }) } function completer(line) { - const I = container.support('I'); - const completions = methodsOfObject(I); + const I = container.support('I') + const completions = methodsOfObject(I) const hits = completions.filter(c => { if (c.indexOf(line) === 0) { - return c; + return c } - return null; - }); - return [hits && hits.length ? hits : completions, line]; + return null + }) + return [hits && hits.length ? hits : completions, line] } function registerVariable(name, value) { - registeredVariables[name] = value; + registeredVariables[name] = value } -module.exports = pause; +module.exports = pause -module.exports.registerVariable = registerVariable; +module.exports.registerVariable = registerVariable diff --git a/lib/store.js b/lib/store.js index 7dfa03df8..9a29577d6 100644 --- a/lib/store.js +++ b/lib/store.js @@ -9,7 +9,8 @@ const store = { timeouts: true, /** @type {boolean} */ dryRun: false, - + /** @type {boolean} */ + onPause: false, /** @type {CodeceptJS.Test | null} */ currentTest: null, }