Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
20 changes: 12 additions & 8 deletions lib/actor.js
Original file line number Diff line number Diff line change
Expand Up @@ -75,7 +75,8 @@ export default function (obj = {}, container) {
if (!container) {
container = Container
}


// Get existing actor or create a new one
const actor = container.actor() || new Actor()

// load all helpers once container initialized
Expand Down Expand Up @@ -111,14 +112,17 @@ export default function (obj = {}, container) {
}
})

container.append({
support: {
I: actor,
},
})
// Update container.support.I to ensure it has the latest actor reference
if (!container.actor() || container.actor() !== actor) {
container.append({
support: {
I: actor,
},
})
}
})
// store.actor = actor;
// add custom steps from actor

// add custom steps from actor immediately
Object.keys(obj).forEach(key => {
const ms = new MetaStep('I', key)
ms.setContext(actor)
Expand Down
67 changes: 44 additions & 23 deletions lib/container.js
Original file line number Diff line number Diff line change
Expand Up @@ -22,6 +22,7 @@ let container = {
helpers: {},
support: {},
proxySupport: {},
proxySupportConfig: {}, // Track config used to create proxySupport
plugins: {},
actor: null,
/**
Expand All @@ -32,7 +33,7 @@ let container = {
translation: {},
/** @type {Result | null} */
result: null,
sharedKeys: new Set() // Track keys shared via share() function
sharedKeys: new Set(), // Track keys shared via share() function
}

/**
Expand Down Expand Up @@ -67,14 +68,15 @@ class Container {
container.support = {}
container.helpers = await createHelpers(config.helpers || {})
container.translation = await loadTranslation(config.translation || null, config.vocabularies || [])
container.proxySupport = createSupportObjects(config.include || {})
container.proxySupportConfig = config.include || {}
container.proxySupport = createSupportObjects(container.proxySupportConfig)
container.plugins = await createPlugins(config.plugins || {}, opts)
container.result = new Result()

// Preload includes (so proxies can expose real objects synchronously)
const includes = config.include || {}

// Ensure I is available for DI modules at import time
// Check if custom I is provided
if (Object.prototype.hasOwnProperty.call(includes, 'I')) {
try {
const mod = includes.I
Expand All @@ -89,7 +91,7 @@ class Container {
throw new Error(`Could not include object I: ${e.message}`)
}
} else {
// Create default actor if not provided via includes
// Create default actor - this sets up the callback in asyncHelperPromise
createActor()
}

Expand All @@ -110,6 +112,9 @@ class Container {
}
}

// Wait for all async helpers to finish loading and populate the actor
await asyncHelperPromise

if (opts && opts.ai) ai.enable(config.ai) // enable AI Assistant
if (config.gherkin) await loadGherkinStepsAsync(config.gherkin.steps || [])
if (opts && typeof opts.timeouts === 'boolean') store.timeouts = opts.timeouts
Expand Down Expand Up @@ -204,8 +209,10 @@ class Container {

// If new support objects are added, update the proxy support
if (newContainer.support) {
const newProxySupport = createSupportObjects(newContainer.support)
container.proxySupport = { ...container.proxySupport, ...newProxySupport }
// Merge the new support config with existing config
container.proxySupportConfig = { ...container.proxySupportConfig, ...newContainer.support }
// Recreate the proxy with merged config
container.proxySupport = createSupportObjects(container.proxySupportConfig)
}

debug('appended', JSON.stringify(newContainer).slice(0, 300))
Expand All @@ -221,6 +228,7 @@ class Container {
static async clear(newHelpers = {}, newSupport = {}, newPlugins = {}) {
container.helpers = newHelpers
container.translation = await loadTranslation()
container.proxySupportConfig = newSupport
container.proxySupport = createSupportObjects(newSupport)
container.plugins = newPlugins
container.sharedKeys = new Set() // Clear shared keys
Expand Down Expand Up @@ -250,10 +258,10 @@ class Container {
// Instead of using append which replaces the entire container,
// directly update the support object to maintain proxy references
Object.assign(container.support, data)

// Track which keys were explicitly shared
Object.keys(data).forEach(key => container.sharedKeys.add(key))

if (!options.local) {
WorkerStorage.share(data)
}
Expand Down Expand Up @@ -292,7 +300,7 @@ async function createHelpers(config) {
if (!HelperClass) {
const helperResult = requireHelperFromModule(helperName, config)
if (helperResult instanceof Promise) {
// Handle async ESM loading
// Handle async ESM loading - create placeholder
helpers[helperName] = {}
asyncHelperPromise = asyncHelperPromise
.then(() => helperResult)
Expand All @@ -311,8 +319,7 @@ async function createHelpers(config) {

checkHelperRequirements(ResolvedHelperClass)
helpers[helperName] = new ResolvedHelperClass(config[helperName])
if (helpers[helperName]._init) await helpers[helperName]._init()
debug(`helper ${helperName} async initialized`)
debug(`helper ${helperName} async loaded`)
})
continue
} else {
Expand All @@ -332,9 +339,8 @@ async function createHelpers(config) {
throw new Error(`Helper class from module '${helperName}' is not a class. Use CJS async module syntax.`)
}

debug(`helper ${helperName} async initialized`)

helpers[helperName] = new ResolvedHelperClass(config[helperName])
debug(`helper ${helperName} async CJS loaded`)
})

continue
Expand All @@ -349,9 +355,18 @@ async function createHelpers(config) {
}
}

for (const name in helpers) {
if (helpers[name]._init) await helpers[name]._init()
}
// Don't await here - let Container.create() handle the await
// This allows actor callbacks to be registered before resolution
asyncHelperPromise = asyncHelperPromise.then(async () => {
// Call _init on all helpers after they're all loaded
for (const name in helpers) {
if (helpers[name]._init) {
await helpers[name]._init()
debug(`helper ${name} _init() called`)
}
}
})

return helpers
}

Expand Down Expand Up @@ -525,10 +540,17 @@ function createSupportObjects(config) {
return [...new Set([...keys, ...container.sharedKeys])]
},
getOwnPropertyDescriptor(target, prop) {
// For destructuring to work, we need to return the actual value from the getter
let value
if (container.sharedKeys.has(prop) && prop in container.support) {
value = container.support[prop]
} else {
value = lazyLoad(prop)
}
return {
enumerable: true,
configurable: true,
value: target[prop],
value: value,
}
},
get(target, key) {
Expand Down Expand Up @@ -677,24 +699,23 @@ async function loadSupportObject(modulePath, supportObjectName) {
// Use dynamic import for both ESM and CJS modules
let importPath = modulePath
let tempJsFile = null

if (typeof importPath === 'string') {
const ext = path.extname(importPath)

// Handle TypeScript files
if (ext === '.ts') {
try {
// Use the TypeScript transpilation utility
const typescript = await import('typescript')
const { tempFile, allTempFiles } = await transpileTypeScript(importPath, typescript)

debug(`Transpiled TypeScript file: ${importPath} -> ${tempFile}`)

// Attach cleanup handler
importPath = tempFile
// Store temp files list in a way that cleanup can access them
tempJsFile = allTempFiles

} catch (tsError) {
throw new Error(`Failed to load TypeScript file ${importPath}: ${tsError.message}. Make sure 'typescript' package is installed.`)
}
Expand All @@ -703,7 +724,7 @@ async function loadSupportObject(modulePath, supportObjectName) {
importPath = `${importPath}.js`
}
}

let obj
try {
obj = await import(importPath)
Expand Down
10 changes: 6 additions & 4 deletions lib/helper/GraphQL.js
Original file line number Diff line number Diff line change
Expand Up @@ -45,6 +45,8 @@ class GraphQL extends Helper {
timeout: 10000,
defaultHeaders: {},
endpoint: '',
onRequest: null,
onResponse: null,
}
this.options = Object.assign(this.options, config)
this.headers = { ...this.options.defaultHeaders }
Expand Down Expand Up @@ -87,8 +89,8 @@ class GraphQL extends Helper {

request.headers = { ...this.headers, ...request.headers }

if (this.config.onRequest) {
await this.config.onRequest(request)
if (this.options.onRequest) {
await this.options.onRequest(request)
}

this.debugSection('Request', JSON.stringify(request))
Expand All @@ -102,8 +104,8 @@ class GraphQL extends Helper {
response = err.response
}

if (this.config.onResponse) {
await this.config.onResponse(response)
if (this.options.onResponse) {
await this.options.onResponse(response)
}

this.debugSection('Response', JSON.stringify(response.data))
Expand Down
7 changes: 3 additions & 4 deletions lib/helper/JSONResponse.js
Original file line number Diff line number Diff line change
Expand Up @@ -72,8 +72,8 @@ class JSONResponse extends Helper {
if (!this.helpers[this.options.requestHelper]) {
throw new Error(`Error setting JSONResponse, helper ${this.options.requestHelper} is not enabled in config, helpers: ${Object.keys(this.helpers)}`)
}
const origOnResponse = this.helpers[this.options.requestHelper].config.onResponse
this.helpers[this.options.requestHelper].config.onResponse = response => {
const origOnResponse = this.helpers[this.options.requestHelper].options.onResponse
this.helpers[this.options.requestHelper].options.onResponse = response => {
this.response = response
if (typeof origOnResponse === 'function') origOnResponse(response)
}
Expand All @@ -83,7 +83,6 @@ class JSONResponse extends Helper {
this.response = null
}


/**
* Checks that response code is equal to the provided one
*
Expand Down Expand Up @@ -372,4 +371,4 @@ class JSONResponse extends Helper {
}
}

export { JSONResponse as default }
export { JSONResponse, JSONResponse as default }
Loading