Skip to content

Commit 072200c

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 6046942 commit 072200c

File tree

8 files changed

+91
-109
lines changed

8 files changed

+91
-109
lines changed

docs/effects.md

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

1414
```js
15-
import { tryTo, retryTo, Within } from 'codeceptjs/effects'
15+
import { tryTo, retryTo, within } from 'codeceptjs/effects'
1616
```
1717

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

79-
## Within
79+
## within
8080

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

8383
```js
84-
import { Within } from 'codeceptjs/effects'
84+
import { within } from 'codeceptjs/effects'
8585

86-
// Begin/end pattern
87-
Within('.modal')
86+
// Begin/leave pattern
87+
const area = within('.modal')
8888
I.see('Modal title')
8989
I.click('Close')
90-
Within()
90+
area.leave()
9191

9292
// Callback pattern
93-
Within('.modal', () => {
93+
within('.modal', () => {
9494
I.see('Modal title')
9595
I.click('Close')
9696
})
9797
```
9898

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

103101
## Usage with TypeScript
104102

105103
Effects are fully typed and work well with TypeScript:
106104

107105
```ts
108-
import { tryTo, retryTo, Within } from 'codeceptjs/effects'
106+
import { tryTo, retryTo, within } from 'codeceptjs/effects'
109107

110108
const success = await tryTo(async () => {
111109
await I.see('Element')

docs/within.md

Lines changed: 50 additions & 61 deletions
Original file line numberDiff line numberDiff line change
@@ -1,53 +1,61 @@
11
---
22
permalink: /within
3-
title: Within
3+
title: within
44
---
55

6-
# Within
6+
# within
77

8-
`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.
8+
`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.
99

1010
```js
11-
import { Within } from 'codeceptjs/effects'
11+
import { within } from 'codeceptjs/effects'
1212
```
1313

14-
## Begin / End Pattern
14+
## Begin / Leave Pattern
1515

16-
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:
16+
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:
1717

1818
```js
19-
Within('.signup-form')
19+
const area = within('.signup-form')
2020
I.fillField('Email', 'user@example.com')
2121
I.fillField('Password', 'secret')
2222
I.click('Sign Up')
23-
Within()
23+
area.leave()
2424
```
2525

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

2836
### Auto-end previous context
2937

30-
Starting a new `Within` automatically ends the previous one:
38+
Starting a new `within` automatically ends the previous one:
3139

3240
```js
33-
Within('.sidebar')
41+
within('.sidebar')
3442
I.click('Dashboard')
3543

36-
Within('.main-content') // ends .sidebar, begins .main-content
44+
within('.main-content') // ends .sidebar, begins .main-content
3745
I.see('Welcome')
38-
Within()
46+
within()
3947
```
4048

4149
### Forgetting to close
4250

43-
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.
51+
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.
4452

4553
## Callback Pattern
4654

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

4957
```js
50-
Within('.signup-form', () => {
58+
within('.signup-form', () => {
5159
I.fillField('Email', 'user@example.com')
5260
I.fillField('Password', 'secret')
5361
I.click('Sign Up')
@@ -57,41 +65,41 @@ I.see('Account created')
5765

5866
### Returning values
5967

60-
The callback pattern supports returning values. Use `await` on both the `Within` call and the inner action:
68+
The callback pattern supports returning values. Use `await` on both the `within` call and the inner action:
6169

6270
```js
63-
const text = await Within('#sidebar', async () => {
71+
const text = await within('#sidebar', async () => {
6472
return await I.grabTextFrom('h1')
6573
})
6674
I.fillField('Search', text)
6775
```
6876

6977
## When to use `await`
7078

71-
**Begin/end pattern** does not need `await`:
79+
**Begin/leave pattern** does not need `await`:
7280

7381
```js
74-
Within('.form')
82+
const area = within('.form')
7583
I.fillField('Name', 'John')
76-
Within()
84+
area.leave()
7785
```
7886

7987
**Callback pattern** needs `await` when:
8088

8189
- The callback is `async`
82-
- You need a return value from `Within`
90+
- You need a return value from `within`
8391

8492
```js
8593
// async callback — await required
86-
await Within('.form', async () => {
94+
await within('.form', async () => {
8795
await I.click('Submit')
8896
await I.waitForText('Done')
8997
})
9098
```
9199

92100
```js
93101
// sync callback — no await needed
94-
Within('.form', () => {
102+
within('.form', () => {
95103
I.fillField('Name', 'John')
96104
I.click('Submit')
97105
})
@@ -102,14 +110,14 @@ Within('.form', () => {
102110
Use the `frame` locator to scope actions inside an iframe:
103111

104112
```js
105-
// Begin/end
106-
Within({ frame: 'iframe' })
113+
// Begin/leave
114+
const area = within({ frame: 'iframe' })
107115
I.fillField('Email', 'user@example.com')
108116
I.click('Submit')
109-
Within()
117+
area.leave()
110118

111119
// Callback
112-
Within({ frame: '#editor-frame' }, () => {
120+
within({ frame: '#editor-frame' }, () => {
113121
I.see('Page content')
114122
})
115123
```
@@ -119,42 +127,42 @@ Within({ frame: '#editor-frame' }, () => {
119127
Pass an array of selectors to reach nested iframes:
120128

121129
```js
122-
Within({ frame: ['.wrapper', '#content-frame'] }, () => {
130+
within({ frame: ['.wrapper', '#content-frame'] }, () => {
123131
I.fillField('Name', 'John')
124132
I.see('Sign in!')
125133
})
126134
```
127135

128136
Each selector in the array navigates one level deeper into the iframe hierarchy.
129137

130-
### switchTo auto-disables Within
138+
### switchTo auto-disables within
131139

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

134142
```js
135-
Within('.sidebar')
143+
const area = within('.sidebar')
136144
I.click('Open editor')
137-
I.switchTo('#editor-frame') // automatically ends Within('.sidebar')
145+
I.switchTo('#editor-frame') // automatically ends within('.sidebar')
138146
I.fillField('content', 'Hello')
139147
I.switchTo() // exits iframe
140148
```
141149

142150
## Usage in Page Objects
143151

144-
In page objects, import `Within` directly:
152+
In page objects, import `within` directly:
145153

146154
```js
147155
// pages/Login.js
148-
import { Within } from 'codeceptjs/effects'
156+
import { within } from 'codeceptjs/effects'
149157

150158
export default {
151159
loginForm: '.login-form',
152160

153161
fillCredentials(email, password) {
154-
Within(this.loginForm)
162+
const area = within(this.loginForm)
155163
I.fillField('Email', email)
156164
I.fillField('Password', password)
157-
Within()
165+
area.leave()
158166
},
159167

160168
submitLogin(email, password) {
@@ -177,39 +185,20 @@ The callback pattern also works in page objects:
177185

178186
```js
179187
// pages/Checkout.js
180-
import { Within } from 'codeceptjs/effects'
188+
import { within } from 'codeceptjs/effects'
181189

182190
export default {
183191
async getTotal() {
184-
return await Within('.order-summary', async () => {
192+
return await within('.order-summary', async () => {
185193
return await I.grabTextFrom('.total')
186194
})
187195
},
188196
}
189197
```
190198

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

212-
When running steps inside a `Within` block, the output shows them indented under the context:
201+
When running steps inside a `within` block, the output shows them indented under the context:
213202

214203
```
215204
Within ".signup-form"
@@ -221,7 +210,7 @@ When running steps inside a `Within` block, the output shows them indented under
221210

222211
## Tips
223212

224-
- Prefer the begin/end pattern for simple linear flows — it's more readable.
213+
- Prefer the begin/leave pattern for simple linear flows — it's more readable.
225214
- Use the callback pattern when you need return values or want guaranteed cleanup.
226-
- Avoid deeply nesting `Within` blocks. If you find yourself needing nested contexts, consider restructuring your test.
227-
- `Within` cannot be used inside a `session`. Use `session` at the top level and `Within` inside it, not the other way around.
215+
- Avoid deeply nesting `within` blocks. If you find yourself needing nested contexts, consider restructuring your test.
216+
- `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 }

0 commit comments

Comments
 (0)