Skip to content

Commit 8c4fb9f

Browse files
committed
export debug module
1 parent b960479 commit 8c4fb9f

File tree

8 files changed

+245
-244
lines changed

8 files changed

+245
-244
lines changed

lib/codecept.js

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -72,7 +72,7 @@ class Codecept {
7272
if (!this.config.noGlobals) {
7373
global.Helper = global.codecept_helper = require('@codeceptjs/helper')
7474
global.actor = global.codecept_actor = require('./actor')
75-
global.pause = require('./pause')
75+
global.pause = require('./debug').pause
7676
global.within = require('./within')
7777
global.session = require('./session')
7878
global.DataTable = require('./data/table')

lib/command/interactive.js

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -43,7 +43,7 @@ module.exports = async function (path, options) {
4343
break
4444
}
4545
}
46-
require('../pause')()
46+
require('../debug').pause()
4747
// recorder.catchWithoutStop((err) => console.log(err.stack));
4848
recorder.add(() => event.emit(event.test.after, {}))
4949
recorder.add(() => event.emit(event.suite.after, {}))

lib/debug.js

Lines changed: 238 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,238 @@
1+
const colors = require('chalk')
2+
const readline = require('readline')
3+
const ora = require('ora-classic')
4+
const debug = require('debug')('codeceptjs:pause')
5+
6+
const container = require('./container')
7+
const history = require('./history')
8+
const store = require('./store')
9+
const aiAssistant = require('./ai')
10+
const recorder = require('./recorder')
11+
const event = require('./event')
12+
const output = require('./output')
13+
const { methodsOfObject, searchWithFusejs } = require('./utils')
14+
15+
// npm install colors
16+
let rl
17+
let nextStep
18+
let finish
19+
let next
20+
let registeredVariables = {}
21+
/**
22+
* Pauses test execution and starts interactive shell
23+
* @param {Object<string, *>} [passedObject]
24+
*/
25+
const pause = function (passedObject = {}) {
26+
if (store.dryRun) return
27+
28+
next = false
29+
// add listener to all next steps to provide next() functionality
30+
event.dispatcher.on(event.step.after, () => {
31+
recorder.add('Start next pause session', () => {
32+
// test already finished, nothing to pause
33+
if (!store.currentTest) return
34+
if (!next) return
35+
return pauseSession()
36+
})
37+
})
38+
39+
event.dispatcher.on(event.test.finished, () => {
40+
finish()
41+
recorder.session.restore('pause')
42+
rl.close()
43+
history.save()
44+
})
45+
46+
recorder.add('Start new session', () => pauseSession(passedObject))
47+
}
48+
49+
function pauseSession(passedObject = {}) {
50+
registeredVariables = passedObject
51+
recorder.session.start('pause')
52+
if (!next) {
53+
let vars = Object.keys(registeredVariables).join(', ')
54+
if (vars) vars = `(vars: ${vars})`
55+
56+
output.print(colors.yellow(' Interactive shell started'))
57+
output.print(colors.yellow(' Use JavaScript syntax to try steps in action'))
58+
output.print(colors.yellow(` - Press ${colors.bold('ENTER')} to run the next step`))
59+
output.print(colors.yellow(` - Press ${colors.bold('TAB')} twice to see all available commands`))
60+
output.print(colors.yellow(` - Type ${colors.bold('exit')} + Enter to exit the interactive shell`))
61+
output.print(colors.yellow(` - Prefix ${colors.bold('=>')} to run js commands ${colors.bold(vars)}`))
62+
63+
if (aiAssistant.isEnabled) {
64+
output.print(colors.blue(` ${colors.bold('AI is enabled! (experimental)')} Write what you want and make AI run it`))
65+
output.print(colors.blue(' Please note, only HTML fragments with interactive elements are sent to AI provider'))
66+
output.print(colors.blue(' Ideas: ask it to fill forms for you or to click'))
67+
}
68+
}
69+
70+
rl = readline.createInterface({
71+
input: process.stdin,
72+
output: process.stdout,
73+
terminal: true,
74+
completer,
75+
history: history.load(),
76+
historySize: 50, // Adjust the history size as needed
77+
})
78+
79+
store.onPause = true
80+
rl.on('line', parseInput)
81+
rl.on('close', () => {
82+
if (!next) console.log('Exiting interactive shell....')
83+
store.onPause = false
84+
})
85+
return new Promise(resolve => {
86+
finish = resolve
87+
// eslint-disable-next-line
88+
return askForStep()
89+
})
90+
}
91+
92+
/* eslint-disable */
93+
async function parseInput(cmd) {
94+
rl.pause()
95+
next = false
96+
recorder.session.start('pause')
97+
if (cmd === '') next = true
98+
if (!cmd || cmd === 'resume' || cmd === 'exit') {
99+
finish()
100+
recorder.session.restore('pause')
101+
rl.close()
102+
history.save()
103+
return nextStep()
104+
}
105+
for (const k of Object.keys(registeredVariables)) {
106+
eval(`var ${k} = registeredVariables['${k}'];`)
107+
}
108+
109+
let executeCommand = Promise.resolve()
110+
111+
const getCmd = () => {
112+
debug('Command:', cmd)
113+
return cmd
114+
}
115+
116+
let isCustomCommand = false
117+
let lastError = null
118+
let isAiCommand = false
119+
let $res
120+
try {
121+
const locate = global.locate // enable locate in this context
122+
123+
const I = container.support('I')
124+
if (cmd.trim().startsWith('=>')) {
125+
isCustomCommand = true
126+
cmd = cmd.trim().substring(2, cmd.length)
127+
} else if (aiAssistant.isEnabled && cmd.trim() && !cmd.match(/^\w+\(/) && cmd.includes(' ')) {
128+
const currentOutputLevel = output.level()
129+
output.level(0)
130+
const res = I.grabSource()
131+
isAiCommand = true
132+
executeCommand = executeCommand.then(async () => {
133+
try {
134+
const html = await res
135+
await aiAssistant.setHtmlContext(html)
136+
} catch (err) {
137+
output.print(output.styles.error(' ERROR '), "Can't get HTML context", err.stack)
138+
return
139+
} finally {
140+
output.level(currentOutputLevel)
141+
}
142+
143+
const spinner = ora('Processing AI request...').start()
144+
cmd = await aiAssistant.writeSteps(cmd)
145+
spinner.stop()
146+
output.print('')
147+
output.print(colors.blue(aiAssistant.getResponse()))
148+
output.print('')
149+
return cmd
150+
})
151+
} else {
152+
cmd = `I.${cmd}`
153+
}
154+
executeCommand = executeCommand
155+
.then(async () => {
156+
const cmd = getCmd()
157+
if (!cmd) return
158+
return eval(cmd)
159+
})
160+
.catch(err => {
161+
debug(err)
162+
if (isAiCommand) return
163+
if (!lastError) output.print(output.styles.error(' ERROR '), err.message)
164+
debug(err.stack)
165+
166+
lastError = err.message
167+
})
168+
169+
const val = await executeCommand
170+
171+
if (isCustomCommand) {
172+
if (val !== undefined) console.log('Result', '$res=', val)
173+
$res = val
174+
}
175+
176+
if (cmd?.startsWith('I.see') || cmd?.startsWith('I.dontSee')) {
177+
output.print(output.styles.success(' OK '), cmd)
178+
}
179+
if (cmd?.startsWith('I.grab')) {
180+
output.print(output.styles.debug(val))
181+
}
182+
183+
history.push(cmd) // add command to history when successful
184+
} catch (err) {
185+
if (!lastError) output.print(output.styles.error(' ERROR '), err.message)
186+
lastError = err.message
187+
}
188+
recorder.session.catch(err => {
189+
const msg = err.cliMessage ? err.cliMessage() : err.message
190+
191+
// pop latest command from history because it failed
192+
history.pop()
193+
194+
if (isAiCommand) return
195+
if (!lastError) output.print(output.styles.error(' FAIL '), msg)
196+
lastError = err.message
197+
})
198+
recorder.add('ask for next step', askForStep)
199+
nextStep()
200+
}
201+
/* eslint-enable */
202+
203+
function askForStep() {
204+
return new Promise(resolve => {
205+
nextStep = resolve
206+
rl.setPrompt(' I.', 3)
207+
rl.resume()
208+
rl.prompt([false])
209+
})
210+
}
211+
212+
function completer(line) {
213+
const I = container.support('I')
214+
const completions = methodsOfObject(I)
215+
// If no input, return all completions
216+
if (!line) {
217+
return [completions, line]
218+
}
219+
220+
// Search using Fuse.js
221+
const searchResults = searchWithFusejs(completions, line, {
222+
threshold: 0.3,
223+
distance: 100,
224+
minMatchCharLength: 1,
225+
})
226+
const hits = searchResults.map(result => result.item)
227+
228+
return [hits, line]
229+
}
230+
231+
function registerVariable(name, value) {
232+
registeredVariables[name] = value
233+
}
234+
235+
module.exports = {
236+
pause,
237+
registerVariable,
238+
}

lib/helper/AI.js

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -8,7 +8,7 @@ const Container = require('../container')
88
const { splitByChunks, minifyHtml } = require('../html')
99
const { beautify } = require('../utils')
1010
const output = require('../output')
11-
const { registerVariable } = require('../pause')
11+
const { registerVariable } = require('../debug')
1212

1313
const gtpRole = {
1414
user: 'user',

lib/index.js

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -27,7 +27,7 @@ module.exports = {
2727
/** @type {typeof CodeceptJS.Helper} */
2828
Helper: require('./helper'),
2929
/** @type {typeof CodeceptJS.pause} */
30-
pause: require('./pause'),
30+
pause: require('./debug').pause,
3131
/** @type {typeof CodeceptJS.within} */
3232
within: require('./within'),
3333
/** @type {typeof CodeceptJS.DataTable} */
@@ -43,4 +43,4 @@ module.exports = {
4343
ai: require('./ai'),
4444

4545
Workers: require('./workers'),
46-
};
46+
}

0 commit comments

Comments
 (0)