codeceptjs 4.0.0-rc.16 → 4.0.0-rc.18

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "codeceptjs",
3
- "version": "4.0.0-rc.16",
3
+ "version": "4.0.0-rc.18",
4
4
  "type": "module",
5
5
  "description": "Supercharged End 2 End Testing Framework for NodeJS",
6
6
  "keywords": [
@@ -87,7 +87,7 @@
87
87
  "publish-beta": "./runok.cjs publish:next-beta-version"
88
88
  },
89
89
  "dependencies": {
90
- "@codeceptjs/configure": "1.0.6",
90
+ "@codeceptjs/configure": "^4.0.0-beta.4",
91
91
  "@codeceptjs/helper": "2.0.4",
92
92
  "@cucumber/cucumber-expressions": "18",
93
93
  "@cucumber/gherkin": "38.0.0",
@@ -115,7 +115,6 @@
115
115
  "html-minifier-terser": "7.2.0",
116
116
  "inquirer": "^8.2.7",
117
117
  "invisi-data": "^1.0.0",
118
- "joi": "18.0.2",
119
118
  "js-beautify": "1.15.4",
120
119
  "lodash.clonedeep": "4.5.0",
121
120
  "lodash.merge": "4.6.2",
@@ -131,14 +130,15 @@
131
130
  "promise-retry": "1.1.1",
132
131
  "resq": "1.11.0",
133
132
  "sprintf-js": "1.1.3",
134
- "uuid": "11.1.0"
133
+ "uuid": "11.1.0",
134
+ "zod": "^4.1.11"
135
135
  },
136
136
  "optionalDependencies": {
137
137
  "@codeceptjs/detox-helper": "1.1.13"
138
138
  },
139
139
  "devDependencies": {
140
140
  "@apollo/server": "^5",
141
- "@codeceptjs/expect-helper": "^1.0.2",
141
+ "@codeceptjs/expect-helper": "^4.0.0-beta.5",
142
142
  "@codeceptjs/mock-request": "0.3.1",
143
143
  "@eslint/eslintrc": "3.3.3",
144
144
  "@eslint/js": "9.39.2",
@@ -173,7 +173,7 @@
173
173
  "jsdoc-typeof-plugin": "1.0.0",
174
174
  "json-server": "0.17.4",
175
175
  "mochawesome": "^7.1.3",
176
- "playwright": "1.55.1",
176
+ "playwright": "^1.59.0",
177
177
  "prettier": "^3.3.2",
178
178
  "puppeteer": "24.36.0",
179
179
  "qrcode-terminal": "0.12.0",
@@ -4,7 +4,6 @@
4
4
  /// <reference path="./promiseBasedTypes.d.ts" />
5
5
  /// <reference types="webdriverio" />
6
6
  /// <reference path="./Mocha.d.ts" />
7
- /// <reference types="joi" />
8
7
  /// <reference types="playwright" />
9
8
 
10
9
  declare namespace CodeceptJS {
@@ -211,9 +210,6 @@ declare namespace CodeceptJS {
211
210
  */
212
211
  JSONResponse?: any
213
212
 
214
- /** Enable AI features for development purposes */
215
- AI?: any
216
-
217
213
  [key: string]: any
218
214
  }
219
215
  /**
@@ -465,7 +461,6 @@ declare namespace CodeceptJS {
465
461
  | { ios: string }
466
462
  | { android: string; ios: string }
467
463
  | { react: string }
468
- | { vue: string }
469
464
  | { shadow: string[] }
470
465
  | { custom: string }
471
466
  | { pw: string }
package/lib/helper/AI.js DELETED
@@ -1,214 +0,0 @@
1
- import HelperModule from '@codeceptjs/helper'
2
- import ora from 'ora-classic'
3
- import fs from 'fs'
4
- import path from 'path'
5
- import ai from '../ai.js'
6
- import Container from '../container.js'
7
- import { splitByChunks, minifyHtml } from '../html.js'
8
- import { beautify } from '../utils.js'
9
- import output from '../output.js'
10
- import { registerVariable } from '../pause.js'
11
-
12
- const standardActingHelpers = Container.STANDARD_ACTING_HELPERS
13
-
14
- const gtpRole = {
15
- user: 'user',
16
- }
17
-
18
- /**
19
- * AI Helper for CodeceptJS.
20
- *
21
- * This helper class provides integration with the AI GPT-3.5 or 4 language model for generating responses to questions or prompts within the context of web pages. It allows you to interact with the GPT-3.5 model to obtain intelligent responses based on HTML fragments or general prompts.
22
- * This helper should be enabled with any web helpers like Playwright or Puppeteer or WebDriver to ensure the HTML context is available.
23
- *
24
- * Use it only in development mode. It is recommended to run it only inside pause() mode.
25
- *
26
- * ## Configuration
27
- *
28
- * This helper should be configured in codecept.conf.{js|ts}
29
- *
30
- * * `chunkSize`: (optional, default: 80000) - The maximum number of characters to send to the AI API at once. We split HTML fragments by 8000 chars to not exceed token limit. Increase this value if you use GPT-4.
31
- */
32
- class AI extends Helper {
33
- constructor(config) {
34
- super(config)
35
- this.aiAssistant = ai
36
-
37
- this.options = {
38
- chunkSize: 80000,
39
- }
40
- this.options = { ...this.options, ...config }
41
- this.aiAssistant.enable(this.config)
42
- }
43
-
44
- _beforeSuite() {
45
- const helpers = Container.helpers()
46
-
47
- for (const helperName of standardActingHelpers) {
48
- if (Object.keys(helpers).indexOf(helperName) > -1) {
49
- this.helper = helpers[helperName]
50
- break
51
- }
52
- }
53
- }
54
-
55
- /**
56
- * Asks the AI GPT language model a question based on the provided prompt within the context of the current page's HTML.
57
- *
58
- * ```js
59
- * I.askGptOnPage('what does this page do?');
60
- * ```
61
- *
62
- * @async
63
- * @param {string} prompt - The question or prompt to ask the GPT model.
64
- * @returns {Promise<string>} - A Promise that resolves to the generated responses from the GPT model, joined by newlines.
65
- */
66
- async askGptOnPage(prompt) {
67
- const html = await this.helper.grabSource()
68
-
69
- const htmlChunks = splitByChunks(html, this.options.chunkSize)
70
-
71
- if (htmlChunks.length > 1) this.debug(`Splitting HTML into ${htmlChunks.length} chunks`)
72
-
73
- const responses = []
74
-
75
- for (const chunk of htmlChunks) {
76
- const messages = [
77
- { role: gtpRole.user, content: prompt },
78
- { role: gtpRole.user, content: `Within this HTML: ${await minifyHtml(chunk)}` },
79
- ]
80
-
81
- if (htmlChunks.length > 1)
82
- messages.push({
83
- role: 'user',
84
- content: 'If action is not possible on this page, do not propose anything, I will send another HTML fragment',
85
- })
86
-
87
- const response = await this._processAIRequest(messages)
88
-
89
- output.print(response)
90
-
91
- responses.push(response)
92
- }
93
-
94
- return responses.join('\n\n')
95
- }
96
-
97
- /**
98
- * Asks the AI a question based on the provided prompt within the context of a specific HTML fragment on the current page.
99
- *
100
- * ```js
101
- * I.askGptOnPageFragment('describe features of this screen', '.screen');
102
- * ```
103
- *
104
- * @async
105
- * @param {string} prompt - The question or prompt to ask the GPT-3.5 model.
106
- * @param {string} locator - The locator or selector used to identify the HTML fragment on the page.
107
- * @returns {Promise<string>} - A Promise that resolves to the generated response from the GPT model.
108
- */
109
- async askGptOnPageFragment(prompt, locator) {
110
- const html = await this.helper.grabHTMLFrom(locator)
111
-
112
- const messages = [
113
- { role: gtpRole.user, content: prompt },
114
- { role: gtpRole.user, content: `Within this HTML: ${await minifyHtml(html)}` },
115
- ]
116
-
117
- const response = await this._processAIRequest(messages)
118
-
119
- output.print(response)
120
-
121
- return response
122
- }
123
-
124
- /**
125
- * Send a general request to AI and return response.
126
- * @param {string} prompt
127
- * @returns {Promise<string>} - A Promise that resolves to the generated response from the GPT model.
128
- */
129
- async askGptGeneralPrompt(prompt) {
130
- const messages = [{ role: gtpRole.user, content: prompt }]
131
-
132
- const response = await this._processAIRequest(messages)
133
-
134
- output.print(response)
135
-
136
- return response
137
- }
138
-
139
- /**
140
- * Generates PageObject for current page using AI.
141
- *
142
- * It saves the PageObject to the output directory. You can review the page object and adjust it as needed and move to pages directory.
143
- * Prompt can be customized in a global config file.
144
- *
145
- * ```js
146
- * // create page object for whole page
147
- * I.askForPageObject('home');
148
- *
149
- * // create page object with extra prompt
150
- * I.askForPageObject('home', 'implement signIn(username, password) method');
151
- *
152
- * // create page object for a specific element
153
- * I.askForPageObject('home', null, '.detail');
154
- * ```
155
- *
156
- * Asks for a page object based on the provided page name, locator, and extra prompt.
157
- *
158
- * @async
159
- * @param {string} pageName - The name of the page to retrieve the object for.
160
- * @param {string|null} [extraPrompt=null] - An optional extra prompt for additional context or information.
161
- * @param {string|null} [locator=null] - An optional locator to find a specific element on the page.
162
- * @returns {Promise<Object>} A promise that resolves to the requested page object.
163
- */
164
- async askForPageObject(pageName, extraPrompt = null, locator = null) {
165
- const spinner = ora(' Processing AI request...').start()
166
-
167
- try {
168
- const html = locator ? await this.helper.grabHTMLFrom(locator) : await this.helper.grabSource()
169
- await this.aiAssistant.setHtmlContext(html)
170
- const response = await this.aiAssistant.generatePageObject(extraPrompt, locator)
171
- spinner.stop()
172
-
173
- if (!response[0]) {
174
- output.error('No response from AI')
175
- return ''
176
- }
177
-
178
- const code = beautify(response[0])
179
-
180
- output.print('----- Generated PageObject ----')
181
- output.print(code)
182
- output.print('-------------------------------')
183
-
184
- const fileName = path.join(output_dir, `${pageName}Page-${Date.now()}.js`)
185
-
186
- output.print(output.styles.bold(`Page object for ${pageName} is saved to ${output.styles.bold(fileName)}`))
187
- fs.writeFileSync(fileName, code)
188
-
189
- try {
190
- registerVariable('page', require(fileName))
191
- output.success('Page object registered for this session as `page` variable')
192
- output.print('Use `=>page.methodName()` in shell to run methods of page object')
193
- output.print('Use `click(page.locatorName)` to check locators of page object')
194
- } catch (err) {
195
- output.error('Error while registering page object')
196
- output.error(err.message)
197
- }
198
-
199
- return code
200
- } catch (e) {
201
- spinner.stop()
202
- throw Error(`Something went wrong! ${e.message}`)
203
- }
204
- }
205
-
206
- async _processAIRequest(messages) {
207
- const spinner = ora(' Processing AI request...').start()
208
- const response = await this.aiAssistant.createCompletion(messages)
209
- spinner.stop()
210
- return response
211
- }
212
- }
213
-
214
- export default AI
@@ -1,167 +0,0 @@
1
- import event from '../event.js'
2
- import pause from '../pause.js'
3
- import recorder from '../recorder.js'
4
- import Container from '../container.js'
5
- import output from '../output.js'
6
-
7
- const supportedHelpers = Container.STANDARD_ACTING_HELPERS
8
-
9
- /**
10
- * Pauses test execution in different modes. Unlike `pauseOnFail`, this plugin supports
11
- * multiple triggers for pausing and is controlled via CLI arguments.
12
- *
13
- * Enable it via `-p` option with a mode:
14
- *
15
- * ```
16
- * npx codeceptjs run -p pauseOn:fail
17
- * npx codeceptjs run -p pauseOn:step
18
- * npx codeceptjs run -p pauseOn:file:tests/login_test.js
19
- * npx codeceptjs run -p pauseOn:file:tests/login_test.js:43
20
- * npx codeceptjs run -p pauseOn:url:/users/*
21
- * ```
22
- *
23
- * #### Modes
24
- *
25
- * * **fail** — pause when a step fails (same as `pauseOnFail` plugin)
26
- * * **step** — pause before first step, use `next` to advance step-by-step
27
- * * **file** — pause when execution reaches a specific file (and optionally line)
28
- * * **url** — pause when the browser URL matches a pattern (supports `*` wildcards)
29
- *
30
- */
31
- export default function (config = {}) {
32
- const args = config._args || []
33
- const mode = args[0] || 'fail'
34
-
35
- switch (mode) {
36
- case 'fail':
37
- return initFailMode()
38
- case 'step':
39
- return initStepMode()
40
- case 'file':
41
- return initFileMode(args.slice(1))
42
- case 'url':
43
- return initUrlMode(args.slice(1))
44
- default:
45
- output.error(`pauseOn: unknown mode "${mode}". Available: fail, step, file, url`)
46
- }
47
- }
48
-
49
- function initFailMode() {
50
- let failed = false
51
-
52
- event.dispatcher.on(event.test.started, () => {
53
- failed = false
54
- })
55
-
56
- event.dispatcher.on(event.step.failed, () => {
57
- failed = true
58
- })
59
-
60
- event.dispatcher.on(event.test.after, () => {
61
- if (failed) pause()
62
- })
63
- }
64
-
65
- function initStepMode() {
66
- let activated = false
67
-
68
- event.dispatcher.on(event.test.before, () => {
69
- if (activated) return
70
- activated = true
71
- recorder.add('pauseOn:step', () => pause())
72
- })
73
- }
74
-
75
- function initFileMode(fileArgs) {
76
- if (fileArgs.length === 0) {
77
- output.error('pauseOn:file requires a path. Usage: -p pauseOn:file:<path>[:<line>]')
78
- return
79
- }
80
-
81
- const targetFile = fileArgs[0]
82
- const targetLine = fileArgs[1] ? parseInt(fileArgs[1], 10) : null
83
- let paused = false
84
-
85
- event.dispatcher.on(event.step.before, (step) => {
86
- if (paused) return
87
-
88
- const stepLine = step.line()
89
- if (!stepLine) return
90
-
91
- const match = parseStepLine(stepLine)
92
- if (!match) return
93
-
94
- const fileMatches = match.file.includes(targetFile) || match.file.endsWith(targetFile)
95
- if (!fileMatches) return
96
-
97
- if (targetLine !== null && match.line !== targetLine) return
98
-
99
- paused = true
100
- recorder.add('pauseOn:file', () => pause())
101
- })
102
- }
103
-
104
- function initUrlMode(urlArgs) {
105
- if (urlArgs.length === 0) {
106
- output.error('pauseOn:url requires a pattern. Usage: -p pauseOn:url:<pattern>')
107
- return
108
- }
109
-
110
- const urlPattern = urlArgs.join(':')
111
-
112
- const helpers = Container.helpers()
113
- let helper = null
114
- for (const helperName of supportedHelpers) {
115
- if (Object.keys(helpers).indexOf(helperName) > -1) {
116
- helper = helpers[helperName]
117
- }
118
- }
119
-
120
- if (!helper) {
121
- output.error('pauseOn:url requires a browser helper (Playwright, WebDriver, Puppeteer, Appium)')
122
- return
123
- }
124
-
125
- const regex = patternToRegex(urlPattern)
126
- let paused = false
127
-
128
- event.dispatcher.on(event.step.after, () => {
129
- if (paused) return
130
-
131
- recorder.add('pauseOn:url check', async () => {
132
- if (paused) return
133
- try {
134
- const currentUrl = await helper.grabCurrentUrl()
135
- if (regex.test(currentUrl)) {
136
- paused = true
137
- return pause()
138
- }
139
- } catch (err) {
140
- // page may not be loaded yet
141
- }
142
- })
143
- })
144
- }
145
-
146
- function parseStepLine(stepLine) {
147
- let line = stepLine.trim()
148
- if (line.startsWith('at ')) line = line.substring(3).trim()
149
-
150
- const lastColon = line.lastIndexOf(':')
151
- if (lastColon < 0) return null
152
- const secondLastColon = line.lastIndexOf(':', lastColon - 1)
153
- if (secondLastColon < 0) return null
154
-
155
- const file = line.substring(0, secondLastColon)
156
- const lineNum = parseInt(line.substring(secondLastColon + 1, lastColon), 10)
157
-
158
- if (isNaN(lineNum)) return null
159
-
160
- return { file, line: lineNum }
161
- }
162
-
163
- function patternToRegex(pattern) {
164
- const escaped = pattern.replace(/[.+?^${}()|[\]\\]/g, '\\$&')
165
- const regexStr = escaped.replace(/\*/g, '.*')
166
- return new RegExp(regexStr)
167
- }