codeceptjs 4.0.0-rc.18 → 4.0.0-rc.19
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/bin/codecept.js +5 -1
- package/bin/codeceptq.js +49 -0
- package/bin/mcp-server.js +250 -82
- package/docs/advanced.md +201 -0
- package/docs/agents.md +159 -0
- package/docs/ai.md +537 -0
- package/docs/aitrace.md +266 -0
- package/docs/api.md +332 -0
- package/docs/assertions.md +415 -0
- package/docs/auth.md +318 -0
- package/docs/basics.md +424 -0
- package/docs/bdd.md +539 -0
- package/docs/best.md +240 -0
- package/docs/bootstrap.md +132 -0
- package/docs/commands.md +352 -0
- package/docs/community-helpers.md +63 -0
- package/docs/configuration.md +230 -0
- package/docs/continuous-integration.md +497 -0
- package/docs/custom-helpers.md +297 -0
- package/docs/data.md +448 -0
- package/docs/debugging.md +332 -0
- package/docs/detox.md +235 -0
- package/docs/docker.md +136 -0
- package/docs/effects.md +179 -0
- package/docs/element-based-testing.md +295 -0
- package/docs/element-selection.md +125 -0
- package/docs/els.md +328 -0
- package/docs/examples.md +161 -0
- package/docs/heal.md +213 -0
- package/docs/helpers/ApiDataFactory.md +267 -0
- package/docs/helpers/Appium.md +1405 -0
- package/docs/helpers/Detox.md +665 -0
- package/docs/helpers/ExpectHelper.md +275 -0
- package/docs/helpers/FileSystem.md +152 -0
- package/docs/helpers/GraphQL.md +152 -0
- package/docs/helpers/GraphQLDataFactory.md +226 -0
- package/docs/helpers/JSONResponse.md +255 -0
- package/docs/helpers/Mochawesome.md +8 -0
- package/docs/helpers/MockRequest.md +377 -0
- package/docs/helpers/MockServer.md +212 -0
- package/docs/helpers/Playwright.md +2969 -0
- package/docs/helpers/Polly.md +44 -0
- package/docs/helpers/Protractor.md +1769 -0
- package/docs/helpers/Puppeteer-firefox.md +86 -0
- package/docs/helpers/Puppeteer.md +2690 -0
- package/docs/helpers/REST.md +289 -0
- package/docs/helpers/SoftExpectHelper.md +352 -0
- package/docs/helpers/WebDriver.md +2682 -0
- package/docs/hooks.md +339 -0
- package/docs/index.md +111 -0
- package/docs/installation.md +83 -0
- package/docs/internal-api.md +265 -0
- package/docs/internal-test-server.md +89 -0
- package/docs/locators.md +355 -0
- package/docs/mcp.md +485 -0
- package/docs/migration-4.md +556 -0
- package/docs/mobile.md +338 -0
- package/docs/pageobjects.md +399 -0
- package/docs/parallel.md +585 -0
- package/docs/playwright.md +714 -0
- package/docs/plugins.md +866 -0
- package/docs/puppeteer.md +314 -0
- package/docs/quickstart.md +120 -0
- package/docs/react.md +70 -0
- package/docs/reports.md +483 -0
- package/docs/retry.md +274 -0
- package/docs/secrets.md +150 -0
- package/docs/sessions.md +80 -0
- package/docs/shadow.md +68 -0
- package/docs/test-structure.md +275 -0
- package/docs/timeouts.md +183 -0
- package/docs/translation.md +247 -0
- package/docs/tutorial.md +271 -0
- package/docs/typescript.md +374 -0
- package/docs/web-element.md +251 -0
- package/docs/webdriver.md +708 -0
- package/docs/within.md +55 -0
- package/lib/command/dryRun.js +9 -3
- package/lib/command/init.js +247 -266
- package/lib/command/query.js +218 -0
- package/lib/config.js +9 -0
- package/lib/element/WebElement.js +37 -0
- package/lib/globals.js +11 -10
- package/lib/helper/Playwright.js +4 -1
- package/lib/html.js +3 -0
- package/lib/index.js +9 -1
- package/lib/locator.js +2 -2
- package/lib/mocha/factory.js +5 -1
- package/lib/mocha/inject.js +1 -1
- package/lib/parser.js +2 -2
- package/lib/plugin/browser.js +2 -1
- package/lib/plugin/expose.js +159 -0
- package/lib/workers.js +1 -15
- package/package.json +7 -5
- package/docs/webapi/amOnPage.mustache +0 -11
- package/docs/webapi/appendField.mustache +0 -16
- package/docs/webapi/attachFile.mustache +0 -24
- package/docs/webapi/blur.mustache +0 -18
- package/docs/webapi/checkOption.mustache +0 -13
- package/docs/webapi/clearCookie.mustache +0 -9
- package/docs/webapi/clearField.mustache +0 -14
- package/docs/webapi/click.mustache +0 -29
- package/docs/webapi/clickLink.mustache +0 -8
- package/docs/webapi/closeCurrentTab.mustache +0 -7
- package/docs/webapi/closeOtherTabs.mustache +0 -8
- package/docs/webapi/dontSee.mustache +0 -11
- package/docs/webapi/dontSeeCheckboxIsChecked.mustache +0 -10
- package/docs/webapi/dontSeeCookie.mustache +0 -8
- package/docs/webapi/dontSeeCurrentPathEquals.mustache +0 -10
- package/docs/webapi/dontSeeCurrentUrlEquals.mustache +0 -10
- package/docs/webapi/dontSeeElement.mustache +0 -12
- package/docs/webapi/dontSeeElementInDOM.mustache +0 -8
- package/docs/webapi/dontSeeInCurrentUrl.mustache +0 -4
- package/docs/webapi/dontSeeInField.mustache +0 -16
- package/docs/webapi/dontSeeInSource.mustache +0 -8
- package/docs/webapi/dontSeeInTitle.mustache +0 -8
- package/docs/webapi/dontSeeTraffic.mustache +0 -13
- package/docs/webapi/doubleClick.mustache +0 -13
- package/docs/webapi/downloadFile.mustache +0 -12
- package/docs/webapi/dragAndDrop.mustache +0 -9
- package/docs/webapi/dragSlider.mustache +0 -11
- package/docs/webapi/executeAsyncScript.mustache +0 -24
- package/docs/webapi/executeScript.mustache +0 -26
- package/docs/webapi/fillField.mustache +0 -21
- package/docs/webapi/flushNetworkTraffics.mustache +0 -5
- package/docs/webapi/focus.mustache +0 -13
- package/docs/webapi/forceClick.mustache +0 -28
- package/docs/webapi/forceRightClick.mustache +0 -18
- package/docs/webapi/grabAllWindowHandles.mustache +0 -7
- package/docs/webapi/grabAttributeFrom.mustache +0 -10
- package/docs/webapi/grabAttributeFromAll.mustache +0 -9
- package/docs/webapi/grabBrowserLogs.mustache +0 -9
- package/docs/webapi/grabCookie.mustache +0 -11
- package/docs/webapi/grabCssPropertyFrom.mustache +0 -11
- package/docs/webapi/grabCssPropertyFromAll.mustache +0 -10
- package/docs/webapi/grabCurrentUrl.mustache +0 -9
- package/docs/webapi/grabCurrentWindowHandle.mustache +0 -6
- package/docs/webapi/grabDataFromPerformanceTiming.mustache +0 -20
- package/docs/webapi/grabElementBoundingRect.mustache +0 -20
- package/docs/webapi/grabGeoLocation.mustache +0 -8
- package/docs/webapi/grabHTMLFrom.mustache +0 -10
- package/docs/webapi/grabHTMLFromAll.mustache +0 -9
- package/docs/webapi/grabNumberOfOpenTabs.mustache +0 -8
- package/docs/webapi/grabNumberOfVisibleElements.mustache +0 -9
- package/docs/webapi/grabPageScrollPosition.mustache +0 -8
- package/docs/webapi/grabPopupText.mustache +0 -5
- package/docs/webapi/grabRecordedNetworkTraffics.mustache +0 -10
- package/docs/webapi/grabSource.mustache +0 -8
- package/docs/webapi/grabTextFrom.mustache +0 -10
- package/docs/webapi/grabTextFromAll.mustache +0 -9
- package/docs/webapi/grabTitle.mustache +0 -8
- package/docs/webapi/grabValueFrom.mustache +0 -9
- package/docs/webapi/grabValueFromAll.mustache +0 -8
- package/docs/webapi/grabWebElement.mustache +0 -9
- package/docs/webapi/grabWebElements.mustache +0 -9
- package/docs/webapi/moveCursorTo.mustache +0 -16
- package/docs/webapi/openNewTab.mustache +0 -7
- package/docs/webapi/pressKey.mustache +0 -12
- package/docs/webapi/pressKeyDown.mustache +0 -12
- package/docs/webapi/pressKeyUp.mustache +0 -12
- package/docs/webapi/pressKeyWithKeyNormalization.mustache +0 -60
- package/docs/webapi/refreshPage.mustache +0 -6
- package/docs/webapi/resizeWindow.mustache +0 -6
- package/docs/webapi/rightClick.mustache +0 -14
- package/docs/webapi/saveElementScreenshot.mustache +0 -10
- package/docs/webapi/saveScreenshot.mustache +0 -12
- package/docs/webapi/say.mustache +0 -10
- package/docs/webapi/scrollIntoView.mustache +0 -11
- package/docs/webapi/scrollPageToBottom.mustache +0 -6
- package/docs/webapi/scrollPageToTop.mustache +0 -6
- package/docs/webapi/scrollTo.mustache +0 -12
- package/docs/webapi/see.mustache +0 -11
- package/docs/webapi/seeAttributesOnElements.mustache +0 -9
- package/docs/webapi/seeCheckboxIsChecked.mustache +0 -10
- package/docs/webapi/seeCookie.mustache +0 -8
- package/docs/webapi/seeCssPropertiesOnElements.mustache +0 -9
- package/docs/webapi/seeCurrentPathEquals.mustache +0 -10
- package/docs/webapi/seeCurrentUrlEquals.mustache +0 -11
- package/docs/webapi/seeElement.mustache +0 -12
- package/docs/webapi/seeElementInDOM.mustache +0 -8
- package/docs/webapi/seeFileDownloaded.mustache +0 -23
- package/docs/webapi/seeInCurrentUrl.mustache +0 -8
- package/docs/webapi/seeInField.mustache +0 -17
- package/docs/webapi/seeInPopup.mustache +0 -8
- package/docs/webapi/seeInSource.mustache +0 -7
- package/docs/webapi/seeInTitle.mustache +0 -8
- package/docs/webapi/seeNumberOfElements.mustache +0 -11
- package/docs/webapi/seeNumberOfVisibleElements.mustache +0 -10
- package/docs/webapi/seeTextEquals.mustache +0 -9
- package/docs/webapi/seeTitleEquals.mustache +0 -8
- package/docs/webapi/seeTraffic.mustache +0 -36
- package/docs/webapi/selectOption.mustache +0 -26
- package/docs/webapi/setCookie.mustache +0 -16
- package/docs/webapi/setGeoLocation.mustache +0 -12
- package/docs/webapi/startRecordingTraffic.mustache +0 -8
- package/docs/webapi/startRecordingWebSocketMessages.mustache +0 -8
- package/docs/webapi/stopRecordingTraffic.mustache +0 -5
- package/docs/webapi/stopRecordingWebSocketMessages.mustache +0 -7
- package/docs/webapi/switchTo.mustache +0 -9
- package/docs/webapi/switchToNextTab.mustache +0 -10
- package/docs/webapi/switchToPreviousTab.mustache +0 -10
- package/docs/webapi/type.mustache +0 -21
- package/docs/webapi/uncheckOption.mustache +0 -13
- package/docs/webapi/wait.mustache +0 -8
- package/docs/webapi/waitForClickable.mustache +0 -11
- package/docs/webapi/waitForCookie.mustache +0 -9
- package/docs/webapi/waitForDetached.mustache +0 -10
- package/docs/webapi/waitForDisabled.mustache +0 -6
- package/docs/webapi/waitForElement.mustache +0 -11
- package/docs/webapi/waitForEnabled.mustache +0 -6
- package/docs/webapi/waitForFunction.mustache +0 -17
- package/docs/webapi/waitForInvisible.mustache +0 -10
- package/docs/webapi/waitForNumberOfTabs.mustache +0 -9
- package/docs/webapi/waitForText.mustache +0 -13
- package/docs/webapi/waitForValue.mustache +0 -10
- package/docs/webapi/waitForVisible.mustache +0 -10
- package/docs/webapi/waitInUrl.mustache +0 -9
- package/docs/webapi/waitNumberOfVisibleElements.mustache +0 -10
- package/docs/webapi/waitToHide.mustache +0 -10
- package/docs/webapi/waitUrlEquals.mustache +0 -10
|
@@ -0,0 +1,218 @@
|
|
|
1
|
+
import fs from 'fs'
|
|
2
|
+
import * as parse5 from 'parse5'
|
|
3
|
+
import { DOMImplementation, XMLSerializer } from '@xmldom/xmldom'
|
|
4
|
+
import xpath from 'xpath'
|
|
5
|
+
import Locator from '../locator.js'
|
|
6
|
+
import { xpathLocator } from '../utils.js'
|
|
7
|
+
|
|
8
|
+
export default async function query(locator, context, options = {}) {
|
|
9
|
+
const html = options.file ? fs.readFileSync(options.file, 'utf8') : await readStdin()
|
|
10
|
+
|
|
11
|
+
if (!html || !html.trim()) {
|
|
12
|
+
console.error('codeceptq: no HTML input. Pipe HTML via stdin or use --file <path>.')
|
|
13
|
+
process.exitCode = 2
|
|
14
|
+
return
|
|
15
|
+
}
|
|
16
|
+
|
|
17
|
+
let xpathExpr
|
|
18
|
+
let contextExpr = null
|
|
19
|
+
try {
|
|
20
|
+
xpathExpr = buildXPath(locator, options)
|
|
21
|
+
if (context) contextExpr = buildXPath(context, {})
|
|
22
|
+
} catch (err) {
|
|
23
|
+
console.error(`codeceptq: cannot build XPath: ${err.message}`)
|
|
24
|
+
process.exitCode = 2
|
|
25
|
+
return
|
|
26
|
+
}
|
|
27
|
+
|
|
28
|
+
const { doc, source } = htmlToDoc(html)
|
|
29
|
+
|
|
30
|
+
let nodes
|
|
31
|
+
try {
|
|
32
|
+
if (contextExpr) {
|
|
33
|
+
const ctxNodes = toArray(xpath.select(contextExpr, doc))
|
|
34
|
+
const seen = new Set()
|
|
35
|
+
nodes = []
|
|
36
|
+
for (const ctx of ctxNodes) {
|
|
37
|
+
for (const m of toArray(xpath.select(xpathExpr, ctx))) {
|
|
38
|
+
if (!seen.has(m)) {
|
|
39
|
+
seen.add(m)
|
|
40
|
+
nodes.push(m)
|
|
41
|
+
}
|
|
42
|
+
}
|
|
43
|
+
}
|
|
44
|
+
} else {
|
|
45
|
+
nodes = toArray(xpath.select(xpathExpr, doc))
|
|
46
|
+
}
|
|
47
|
+
} catch (err) {
|
|
48
|
+
console.error(`codeceptq: XPath evaluation failed for "${xpathExpr}": ${err.message}`)
|
|
49
|
+
process.exitCode = 2
|
|
50
|
+
return
|
|
51
|
+
}
|
|
52
|
+
|
|
53
|
+
const limit = parseInt(options.limit, 10) || 20
|
|
54
|
+
const snippetLen = parseInt(options.snippet, 10) || 500
|
|
55
|
+
const truncated = nodes.slice(0, limit)
|
|
56
|
+
const where = options.file || 'stdin'
|
|
57
|
+
|
|
58
|
+
if (options.json) {
|
|
59
|
+
process.stdout.write(
|
|
60
|
+
JSON.stringify(
|
|
61
|
+
{
|
|
62
|
+
locator,
|
|
63
|
+
context: context || null,
|
|
64
|
+
xpath: xpathExpr,
|
|
65
|
+
contextXPath: contextExpr,
|
|
66
|
+
source: where,
|
|
67
|
+
total: nodes.length,
|
|
68
|
+
shown: truncated.length,
|
|
69
|
+
matches: truncated.map(n => ({
|
|
70
|
+
line: n.__line ?? null,
|
|
71
|
+
snippet: renderSnippet(n, source, snippetLen, options.full),
|
|
72
|
+
})),
|
|
73
|
+
},
|
|
74
|
+
null,
|
|
75
|
+
2,
|
|
76
|
+
) + '\n',
|
|
77
|
+
)
|
|
78
|
+
} else {
|
|
79
|
+
if (nodes.length === 0) {
|
|
80
|
+
console.log(`No matches for ${quote(locator)}${context ? ` within ${quote(context)}` : ''} in ${where}`)
|
|
81
|
+
console.log(`(xpath: ${xpathExpr})`)
|
|
82
|
+
} else {
|
|
83
|
+
const noun = nodes.length === 1 ? 'match' : 'matches'
|
|
84
|
+
const more = nodes.length > truncated.length ? ` (showing first ${truncated.length})` : ''
|
|
85
|
+
console.log(`${nodes.length} ${noun} for ${quote(locator)}${context ? ` within ${quote(context)}` : ''} in ${where}${more}`)
|
|
86
|
+
console.log()
|
|
87
|
+
truncated.forEach((node, i) => {
|
|
88
|
+
const line = node.__line ?? '?'
|
|
89
|
+
console.log(`${i + 1}. Line ${line}`)
|
|
90
|
+
const snippet = renderSnippet(node, source, snippetLen, options.full)
|
|
91
|
+
snippet.split('\n').forEach(l => console.log(' ' + l))
|
|
92
|
+
console.log()
|
|
93
|
+
})
|
|
94
|
+
}
|
|
95
|
+
}
|
|
96
|
+
|
|
97
|
+
if (nodes.length === 0) process.exitCode = 1
|
|
98
|
+
}
|
|
99
|
+
|
|
100
|
+
function buildXPath(input, options) {
|
|
101
|
+
const literal = xpathLocator.literal(input)
|
|
102
|
+
if (options.field) return Locator.field.byText(literal)
|
|
103
|
+
if (options.click || options.clickable) return Locator.clickable.wide(literal)
|
|
104
|
+
if (options.checkable) return Locator.checkable.byText(literal)
|
|
105
|
+
if (options.select) {
|
|
106
|
+
return Locator.select.byVisibleText(literal).replace(/\.\/(option|optgroup)/g, './/$1')
|
|
107
|
+
}
|
|
108
|
+
|
|
109
|
+
if (options.xpath) return new Locator({ xpath: input }).toXPath()
|
|
110
|
+
if (options.css) return new Locator({ css: input }).toXPath()
|
|
111
|
+
|
|
112
|
+
const loc = new Locator(input)
|
|
113
|
+
if (loc.type === 'fuzzy') {
|
|
114
|
+
return xpathLocator.combine([Locator.clickable.wide(literal), Locator.field.byText(literal)])
|
|
115
|
+
}
|
|
116
|
+
return loc.toXPath()
|
|
117
|
+
}
|
|
118
|
+
|
|
119
|
+
function htmlToDoc(html) {
|
|
120
|
+
const p5doc = parse5.parse(html, { sourceCodeLocationInfo: true })
|
|
121
|
+
const impl = new DOMImplementation()
|
|
122
|
+
const doc = impl.createDocument(null, null, null)
|
|
123
|
+
walkParse5(p5doc, doc, doc)
|
|
124
|
+
return { doc, source: html }
|
|
125
|
+
}
|
|
126
|
+
|
|
127
|
+
function walkParse5(p5node, xmlParent, xmlDoc) {
|
|
128
|
+
for (const child of p5node.childNodes || []) {
|
|
129
|
+
const name = child.nodeName
|
|
130
|
+
if (name === '#text') {
|
|
131
|
+
if (child.value != null) {
|
|
132
|
+
const t = xmlDoc.createTextNode(child.value)
|
|
133
|
+
if (child.sourceCodeLocation) t.__line = child.sourceCodeLocation.startLine
|
|
134
|
+
xmlParent.appendChild(t)
|
|
135
|
+
}
|
|
136
|
+
} else if (name === '#comment') {
|
|
137
|
+
try {
|
|
138
|
+
xmlParent.appendChild(xmlDoc.createComment(child.data || ''))
|
|
139
|
+
} catch {
|
|
140
|
+
// ignore comments xmldom rejects
|
|
141
|
+
}
|
|
142
|
+
} else if (name === '#documentType') {
|
|
143
|
+
// skip doctype
|
|
144
|
+
} else {
|
|
145
|
+
const tagName = child.tagName || name
|
|
146
|
+
let el
|
|
147
|
+
try {
|
|
148
|
+
el = xmlDoc.createElement(tagName)
|
|
149
|
+
} catch {
|
|
150
|
+
continue
|
|
151
|
+
}
|
|
152
|
+
for (const attr of child.attrs || []) {
|
|
153
|
+
try {
|
|
154
|
+
el.setAttribute(attr.name, attr.value)
|
|
155
|
+
} catch {
|
|
156
|
+
// ignore attrs xmldom rejects (namespaces, invalid names)
|
|
157
|
+
}
|
|
158
|
+
}
|
|
159
|
+
const loc = child.sourceCodeLocation
|
|
160
|
+
if (loc) {
|
|
161
|
+
el.__line = loc.startLine
|
|
162
|
+
el.__startOffset = loc.startOffset
|
|
163
|
+
el.__endOffset = loc.endOffset
|
|
164
|
+
el.__startTagEndOffset = loc.startTag ? loc.startTag.endOffset : loc.endOffset
|
|
165
|
+
}
|
|
166
|
+
xmlParent.appendChild(el)
|
|
167
|
+
walkParse5(child, el, xmlDoc)
|
|
168
|
+
}
|
|
169
|
+
}
|
|
170
|
+
}
|
|
171
|
+
|
|
172
|
+
function renderSnippet(node, source, snippetLen, full) {
|
|
173
|
+
if (typeof node.__startOffset !== 'number') {
|
|
174
|
+
try {
|
|
175
|
+
return new XMLSerializer().serializeToString(node)
|
|
176
|
+
} catch {
|
|
177
|
+
return `<${node.nodeName || '?'}>`
|
|
178
|
+
}
|
|
179
|
+
}
|
|
180
|
+
const start = node.__startOffset
|
|
181
|
+
const end = node.__endOffset ?? start
|
|
182
|
+
if (full) return source.slice(start, end)
|
|
183
|
+
|
|
184
|
+
const tagEnd = node.__startTagEndOffset ?? end
|
|
185
|
+
const openingTag = source.slice(start, tagEnd)
|
|
186
|
+
if (end <= tagEnd) return openingTag
|
|
187
|
+
|
|
188
|
+
const totalLen = end - start
|
|
189
|
+
if (totalLen <= snippetLen) return source.slice(start, end)
|
|
190
|
+
|
|
191
|
+
const remaining = Math.max(0, snippetLen - openingTag.length)
|
|
192
|
+
if (remaining < 20) return openingTag + ' …'
|
|
193
|
+
return openingTag + source.slice(tagEnd, tagEnd + remaining) + ' …'
|
|
194
|
+
}
|
|
195
|
+
|
|
196
|
+
function readStdin() {
|
|
197
|
+
return new Promise((resolve, reject) => {
|
|
198
|
+
if (process.stdin.isTTY) {
|
|
199
|
+
resolve('')
|
|
200
|
+
return
|
|
201
|
+
}
|
|
202
|
+
let data = ''
|
|
203
|
+
process.stdin.setEncoding('utf8')
|
|
204
|
+
process.stdin.on('data', chunk => (data += chunk))
|
|
205
|
+
process.stdin.on('end', () => resolve(data))
|
|
206
|
+
process.stdin.on('error', reject)
|
|
207
|
+
})
|
|
208
|
+
}
|
|
209
|
+
|
|
210
|
+
function toArray(v) {
|
|
211
|
+
if (Array.isArray(v)) return v
|
|
212
|
+
if (v == null || v === '' || typeof v === 'boolean' || typeof v === 'number') return []
|
|
213
|
+
return [v]
|
|
214
|
+
}
|
|
215
|
+
|
|
216
|
+
function quote(s) {
|
|
217
|
+
return `'${String(s).replace(/'/g, "\\'")}'`
|
|
218
|
+
}
|
package/lib/config.js
CHANGED
|
@@ -214,6 +214,15 @@ async function loadConfigFile(configFile) {
|
|
|
214
214
|
const require = createRequire(import.meta.url)
|
|
215
215
|
const extensionName = path.extname(configFile)
|
|
216
216
|
|
|
217
|
+
// Populate the in-process registry that packages like @codeceptjs/configure
|
|
218
|
+
// look up at config-import time (their proxies throw if `globalThis.codeceptjs`
|
|
219
|
+
// is missing). initCodeceptGlobals sets this too, but only later during
|
|
220
|
+
// bootstrap — config files are imported here first.
|
|
221
|
+
if (!globalThis.codeceptjs) {
|
|
222
|
+
const indexModule = await import('./index.js')
|
|
223
|
+
globalThis.codeceptjs = indexModule.default || indexModule
|
|
224
|
+
}
|
|
225
|
+
|
|
217
226
|
// .conf.js config file
|
|
218
227
|
if (extensionName === '.js' || extensionName === '.ts' || extensionName === '.cjs') {
|
|
219
228
|
let configModule
|
|
@@ -513,6 +513,43 @@ class WebElement {
|
|
|
513
513
|
return simplifyHtmlElement(outerHTML, maxLength)
|
|
514
514
|
}
|
|
515
515
|
|
|
516
|
+
/**
|
|
517
|
+
* Plain-object snapshot of the element — text, simplified HTML, visibility,
|
|
518
|
+
* enabled state, and a curated set of attributes. Each underlying call is
|
|
519
|
+
* isolated so a single failure (e.g. detached element) doesn't poison the
|
|
520
|
+
* rest. Suitable for JSON.stringify, log output, MCP tool responses.
|
|
521
|
+
*
|
|
522
|
+
* @param {object} [opts]
|
|
523
|
+
* @param {number} [opts.maxHtmlLength=300] passed through to toSimplifiedHTML
|
|
524
|
+
* @param {string[]} [opts.attrs] attribute names to surface
|
|
525
|
+
* @returns {Promise<{text?: string, html?: string, visible?: boolean, enabled?: boolean, attrs?: object}>}
|
|
526
|
+
*/
|
|
527
|
+
async describe({ maxHtmlLength = 300, attrs = ['id', 'class', 'name', 'role', 'type', 'href', 'value', 'aria-label', 'placeholder', 'data-testid'] } = {}) {
|
|
528
|
+
const out = {}
|
|
529
|
+
await Promise.all([
|
|
530
|
+
this.toSimplifiedHTML(maxHtmlLength).then(v => { if (v) out.html = v }, () => {}),
|
|
531
|
+
this.getText().then(v => { const t = v?.trim(); if (t) out.text = t }, () => {}),
|
|
532
|
+
this.isVisible().then(v => { out.visible = v }, () => {}),
|
|
533
|
+
this.isEnabled().then(v => { out.enabled = v }, () => {}),
|
|
534
|
+
])
|
|
535
|
+
const collected = {}
|
|
536
|
+
await Promise.all(attrs.map(async name => {
|
|
537
|
+
try {
|
|
538
|
+
const v = await this.getAttribute(name)
|
|
539
|
+
if (v != null && v !== '') collected[name] = v
|
|
540
|
+
} catch {}
|
|
541
|
+
}))
|
|
542
|
+
if (Object.keys(collected).length) out.attrs = collected
|
|
543
|
+
return out
|
|
544
|
+
}
|
|
545
|
+
|
|
546
|
+
// Make accidental JSON.stringify (e.g. returning a WebElement from MCP run_code)
|
|
547
|
+
// produce a usable hint instead of `{}` — the underlying handle isn't
|
|
548
|
+
// serializable. Use .describe() for the real plain-object snapshot.
|
|
549
|
+
toJSON() {
|
|
550
|
+
return `[WebElement ${this.helperType} — call .describe() for a plain-object snapshot or .toSimplifiedHTML() for HTML]`
|
|
551
|
+
}
|
|
552
|
+
|
|
516
553
|
_normalizeLocator(locator) {
|
|
517
554
|
if (typeof locator === 'string') {
|
|
518
555
|
return locator
|
package/lib/globals.js
CHANGED
|
@@ -27,9 +27,19 @@ export async function initCodeceptGlobals(dir, config, container) {
|
|
|
27
27
|
global.codecept_dir = dir
|
|
28
28
|
global.output_dir = fsPath.resolve(dir, config.output)
|
|
29
29
|
|
|
30
|
+
// pause/inject/share stay global even under noGlobals — they're the everyday
|
|
31
|
+
// debugging/wiring entry points and have no useful import alternative for
|
|
32
|
+
// page-object code that runs before the container is available.
|
|
33
|
+
global.pause = async (...args) => {
|
|
34
|
+
const pauseModule = await import('./pause.js')
|
|
35
|
+
return (pauseModule.default || pauseModule)(...args)
|
|
36
|
+
}
|
|
37
|
+
global.inject = () => container.support()
|
|
38
|
+
global.share = container.share
|
|
39
|
+
|
|
30
40
|
if (config.noGlobals) return;
|
|
31
41
|
|
|
32
|
-
output.print(output.styles.debug('Global functions are deprecated. Use `import { Helper,
|
|
42
|
+
output.print(output.styles.debug('Global functions are deprecated. Use `import { Helper, within, session } from "codeceptjs"` instead. Set `noGlobals: true` in config to disable globals.'));
|
|
33
43
|
|
|
34
44
|
const HelperModule = await import('@codeceptjs/helper')
|
|
35
45
|
global.Helper = global.codecept_helper = HelperModule.default || HelperModule
|
|
@@ -40,12 +50,6 @@ export async function initCodeceptGlobals(dir, config, container) {
|
|
|
40
50
|
}
|
|
41
51
|
global.Actor = global.actor
|
|
42
52
|
|
|
43
|
-
// Use dynamic imports for modules to avoid circular dependencies
|
|
44
|
-
global.pause = async (...args) => {
|
|
45
|
-
const pauseModule = await import('./pause.js')
|
|
46
|
-
return (pauseModule.default || pauseModule)(...args)
|
|
47
|
-
}
|
|
48
|
-
|
|
49
53
|
global.within = async (...args) => {
|
|
50
54
|
return (await import('./effects.js')).within(...args)
|
|
51
55
|
}
|
|
@@ -62,9 +66,6 @@ export async function initCodeceptGlobals(dir, config, container) {
|
|
|
62
66
|
return locator.build(locatorQuery)
|
|
63
67
|
}
|
|
64
68
|
|
|
65
|
-
global.inject = () => container.support()
|
|
66
|
-
global.share = container.share
|
|
67
|
-
|
|
68
69
|
const secretModule = await import('./secret.js')
|
|
69
70
|
global.secret = secretModule.secret || (secretModule.default && secretModule.default.secret)
|
|
70
71
|
|
package/lib/helper/Playwright.js
CHANGED
|
@@ -2674,8 +2674,11 @@ class Playwright extends Helper {
|
|
|
2674
2674
|
* @returns {Promise<any>}
|
|
2675
2675
|
*/
|
|
2676
2676
|
async executeScript(fn, arg) {
|
|
2677
|
+
if (arg && typeof arg.getNativeElement === 'function') arg = arg.getNativeElement()
|
|
2678
|
+
if (arg && typeof arg.evaluate === 'function' && typeof arg.locator === 'function') {
|
|
2679
|
+
return arg.evaluate(fn)
|
|
2680
|
+
}
|
|
2677
2681
|
if (this.context && this.context.constructor.name === 'FrameLocator') {
|
|
2678
|
-
// switching to iframe context
|
|
2679
2682
|
return this.context.locator(':root').evaluate(fn, arg)
|
|
2680
2683
|
}
|
|
2681
2684
|
return this.page.evaluate.apply(this.page, [fn, arg])
|
package/lib/html.js
CHANGED
|
@@ -323,6 +323,9 @@ async function formatHtml(html) {
|
|
|
323
323
|
wrap_line_length: 0,
|
|
324
324
|
preserve_newlines: false,
|
|
325
325
|
end_with_newline: false,
|
|
326
|
+
// Force every element onto its own line so line numbers in trace HTML
|
|
327
|
+
// map 1:1 to elements (consumed by codeceptq for AI/agent debugging).
|
|
328
|
+
inline: [],
|
|
326
329
|
})
|
|
327
330
|
} catch (e) {
|
|
328
331
|
return processed
|
package/lib/index.js
CHANGED
|
@@ -23,6 +23,10 @@ import heal from './heal.js'
|
|
|
23
23
|
import ai from './ai.js'
|
|
24
24
|
import Workers from './workers.js'
|
|
25
25
|
import Secret, { secret } from './secret.js'
|
|
26
|
+
import session from './session.js'
|
|
27
|
+
|
|
28
|
+
const inject = (name) => container.support(name)
|
|
29
|
+
const locate = (query) => locator.build(query)
|
|
26
30
|
|
|
27
31
|
export default {
|
|
28
32
|
/** @type {typeof CodeceptJS.Codecept} */
|
|
@@ -67,7 +71,11 @@ export default {
|
|
|
67
71
|
Secret,
|
|
68
72
|
/** @type {typeof CodeceptJS.secret} */
|
|
69
73
|
secret,
|
|
74
|
+
|
|
75
|
+
session,
|
|
76
|
+
inject,
|
|
77
|
+
locate,
|
|
70
78
|
}
|
|
71
79
|
|
|
72
80
|
// Named exports for ESM compatibility
|
|
73
|
-
export { codecept, output, container, event, recorder, config, actor, helper, pause, within, dataTable, dataTableArgument, store, locator, heal, ai, Workers, Secret, secret }
|
|
81
|
+
export { codecept, output, container, event, recorder, config, actor, helper, pause, within, dataTable, dataTableArgument, store, locator, heal, ai, Workers, Secret, secret, session, inject, locate }
|
package/lib/locator.js
CHANGED
|
@@ -589,7 +589,7 @@ Locator.clickable = {
|
|
|
589
589
|
`.//button[./@name = ${literal}]`,
|
|
590
590
|
`.//*[@aria-label = ${literal}]`,
|
|
591
591
|
`.//*[@title = ${literal}]`,
|
|
592
|
-
`.//*[@aria-labelledby = //*[@id][normalize-space(string(.)) = ${literal}]/@id
|
|
592
|
+
`.//*[@aria-labelledby][@aria-labelledby = //*[@id][normalize-space(string(.)) = ${literal}]/@id]`,
|
|
593
593
|
`.//*[@role='button'][normalize-space(.)=${literal}]`,
|
|
594
594
|
`.//*[@role='tab' or @role='link' or @role='menuitem' or @role='menuitemcheckbox' or @role='menuitemradio' or @role='option' or @role='treeitem'][contains(normalize-space(string(.)), ${literal})]`,
|
|
595
595
|
]),
|
|
@@ -632,7 +632,7 @@ Locator.field = {
|
|
|
632
632
|
`.//label[contains(normalize-space(string(.)), ${literal})]//.//*[self::input | self::textarea | self::select][not(./@type = 'submit' or ./@type = 'image' or ./@type = 'hidden')]`,
|
|
633
633
|
`.//*[@aria-label = ${literal}]`,
|
|
634
634
|
`.//*[@title = ${literal}]`,
|
|
635
|
-
`.//*[@aria-labelledby = //*[@id][normalize-space(string(.)) = ${literal}]/@id
|
|
635
|
+
`.//*[@aria-labelledby][@aria-labelledby = //*[@id][normalize-space(string(.)) = ${literal}]/@id]`,
|
|
636
636
|
]),
|
|
637
637
|
|
|
638
638
|
/**
|
package/lib/mocha/factory.js
CHANGED
|
@@ -17,7 +17,11 @@ let mocha
|
|
|
17
17
|
|
|
18
18
|
class MochaFactory {
|
|
19
19
|
static create(config, opts) {
|
|
20
|
-
|
|
20
|
+
const merged = Object.assign({}, config, opts)
|
|
21
|
+
mocha = new Mocha(merged)
|
|
22
|
+
if (merged.cleanReferencesAfterRun !== true) {
|
|
23
|
+
mocha.cleanReferencesAfterRun(false)
|
|
24
|
+
}
|
|
21
25
|
output.process(opts.child)
|
|
22
26
|
mocha.ui(scenarioUiFunction)
|
|
23
27
|
|
package/lib/mocha/inject.js
CHANGED
|
@@ -5,7 +5,7 @@ const getInjectedArguments = async (fn, test, suite) => {
|
|
|
5
5
|
const container = containerModule.default || containerModule
|
|
6
6
|
|
|
7
7
|
const testArgs = {}
|
|
8
|
-
const params = getParams(fn) || []
|
|
8
|
+
const params = getParams(fn, { warnOnLegacyFormat: true }) || []
|
|
9
9
|
const objects = container.support()
|
|
10
10
|
|
|
11
11
|
for (const key of params) {
|
package/lib/parser.js
CHANGED
|
@@ -14,11 +14,11 @@ export const getParamsToString = function (fn) {
|
|
|
14
14
|
return getParams(newFn).join(', ')
|
|
15
15
|
}
|
|
16
16
|
|
|
17
|
-
function getParams(fn) {
|
|
17
|
+
function getParams(fn, { warnOnLegacyFormat = false } = {}) {
|
|
18
18
|
if (fn.isSinonProxy) return []
|
|
19
19
|
try {
|
|
20
20
|
const reflected = parser.parse(fn)
|
|
21
|
-
if (reflected.args.length > 1 || reflected.args[0] === 'I') {
|
|
21
|
+
if (warnOnLegacyFormat && (reflected.args.length > 1 || reflected.args[0] === 'I')) {
|
|
22
22
|
output.error('Error: old CodeceptJS v2 format detected. Upgrade your project to the new format -> https://bit.ly/codecept3Up')
|
|
23
23
|
}
|
|
24
24
|
if (reflected.destructuredArgs.length > 0) reflected.args = [...reflected.destructuredArgs]
|
package/lib/plugin/browser.js
CHANGED
|
@@ -31,7 +31,8 @@ import output from '../output.js'
|
|
|
31
31
|
* logs a hint and skips the override.
|
|
32
32
|
*/
|
|
33
33
|
export default async function (config = {}) {
|
|
34
|
-
const
|
|
34
|
+
const { _args, enabled, ...rest } = config
|
|
35
|
+
const opts = { ...rest, ...parseArgs(_args || []) }
|
|
35
36
|
if (Object.keys(opts).length === 0) return
|
|
36
37
|
|
|
37
38
|
const configure = await tryImportConfigure()
|
|
@@ -0,0 +1,159 @@
|
|
|
1
|
+
import Container from '../container.js'
|
|
2
|
+
|
|
3
|
+
const RESERVED_NAMES = new Set(['I', 'test', 'suite'])
|
|
4
|
+
const SHORTHAND_PROPERTIES = new Set(['page', 'browser', 'browserContext', 'context'])
|
|
5
|
+
|
|
6
|
+
const defaultConfig = {
|
|
7
|
+
inject: {},
|
|
8
|
+
}
|
|
9
|
+
|
|
10
|
+
/**
|
|
11
|
+
* Exposes properties from helper instances as injectable test arguments.
|
|
12
|
+
* Use it to access the underlying Playwright/Puppeteer `page`, the wdio `browser` client,
|
|
13
|
+
* or any other helper internal directly from a Scenario:
|
|
14
|
+
*
|
|
15
|
+
* ```js
|
|
16
|
+
* Scenario('listen for requests', async ({ I, page, browser }) => {
|
|
17
|
+
* page.on('request', r => console.log(r.url()))
|
|
18
|
+
* await page.evaluate(() => 1 + 1)
|
|
19
|
+
* I.amOnPage('/')
|
|
20
|
+
* })
|
|
21
|
+
* ```
|
|
22
|
+
*
|
|
23
|
+
* The injected value is a live proxy: every property access reads the *current*
|
|
24
|
+
* helper property, so mid-test reassignments (popups, `switchToNextTab`,
|
|
25
|
+
* `openNewTab`) are reflected automatically. Calls are not wrapped as
|
|
26
|
+
* CodeceptJS steps — `await page.evaluate(...)` runs as native Playwright.
|
|
27
|
+
*
|
|
28
|
+
* #### Configuration
|
|
29
|
+
*
|
|
30
|
+
* `inject` maps an injection name to a `HelperName.propertyName` string. A
|
|
31
|
+
* value with no dot is shorthand for "first configured browser helper that
|
|
32
|
+
* exposes this property" (allowed properties: `page`, `browser`,
|
|
33
|
+
* `browserContext`, `context`).
|
|
34
|
+
*
|
|
35
|
+
* ```js
|
|
36
|
+
* plugins: {
|
|
37
|
+
* expose: {
|
|
38
|
+
* enabled: true,
|
|
39
|
+
* inject: {
|
|
40
|
+
* page: 'Playwright.page',
|
|
41
|
+
* browser: 'Playwright.browser',
|
|
42
|
+
* browserContext: 'Playwright.browserContext',
|
|
43
|
+
* frame: 'Playwright.context', // current frame set by switchTo
|
|
44
|
+
* wdio: 'WebDriver.browser',
|
|
45
|
+
* }
|
|
46
|
+
* }
|
|
47
|
+
* }
|
|
48
|
+
* ```
|
|
49
|
+
*
|
|
50
|
+
* Shorthand:
|
|
51
|
+
*
|
|
52
|
+
* ```js
|
|
53
|
+
* plugins: {
|
|
54
|
+
* expose: {
|
|
55
|
+
* enabled: true,
|
|
56
|
+
* inject: {
|
|
57
|
+
* page: 'page', // resolves to Playwright.page or Puppeteer.page
|
|
58
|
+
* }
|
|
59
|
+
* }
|
|
60
|
+
* }
|
|
61
|
+
* ```
|
|
62
|
+
*
|
|
63
|
+
* #### Caveats
|
|
64
|
+
*
|
|
65
|
+
* - The injected value is a `Proxy`, not the actual `Page`/`Browser` instance,
|
|
66
|
+
* so `page instanceof Page` is `false`. Use duck typing instead.
|
|
67
|
+
* - Cached method references lose the live binding. Call `page.click(...)`,
|
|
68
|
+
* not `const click = page.click; click(...)`.
|
|
69
|
+
* - In dry-run mode the underlying helper property is `undefined`; accessing
|
|
70
|
+
* any property on the proxy returns `undefined` rather than throwing.
|
|
71
|
+
*/
|
|
72
|
+
export default function (config = {}) {
|
|
73
|
+
config = { ...defaultConfig, ...config }
|
|
74
|
+
|
|
75
|
+
const mappings = parseMappings(config.inject)
|
|
76
|
+
|
|
77
|
+
const support = {}
|
|
78
|
+
for (const [name, { helperName, property }] of Object.entries(mappings)) {
|
|
79
|
+
support[name] = makeLiveProxy(helperName, property)
|
|
80
|
+
}
|
|
81
|
+
Container.append({ support })
|
|
82
|
+
}
|
|
83
|
+
|
|
84
|
+
function parseMappings(inject) {
|
|
85
|
+
const out = {}
|
|
86
|
+
for (const [name, value] of Object.entries(inject || {})) {
|
|
87
|
+
if (RESERVED_NAMES.has(name)) {
|
|
88
|
+
throw new Error(`expose plugin: inject name '${name}' is reserved`)
|
|
89
|
+
}
|
|
90
|
+
if (typeof value !== 'string' || !value) {
|
|
91
|
+
throw new Error(`expose plugin: inject value for '${name}' must be a non-empty string`)
|
|
92
|
+
}
|
|
93
|
+
|
|
94
|
+
let helperName
|
|
95
|
+
let property
|
|
96
|
+
|
|
97
|
+
if (value.includes('.')) {
|
|
98
|
+
const dot = value.indexOf('.')
|
|
99
|
+
helperName = value.slice(0, dot)
|
|
100
|
+
property = value.slice(dot + 1)
|
|
101
|
+
if (!helperName || !property) {
|
|
102
|
+
throw new Error(`expose plugin: invalid inject value '${value}' for '${name}' (expected 'HelperName.propertyName')`)
|
|
103
|
+
}
|
|
104
|
+
if (!Container.helpers(helperName)) {
|
|
105
|
+
throw new Error(`expose plugin: helper '${helperName}' is not configured (needed for inject '${name}')`)
|
|
106
|
+
}
|
|
107
|
+
} else {
|
|
108
|
+
property = value
|
|
109
|
+
if (!SHORTHAND_PROPERTIES.has(property)) {
|
|
110
|
+
throw new Error(`expose plugin: shorthand '${property}' is not a known helper property for '${name}' (use 'HelperName.${property}' instead)`)
|
|
111
|
+
}
|
|
112
|
+
helperName = Container.STANDARD_ACTING_HELPERS.find(h => Container.helpers(h))
|
|
113
|
+
if (!helperName) {
|
|
114
|
+
throw new Error(`expose plugin: no standard browser helper configured (needed for inject '${name}')`)
|
|
115
|
+
}
|
|
116
|
+
}
|
|
117
|
+
|
|
118
|
+
out[name] = { helperName, property }
|
|
119
|
+
}
|
|
120
|
+
return out
|
|
121
|
+
}
|
|
122
|
+
|
|
123
|
+
function makeLiveProxy(helperName, property) {
|
|
124
|
+
const resolve = () => Container.helpers(helperName)?.[property]
|
|
125
|
+
return new Proxy(function () {}, {
|
|
126
|
+
get(_, prop) {
|
|
127
|
+
const target = resolve()
|
|
128
|
+
if (target == null) return undefined
|
|
129
|
+
const value = target[prop]
|
|
130
|
+
if (typeof value === 'function') return value.bind(target)
|
|
131
|
+
return value
|
|
132
|
+
},
|
|
133
|
+
has(_, prop) {
|
|
134
|
+
const target = resolve()
|
|
135
|
+
return target != null && prop in target
|
|
136
|
+
},
|
|
137
|
+
apply(_, thisArg, args) {
|
|
138
|
+
const target = resolve()
|
|
139
|
+
return target?.apply(thisArg, args)
|
|
140
|
+
},
|
|
141
|
+
set(_, prop, value) {
|
|
142
|
+
const target = resolve()
|
|
143
|
+
if (target != null) target[prop] = value
|
|
144
|
+
return true
|
|
145
|
+
},
|
|
146
|
+
getPrototypeOf() {
|
|
147
|
+
const target = resolve()
|
|
148
|
+
return target != null ? Object.getPrototypeOf(target) : null
|
|
149
|
+
},
|
|
150
|
+
ownKeys() {
|
|
151
|
+
const target = resolve()
|
|
152
|
+
return target != null ? Reflect.ownKeys(target) : []
|
|
153
|
+
},
|
|
154
|
+
getOwnPropertyDescriptor(_, prop) {
|
|
155
|
+
const target = resolve()
|
|
156
|
+
return target != null ? Object.getOwnPropertyDescriptor(target, prop) : undefined
|
|
157
|
+
},
|
|
158
|
+
})
|
|
159
|
+
}
|
package/lib/workers.js
CHANGED
|
@@ -521,22 +521,8 @@ class Workers extends EventEmitter {
|
|
|
521
521
|
// Workers are already running, this is just a placeholder step
|
|
522
522
|
})
|
|
523
523
|
|
|
524
|
-
// Add overall timeout to prevent infinite hanging
|
|
525
|
-
const overallTimeout = setTimeout(() => {
|
|
526
|
-
console.error('[Main] Overall timeout reached (10 minutes). Force terminating remaining workers...')
|
|
527
|
-
workerThreads.forEach(w => {
|
|
528
|
-
try {
|
|
529
|
-
w.terminate()
|
|
530
|
-
} catch (e) {
|
|
531
|
-
// ignore
|
|
532
|
-
}
|
|
533
|
-
})
|
|
534
|
-
this._finishRun()
|
|
535
|
-
}, 600000) // 10 minutes
|
|
536
|
-
|
|
537
524
|
return new Promise(resolve => {
|
|
538
525
|
this.on('end', () => {
|
|
539
|
-
clearTimeout(overallTimeout)
|
|
540
526
|
resolve()
|
|
541
527
|
})
|
|
542
528
|
})
|
|
@@ -565,7 +551,7 @@ class Workers extends EventEmitter {
|
|
|
565
551
|
// Track last activity time to detect hanging workers
|
|
566
552
|
let lastActivity = Date.now()
|
|
567
553
|
let currentTest = null
|
|
568
|
-
const workerTimeout =
|
|
554
|
+
const workerTimeout = process.env.CODECEPT_WORKER_TIMEOUT ? ms(process.env.CODECEPT_WORKER_TIMEOUT) : ms('5m')
|
|
569
555
|
|
|
570
556
|
const timeoutChecker = setInterval(() => {
|
|
571
557
|
const elapsed = Date.now() - lastActivity
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "codeceptjs",
|
|
3
|
-
"version": "4.0.0-rc.
|
|
3
|
+
"version": "4.0.0-rc.19",
|
|
4
4
|
"type": "module",
|
|
5
5
|
"description": "Supercharged End 2 End Testing Framework for NodeJS",
|
|
6
6
|
"keywords": [
|
|
@@ -26,7 +26,8 @@
|
|
|
26
26
|
"lib",
|
|
27
27
|
"translations",
|
|
28
28
|
"typings/**/*.d.ts",
|
|
29
|
-
"docs
|
|
29
|
+
"docs/*.md",
|
|
30
|
+
"docs/helpers/**"
|
|
30
31
|
],
|
|
31
32
|
"main": "lib/index.js",
|
|
32
33
|
"module": "lib/index.js",
|
|
@@ -46,7 +47,8 @@
|
|
|
46
47
|
},
|
|
47
48
|
"bin": {
|
|
48
49
|
"codeceptjs": "./bin/codecept.js",
|
|
49
|
-
"codeceptjs-mcp": "./bin/mcp-server.js"
|
|
50
|
+
"codeceptjs-mcp": "./bin/mcp-server.js",
|
|
51
|
+
"codeceptq": "./bin/codeceptq.js"
|
|
50
52
|
},
|
|
51
53
|
"repository": "codeceptjs/CodeceptJS",
|
|
52
54
|
"scripts": {
|
|
@@ -131,6 +133,7 @@
|
|
|
131
133
|
"resq": "1.11.0",
|
|
132
134
|
"sprintf-js": "1.1.3",
|
|
133
135
|
"uuid": "11.1.0",
|
|
136
|
+
"xpath": "0.0.34",
|
|
134
137
|
"zod": "^4.1.11"
|
|
135
138
|
},
|
|
136
139
|
"optionalDependencies": {
|
|
@@ -192,8 +195,7 @@
|
|
|
192
195
|
"typescript": "5.9.3",
|
|
193
196
|
"wdio-docker-service": "3.2.1",
|
|
194
197
|
"webdriverio": "9.23.0",
|
|
195
|
-
"xml2js": "0.6.2"
|
|
196
|
-
"xpath": "0.0.34"
|
|
198
|
+
"xml2js": "0.6.2"
|
|
197
199
|
},
|
|
198
200
|
"peerDependencies": {
|
|
199
201
|
"tsx": "^4.0.0"
|