Within narrows the execution context to a specific element or iframe on the page. All actions called inside a Within block are scoped to the matched element.
import { Within } from 'codeceptjs/effects'The simplest way to use Within is the begin/end pattern. Call Within with a locator to start, perform actions, then call Within() with no arguments to end:
Within('.signup-form')
I.fillField('Email', 'user@example.com')
I.fillField('Password', 'secret')
I.click('Sign Up')
Within()Steps between Within('.signup-form') and Within() are scoped to .signup-form. After Within(), the context resets to the full page.
Starting a new Within automatically ends the previous one:
Within('.sidebar')
I.click('Dashboard')
Within('.main-content') // ends .sidebar, begins .main-content
I.see('Welcome')
Within()If you forget to call Within() at the end, the context is automatically cleaned up when the test finishes. However, it is good practice to always close it explicitly.
The callback pattern wraps actions in a function. The context is automatically closed when the function returns:
Within('.signup-form', () => {
I.fillField('Email', 'user@example.com')
I.fillField('Password', 'secret')
I.click('Sign Up')
})
I.see('Account created')The callback pattern supports returning values. Use await on both the Within call and the inner action:
const text = await Within('#sidebar', async () => {
return await I.grabTextFrom('h1')
})
I.fillField('Search', text)Begin/end pattern does not need await:
Within('.form')
I.fillField('Name', 'John')
Within()Callback pattern needs await when:
- The callback is
async - You need a return value from
Within
// async callback — await required
await Within('.form', async () => {
await I.click('Submit')
await I.waitForText('Done')
})// sync callback — no await needed
Within('.form', () => {
I.fillField('Name', 'John')
I.click('Submit')
})Use the frame locator to scope actions inside an iframe:
// Begin/end
Within({ frame: 'iframe' })
I.fillField('Email', 'user@example.com')
I.click('Submit')
Within()
// Callback
Within({ frame: '#editor-frame' }, () => {
I.see('Page content')
})Pass an array of selectors to reach nested iframes:
Within({ frame: ['.wrapper', '#content-frame'] }, () => {
I.fillField('Name', 'John')
I.see('Sign in!')
})Each selector in the array navigates one level deeper into the iframe hierarchy.
If you call I.switchTo() while inside a Within context, the within context is automatically ended. This prevents conflicts between the two scoping mechanisms:
Within('.sidebar')
I.click('Open editor')
I.switchTo('#editor-frame') // automatically ends Within('.sidebar')
I.fillField('content', 'Hello')
I.switchTo() // exits iframeIn page objects, import Within directly:
// pages/Login.js
import { Within } from 'codeceptjs/effects'
export default {
loginForm: '.login-form',
fillCredentials(email, password) {
Within(this.loginForm)
I.fillField('Email', email)
I.fillField('Password', password)
Within()
},
submitLogin(email, password) {
this.fillCredentials(email, password)
I.click('Log In')
},
}// tests/login_test.js
Scenario('user can log in', ({ I, loginPage }) => {
I.amOnPage('/login')
loginPage.submitLogin('user@example.com', 'password')
I.see('Dashboard')
})The callback pattern also works in page objects:
// pages/Checkout.js
import { Within } from 'codeceptjs/effects'
export default {
async getTotal() {
return await Within('.order-summary', async () => {
return await I.grabTextFrom('.total')
})
},
}The lowercase within() is still available as a global function for backward compatibility, but it is deprecated:
// deprecated — still works, shows a one-time warning
within('.form', () => {
I.fillField('Name', 'John')
})
// recommended
import { Within } from 'codeceptjs/effects'
Within('.form', () => {
I.fillField('Name', 'John')
})The global within only supports the callback pattern. For the begin/end pattern, you must import Within.
When running steps inside a Within block, the output shows them indented under the context:
Within ".signup-form"
I fill field "Email", "user@example.com"
I fill field "Password", "secret"
I click "Sign Up"
I see "Account created"
- Prefer the begin/end pattern for simple linear flows — it's more readable.
- Use the callback pattern when you need return values or want guaranteed cleanup.
- Avoid deeply nesting
Withinblocks. If you find yourself needing nested contexts, consider restructuring your test. Withincannot be used inside asession. Usesessionat the top level andWithininside it, not the other way around.