Skip to content

Commit e7cd69d

Browse files
DavertMikclaude
andcommitted
feat: rename Within() to within() and add .leave() API for explicit context control
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
1 parent 7115a82 commit e7cd69d

File tree

8 files changed

+90
-108
lines changed

8 files changed

+90
-108
lines changed

docs/effects.md

Lines changed: 10 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -7,7 +7,7 @@ Effects are functions that can modify scenario flow. They provide ways to handle
77
Effects can be imported directly from CodeceptJS:
88

99
```js
10-
import { tryTo, retryTo, Within } from 'codeceptjs/effects'
10+
import { tryTo, retryTo, within } from 'codeceptjs/effects'
1111
```
1212

1313
> 📝 Note: Prior to v3.7, `tryTo` and `retryTo` were available globally via plugins. This behavior is deprecated and will be removed in v4.0.
@@ -71,36 +71,34 @@ await retryTo(tries => {
7171
}, 3)
7272
```
7373

74-
## Within
74+
## within
7575

76-
The `Within` effect scopes actions to a specific element or iframe. It supports both a begin/end pattern and a callback pattern:
76+
The `within` effect scopes actions to a specific element or iframe. It supports both a begin/leave pattern and a callback pattern:
7777

7878
```js
79-
import { Within } from 'codeceptjs/effects'
79+
import { within } from 'codeceptjs/effects'
8080

81-
// Begin/end pattern
82-
Within('.modal')
81+
// Begin/leave pattern
82+
const area = within('.modal')
8383
I.see('Modal title')
8484
I.click('Close')
85-
Within()
85+
area.leave()
8686

8787
// Callback pattern
88-
Within('.modal', () => {
88+
within('.modal', () => {
8989
I.see('Modal title')
9090
I.click('Close')
9191
})
9292
```
9393

94-
See the full [Within documentation](/within) for details on iframes, page objects, and `await` usage.
95-
96-
> The lowercase `within()` is deprecated. Use `Within` instead.
94+
See the full [within documentation](/within) for details on iframes, page objects, and `await` usage.
9795

9896
## Usage with TypeScript
9997

10098
Effects are fully typed and work well with TypeScript:
10199

102100
```ts
103-
import { tryTo, retryTo, Within } from 'codeceptjs/effects'
101+
import { tryTo, retryTo, within } from 'codeceptjs/effects'
104102

105103
const success = await tryTo(async () => {
106104
await I.see('Element')

docs/within.md

Lines changed: 49 additions & 60 deletions
Original file line numberDiff line numberDiff line change
@@ -1,48 +1,56 @@
1-
# Within
1+
# within
22

3-
`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.
3+
`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.
44

55
```js
6-
import { Within } from 'codeceptjs/effects'
6+
import { within } from 'codeceptjs/effects'
77
```
88

9-
## Begin / End Pattern
9+
## Begin / Leave Pattern
1010

11-
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:
11+
The simplest way to use `within` is the begin/leave pattern. Call `within` with a locator to start — it returns a context object. Call `.leave()` on it when done:
1212

1313
```js
14-
Within('.signup-form')
14+
const area = within('.signup-form')
1515
I.fillField('Email', 'user@example.com')
1616
I.fillField('Password', 'secret')
1717
I.click('Sign Up')
18-
Within()
18+
area.leave()
1919
```
2020

21-
Steps between `Within('.signup-form')` and `Within()` are scoped to `.signup-form`. After `Within()`, the context resets to the full page.
21+
Steps between `within('.signup-form')` and `area.leave()` are scoped to `.signup-form`. After `leave()`, the context resets to the full page.
22+
23+
You can also end a context by calling `within()` with no arguments:
24+
25+
```js
26+
within('.signup-form')
27+
I.fillField('Email', 'user@example.com')
28+
within()
29+
```
2230

2331
### Auto-end previous context
2432

25-
Starting a new `Within` automatically ends the previous one:
33+
Starting a new `within` automatically ends the previous one:
2634

2735
```js
28-
Within('.sidebar')
36+
within('.sidebar')
2937
I.click('Dashboard')
3038

31-
Within('.main-content') // ends .sidebar, begins .main-content
39+
within('.main-content') // ends .sidebar, begins .main-content
3240
I.see('Welcome')
33-
Within()
41+
within()
3442
```
3543

3644
### Forgetting to close
3745

38-
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.
46+
If you forget to call `leave()` or `within()` at the end, the context is automatically cleaned up when the test finishes. However, it is good practice to always close it explicitly.
3947

4048
## Callback Pattern
4149

4250
The callback pattern wraps actions in a function. The context is automatically closed when the function returns:
4351

4452
```js
45-
Within('.signup-form', () => {
53+
within('.signup-form', () => {
4654
I.fillField('Email', 'user@example.com')
4755
I.fillField('Password', 'secret')
4856
I.click('Sign Up')
@@ -52,41 +60,41 @@ I.see('Account created')
5260

5361
### Returning values
5462

55-
The callback pattern supports returning values. Use `await` on both the `Within` call and the inner action:
63+
The callback pattern supports returning values. Use `await` on both the `within` call and the inner action:
5664

5765
```js
58-
const text = await Within('#sidebar', async () => {
66+
const text = await within('#sidebar', async () => {
5967
return await I.grabTextFrom('h1')
6068
})
6169
I.fillField('Search', text)
6270
```
6371

6472
## When to use `await`
6573

66-
**Begin/end pattern** does not need `await`:
74+
**Begin/leave pattern** does not need `await`:
6775

6876
```js
69-
Within('.form')
77+
const area = within('.form')
7078
I.fillField('Name', 'John')
71-
Within()
79+
area.leave()
7280
```
7381

7482
**Callback pattern** needs `await` when:
7583

7684
- The callback is `async`
77-
- You need a return value from `Within`
85+
- You need a return value from `within`
7886

7987
```js
8088
// async callback — await required
81-
await Within('.form', async () => {
89+
await within('.form', async () => {
8290
await I.click('Submit')
8391
await I.waitForText('Done')
8492
})
8593
```
8694

8795
```js
8896
// sync callback — no await needed
89-
Within('.form', () => {
97+
within('.form', () => {
9098
I.fillField('Name', 'John')
9199
I.click('Submit')
92100
})
@@ -97,14 +105,14 @@ Within('.form', () => {
97105
Use the `frame` locator to scope actions inside an iframe:
98106

99107
```js
100-
// Begin/end
101-
Within({ frame: 'iframe' })
108+
// Begin/leave
109+
const area = within({ frame: 'iframe' })
102110
I.fillField('Email', 'user@example.com')
103111
I.click('Submit')
104-
Within()
112+
area.leave()
105113

106114
// Callback
107-
Within({ frame: '#editor-frame' }, () => {
115+
within({ frame: '#editor-frame' }, () => {
108116
I.see('Page content')
109117
})
110118
```
@@ -114,42 +122,42 @@ Within({ frame: '#editor-frame' }, () => {
114122
Pass an array of selectors to reach nested iframes:
115123

116124
```js
117-
Within({ frame: ['.wrapper', '#content-frame'] }, () => {
125+
within({ frame: ['.wrapper', '#content-frame'] }, () => {
118126
I.fillField('Name', 'John')
119127
I.see('Sign in!')
120128
})
121129
```
122130

123131
Each selector in the array navigates one level deeper into the iframe hierarchy.
124132

125-
### switchTo auto-disables Within
133+
### switchTo auto-disables within
126134

127-
If you call `I.switchTo()` while inside a `Within` context, the within context is automatically ended. This prevents conflicts between the two scoping mechanisms:
135+
If you call `I.switchTo()` while inside a `within` context, the within context is automatically ended. This prevents conflicts between the two scoping mechanisms:
128136

129137
```js
130-
Within('.sidebar')
138+
const area = within('.sidebar')
131139
I.click('Open editor')
132-
I.switchTo('#editor-frame') // automatically ends Within('.sidebar')
140+
I.switchTo('#editor-frame') // automatically ends within('.sidebar')
133141
I.fillField('content', 'Hello')
134142
I.switchTo() // exits iframe
135143
```
136144

137145
## Usage in Page Objects
138146

139-
In page objects, import `Within` directly:
147+
In page objects, import `within` directly:
140148

141149
```js
142150
// pages/Login.js
143-
import { Within } from 'codeceptjs/effects'
151+
import { within } from 'codeceptjs/effects'
144152

145153
export default {
146154
loginForm: '.login-form',
147155

148156
fillCredentials(email, password) {
149-
Within(this.loginForm)
157+
const area = within(this.loginForm)
150158
I.fillField('Email', email)
151159
I.fillField('Password', password)
152-
Within()
160+
area.leave()
153161
},
154162

155163
submitLogin(email, password) {
@@ -172,39 +180,20 @@ The callback pattern also works in page objects:
172180

173181
```js
174182
// pages/Checkout.js
175-
import { Within } from 'codeceptjs/effects'
183+
import { within } from 'codeceptjs/effects'
176184

177185
export default {
178186
async getTotal() {
179-
return await Within('.order-summary', async () => {
187+
return await within('.order-summary', async () => {
180188
return await I.grabTextFrom('.total')
181189
})
182190
},
183191
}
184192
```
185193

186-
## Deprecated: lowercase `within`
187-
188-
The lowercase `within()` is still available as a global function for backward compatibility, but it is deprecated:
189-
190-
```js
191-
// deprecated — still works, shows a one-time warning
192-
within('.form', () => {
193-
I.fillField('Name', 'John')
194-
})
195-
196-
// recommended
197-
import { Within } from 'codeceptjs/effects'
198-
Within('.form', () => {
199-
I.fillField('Name', 'John')
200-
})
201-
```
202-
203-
The global `within` only supports the callback pattern. For the begin/end pattern, you must import `Within`.
204-
205194
## Output
206195

207-
When running steps inside a `Within` block, the output shows them indented under the context:
196+
When running steps inside a `within` block, the output shows them indented under the context:
208197

209198
```
210199
Within ".signup-form"
@@ -216,7 +205,7 @@ When running steps inside a `Within` block, the output shows them indented under
216205

217206
## Tips
218207

219-
- Prefer the begin/end pattern for simple linear flows — it's more readable.
208+
- Prefer the begin/leave pattern for simple linear flows — it's more readable.
220209
- Use the callback pattern when you need return values or want guaranteed cleanup.
221-
- Avoid deeply nesting `Within` blocks. If you find yourself needing nested contexts, consider restructuring your test.
222-
- `Within` cannot be used inside a `session`. Use `session` at the top level and `Within` inside it, not the other way around.
210+
- Avoid deeply nesting `within` blocks. If you find yourself needing nested contexts, consider restructuring your test.
211+
- `within` cannot be used inside a `session`. Use `session` at the top level and `within` inside it, not the other way around.

lib/effects.js

Lines changed: 9 additions & 18 deletions
Original file line numberDiff line numberDiff line change
@@ -6,7 +6,12 @@ import container from './container.js'
66
import { isAsyncFunction } from './utils.js'
77
import { WithinContext, WithinStep } from './step/within.js'
88

9-
function Within(context, fn) {
9+
/**
10+
* @param {CodeceptJS.LocatorOrString} context
11+
* @param {Function} fn
12+
* @return {Promise<*> | undefined}
13+
*/
14+
function within(context, fn) {
1015
if (!context && !fn) {
1116
WithinContext.endCurrent()
1217
return
@@ -15,7 +20,7 @@ function Within(context, fn) {
1520
if (context && !fn) {
1621
const ctx = new WithinContext(context)
1722
ctx.start()
18-
return
23+
return ctx
1924
}
2025

2126
const helpers = store.dryRun ? {} : container.helpers()
@@ -80,20 +85,6 @@ function Within(context, fn) {
8085
)
8186
}
8287

83-
let withinDeprecationWarned = false
84-
/**
85-
* @param {CodeceptJS.LocatorOrString} context
86-
* @param {Function} fn
87-
* @return {Promise<*> | undefined}
88-
*/
89-
function within(context, fn) {
90-
if (!withinDeprecationWarned) {
91-
withinDeprecationWarned = true
92-
output.print(' [deprecated] within() is deprecated. Use Within() from "codeceptjs/effects" instead.')
93-
}
94-
return Within(context, fn)
95-
}
96-
9788
/**
9889
* A utility function for CodeceptJS tests that acts as a soft assertion.
9990
* Executes a callback within a recorded session, ensuring errors are handled gracefully without failing the test immediately.
@@ -306,12 +297,12 @@ async function tryTo(callback) {
306297
)
307298
}
308299

309-
export { hopeThat, retryTo, tryTo, within, Within }
300+
export { hopeThat, retryTo, tryTo, within, within as Within }
310301

311302
export default {
312303
hopeThat,
313304
retryTo,
314305
tryTo,
315306
within,
316-
Within,
307+
Within: within,
317308
}

lib/index.js

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -14,7 +14,7 @@ import config from './config.js'
1414
import actor from './actor.js'
1515
import helper from './helper.js'
1616
import pause from './pause.js'
17-
import { within, Within } from './effects.js'
17+
import { within } from './effects.js'
1818
import dataTable from './data/table.js'
1919
import dataTableArgument from './data/dataTableArgument.js'
2020
import store from './store.js'
@@ -49,7 +49,7 @@ export default {
4949
pause,
5050
/** @type {typeof CodeceptJS.within} */
5151
within,
52-
Within,
52+
Within: within,
5353
/** @type {typeof CodeceptJS.DataTable} */
5454
dataTable,
5555
/** @type {typeof CodeceptJS.DataTableArgument} */
@@ -71,4 +71,4 @@ export default {
7171
}
7272

7373
// Named exports for ESM compatibility
74-
export { codecept, output, container, event, recorder, config, actor, helper, pause, within, Within, dataTable, dataTableArgument, store, locator, heal, ai, Workers, Secret, secret }
74+
export { codecept, output, container, event, recorder, config, actor, helper, pause, within, within as Within, dataTable, dataTableArgument, store, locator, heal, ai, Workers, Secret, secret }

lib/step/within.js

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -65,6 +65,10 @@ class WithinContext {
6565
event.dispatcher.removeListener(event.step.before, this.attachMetaStep)
6666
}
6767

68+
leave() {
69+
this.end()
70+
}
71+
6872
static current() {
6973
return currentWithin
7074
}

0 commit comments

Comments
 (0)