-
Notifications
You must be signed in to change notification settings - Fork 2
Expand file tree
/
Copy pathindex.html
More file actions
451 lines (399 loc) · 160 KB
/
Copy pathindex.html
File metadata and controls
451 lines (399 loc) · 160 KB
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
415
416
417
418
419
420
421
422
423
424
425
426
427
428
429
430
431
432
433
434
435
436
437
438
439
440
441
442
443
444
445
446
447
448
449
450
451
<!DOCTYPE html><html lang="en" dir="ltr" data-theme="dark" data-has-toc data-has-sidebar class="astro-qj6ycuyd"> <head><meta name="astro-view-transitions-enabled" content="true"><meta name="astro-view-transitions-fallback" content="animate"><script type="module" src="/_astro/ClientRouter.astro_astro_type_script_index_0_lang.CUKa2Nj2.js"></script> <meta charset="utf-8"/><meta name="viewport" content="width=device-width, initial-scale=1"/><title>Page Objects | </title><link rel="canonical" href="https://codecept.io/pageobjects/"/><link rel="sitemap" href="/sitemap-index.xml"/><link rel="shortcut icon" href="/favicon.svg" type="image/svg+xml"/><meta name="generator" content="Astro v5.16.5"/><meta name="generator" content="Starlight v0.37.1"/><meta property="og:title" content="Page Objects"/><meta property="og:type" content="article"/><meta property="og:url" content="https://codecept.io/pageobjects/"/><meta property="og:locale" content="en"/><meta property="og:site_name" content/><meta name="twitter:card" content="summary_large_image"/> <meta name="vtbot-replace-swap" content="data-theme"><script type="module" src="/_astro/ReplacementSwap.astro_astro_type_script_index_0_lang.D1Fl65FS.js"></script> <script type="module" src="/_astro/StarlightConnector.astro_astro_type_script_index_0_lang.BdsaPlsR.js"></script> <link rel="icon" href="/favicon.ico" sizes="32x32"> <link rel="apple-touch-icon" sizes="180x180" href="/apple-touch-icon.png"> <link rel="manifest" href="/site.webmanifest"> <meta name="theme-color" content="#ffffff" media="(prefers-color-scheme: light)"> <meta name="theme-color" content="#1a1d28" media="(prefers-color-scheme: dark)"> <meta property="og:image" content="https://codecept.io/og-image.png"> <meta property="og:image:width" content="1200"> <meta property="og:image:height" content="630"> <meta property="og:image:alt" content="CodeceptJS — Supercharged End 2 End Testing"> <meta name="twitter:image" content="https://codecept.io/og-image.png"> <script type="application/ld+json">{"@context":"https://schema.org","@type":"WebSite","@id":"pageobjects","image":{"@type":"ImageObject","url":"https://codecept.io/og-image.png"},"inLanguage":"en","name":"","url":"https://codecept.io/pageobjects/","sameAs":["https://codecept.io/","https://twitter.com/codeceptjs","https://codeceptjs.slack.com/join/shared_invite/zt-1dkxkju78-9aThX6G2~iDkLwhuRsPVsg","https://codecept.discourse.group/","https://stackoverflow.com/questions/tagged/codeceptjs","https://github.com/codeceptjs/CodeceptJS/discussions","https://github.com/codeceptjs/CodeceptJS"]}</script><script type="application/ld+json">{"@context":"https://schema.org","@type":"Organization","image":{"@type":"ImageObject","url":"https://codecept.io/og-image.png"},"url":"https://codecept.io/pageobjects/","sameAs":["https://codecept.io/","https://twitter.com/codeceptjs","https://codeceptjs.slack.com/join/shared_invite/zt-1dkxkju78-9aThX6G2~iDkLwhuRsPVsg","https://codecept.discourse.group/","https://stackoverflow.com/questions/tagged/codeceptjs","https://github.com/codeceptjs/CodeceptJS/discussions","https://github.com/codeceptjs/CodeceptJS"],"logo":{"@type":"ImageObject","url":"https://codecept.io/favicon-512.png"},"name":"","@id":"pageobjects"}</script><script type="application/ld+json">{"@context":"https://schema.org","@type":"Article","headline":"Page Objects","url":"https://codecept.io/pageobjects/","image":{"@type":"ImageObject","url":"https://codecept.io/og-image.png"},"inLanguage":"en"}</script><script>
window.StarlightThemeProvider = (() => {
const storedTheme =
typeof localStorage !== 'undefined' && localStorage.getItem('starlight-theme');
const theme =
storedTheme ||
(window.matchMedia('(prefers-color-scheme: light)').matches ? 'light' : 'dark');
document.documentElement.dataset.theme = theme === 'light' ? 'light' : 'dark';
return {
updatePickers(theme = storedTheme || 'auto') {
document.querySelectorAll('starlight-theme-select').forEach((picker) => {
const select = picker.querySelector('select');
if (select) select.value = theme;
/** @type {HTMLTemplateElement | null} */
const tmpl = document.querySelector(`#theme-icons`);
const newIcon = tmpl && tmpl.content.querySelector('.' + theme);
if (newIcon) {
const oldIcon = picker.querySelector('svg.label-icon');
if (oldIcon) {
oldIcon.replaceChildren(...newIcon.cloneNode(true).childNodes);
}
}
});
},
};
})();
</script><template id="theme-icons"><svg aria-hidden="true" class="light astro-cbi3kmoi" width="16" height="16" viewBox="0 0 24 24" fill="currentColor" style="--sl-icon-size: 1em;"><path d="M5 12a1 1 0 0 0-1-1H3a1 1 0 0 0 0 2h1a1 1 0 0 0 1-1Zm.64 5-.71.71a1 1 0 0 0 0 1.41 1 1 0 0 0 1.41 0l.71-.71A1 1 0 0 0 5.64 17ZM12 5a1 1 0 0 0 1-1V3a1 1 0 0 0-2 0v1a1 1 0 0 0 1 1Zm5.66 2.34a1 1 0 0 0 .7-.29l.71-.71a1 1 0 1 0-1.41-1.41l-.66.71a1 1 0 0 0 0 1.41 1 1 0 0 0 .66.29Zm-12-.29a1 1 0 0 0 1.41 0 1 1 0 0 0 0-1.41l-.71-.71a1.004 1.004 0 1 0-1.43 1.41l.73.71ZM21 11h-1a1 1 0 0 0 0 2h1a1 1 0 0 0 0-2Zm-2.64 6A1 1 0 0 0 17 18.36l.71.71a1 1 0 0 0 1.41 0 1 1 0 0 0 0-1.41l-.76-.66ZM12 6.5a5.5 5.5 0 1 0 5.5 5.5A5.51 5.51 0 0 0 12 6.5Zm0 9a3.5 3.5 0 1 1 0-7 3.5 3.5 0 0 1 0 7Zm0 3.5a1 1 0 0 0-1 1v1a1 1 0 0 0 2 0v-1a1 1 0 0 0-1-1Z"/></svg><svg aria-hidden="true" class="dark astro-cbi3kmoi" width="16" height="16" viewBox="0 0 24 24" fill="currentColor" style="--sl-icon-size: 1em;"><path d="M21.64 13a1 1 0 0 0-1.05-.14 8.049 8.049 0 0 1-3.37.73 8.15 8.15 0 0 1-8.14-8.1 8.59 8.59 0 0 1 .25-2A1 1 0 0 0 8 2.36a10.14 10.14 0 1 0 14 11.69 1 1 0 0 0-.36-1.05Zm-9.5 6.69A8.14 8.14 0 0 1 7.08 5.22v.27a10.15 10.15 0 0 0 10.14 10.14 9.784 9.784 0 0 0 2.1-.22 8.11 8.11 0 0 1-7.18 4.32v-.04Z"/></svg><svg aria-hidden="true" class="auto astro-cbi3kmoi" width="16" height="16" viewBox="0 0 24 24" fill="currentColor" style="--sl-icon-size: 1em;"><path d="M21 14h-1V7a3 3 0 0 0-3-3H7a3 3 0 0 0-3 3v7H3a1 1 0 0 0-1 1v2a3 3 0 0 0 3 3h14a3 3 0 0 0 3-3v-2a1 1 0 0 0-1-1ZM6 7a1 1 0 0 1 1-1h10a1 1 0 0 1 1 1v7H6V7Zm14 10a1 1 0 0 1-1 1H5a1 1 0 0 1-1-1v-1h16v1Z"/></svg></template><link rel="stylesheet" href="/_astro/print.DNXP8c50.css" media="print"><link rel="stylesheet" href="/_astro/index.w1dVAkJG.css"><script type="module" src="/_astro/page.DxliP9JT.js"></script></head> <body class="astro-qj6ycuyd"> <a href="#_top" class="astro-6tfyvusb">Skip to content</a> <div class="page sl-flex astro-4toy2ex6" data-page-slug="pageobjects"> <header id="site-header" class="header astro-4toy2ex6"> <div class="header astro-6cxfurdp"> <div class="title-wrapper sl-flex astro-6cxfurdp"> <a href="/" class="site-title sl-flex astro-idrpryed"> <img class="light:sl-hidden print:hidden astro-idrpryed" alt="CodeceptJS" src="/_astro/logo-dark.CPgSpTW_.svg" width="307" height="60"> <img class="dark:sl-hidden print:block astro-idrpryed" alt="CodeceptJS" src="/_astro/logo-light.BK93Ex33.svg" width="307" height="60"> <span class="sr-only astro-idrpryed" translate="no"> <span class="text-xl font-bold bg-clip-text text-gray-700 dark:text-gray-300 astro-idrpryed">CodeceptJS</span> </span> </a> </div> <div class="sl-flex print:hidden astro-6cxfurdp"> <div id="docsearch"></div> <script type="module" async>const apiKey = "6daf922b0e4a0992c66430766e751b1006a943857f503413ae4d36e489eeecc1";
const searchHost = "https://search.testomat.io";
const indexUid = "codeceptjs";
import { docsearch } from "https://cdn.jsdelivr.net/npm/meilisearch-docsearch@latest/dist/index.bundled.esm.js"
// web-api / mobile-api prioritization is handled server-side via
// `page_rank` + the `page_rank:desc` ranking rule in docsearch.json
// (custom_settings). No client-side hit reordering needed.
docsearch({
container: "#docsearch",
host: searchHost,
apiKey: apiKey,
indexUid: indexUid,
meilisearchOptions: {
limit: 20,
},
});
document.addEventListener("astro:before-preparation", () => {
document.querySelector(".docsearch-modal-search-cancel-btn")?.click();
});
</script> </div> <div class="sl-hidden md:sl-flex print:hidden right-group astro-6cxfurdp"> <div class="sl-flex social-icons astro-6cxfurdp"> <nav class="cc-header-nav" aria-label="Main navigation"> <button type="button" class="cc-nav-burger" aria-expanded="false" aria-label="Open navigation menu"> <svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" class="cc-nav-burger-icon cc-nav-burger-open"><line x1="4" x2="20" y1="6" y2="6"></line><line x1="4" x2="20" y1="12" y2="12"></line><line x1="4" x2="20" y1="18" y2="18"></line></svg> <svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" class="cc-nav-burger-icon cc-nav-burger-close"><line x1="18" x2="6" y1="6" y2="18"></line><line x1="6" x2="18" y1="6" y2="18"></line></svg> </button> <div class="cc-nav-links"> <a href="/quickstart/" class="cc-nav-link no-underline text-gray-700 dark:text-gray-300 hover:text-black dark:hover:text-gray-200 transition-colors">
Guides
</a> <a href="/web-api/" class="cc-nav-link no-underline text-gray-700 dark:text-gray-300 hover:text-black dark:hover:text-gray-200 transition-colors">
API
</a> <a href="/blog/codeceptjs-4/" class="cc-nav-link no-underline text-gray-700 dark:text-gray-300 hover:text-black dark:hover:text-gray-200 transition-colors">
Blog
</a> <div class="cc-nav-dropdown"> <button type="button" class="cc-nav-link cc-nav-dropdown-btn text-gray-700 dark:text-gray-300 hover:text-black dark:hover:text-gray-200 transition-colors" aria-expanded="false">
Community
<svg xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" class="cc-nav-chevron lucide lucide-chevron-down"><path d="m6 9 6 6 6-6"></path></svg> </button> <div class="cc-nav-dropdown-panel bg-white dark:bg-gray-800"> <a href="https://github.com/codeceptjs/CodeceptJS" class="cc-nav-dropdown-item no-underline text-gray-700 dark:text-gray-300 hover:text-black dark:hover:text-gray-200"> <svg xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" class="lucide lucide-github"><path d="M15 22v-4a4.8 4.8 0 0 0-1-3.5c3 0 6-2 6-5.5.08-1.25-.27-2.48-1-3.5.28-1.15.28-2.35 0-3.5 0 0-1 0-3 1.5-2.64-.5-5.36-.5-8 0C6 2 5 2 5 2c-.3 1.15-.3 2.35 0 3.5A5.403 5.403 0 0 0 4 9c0 3.5 3 5.5 6 5.5-.39.49-.68 1.05-.85 1.65-.17.6-.22 1.23-.15 1.85v4"></path><path d="M9 18c-4.51 2-5-2-7-2"></path></svg>
GitHub
</a> <a href="https://github.com/codeceptjs/CodeceptJS/discussions" class="cc-nav-dropdown-item no-underline text-gray-700 dark:text-gray-300 hover:text-black dark:hover:text-gray-200"> <svg xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" class="lucide lucide-message-circle"><path d="M7.9 20A9 9 0 1 0 4 16.1L2 22Z"></path></svg>
Discussions
</a> <a href="https://github.com/codeceptjs/CodeceptJS/issues" class="cc-nav-dropdown-item no-underline text-gray-700 dark:text-gray-300 hover:text-black dark:hover:text-gray-200"> <svg xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" class="lucide lucide-circle-dot"><circle cx="12" cy="12" r="10"></circle><circle cx="12" cy="12" r="1"></circle></svg>
Issues
</a> <a href="https://bit.ly/chat-codeceptjs" class="cc-nav-dropdown-item no-underline text-gray-700 dark:text-gray-300 hover:text-black dark:hover:text-gray-200"> <svg xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" class="lucide lucide-message-circle"><path d="M7.9 20A9 9 0 1 0 4 16.1L2 22Z"></path></svg>
Slack
</a> <div class="cc-nav-dropdown-divider"></div> <a href="/release" class="cc-nav-dropdown-item no-underline text-gray-700 dark:text-gray-300 hover:text-black dark:hover:text-gray-200"> <svg xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" class="lucide lucide-tag"><path d="M12.586 2.586A2 2 0 0 0 11.172 2H4a2 2 0 0 0-2 2v7.172a2 2 0 0 0 .586 1.414l8.704 8.704a2.426 2.426 0 0 0 3.42 0l6.58-6.58a2.426 2.426 0 0 0 0-3.42z"></path><circle cx="7.5" cy="7.5" r=".5" fill="currentColor"></circle></svg>
Releases
</a> <a href="/media-kit" class="cc-nav-dropdown-item no-underline text-gray-700 dark:text-gray-300 hover:text-black dark:hover:text-gray-200"> <img src="/logo/icon.svg" alt="" width="16" height="16">
Media Kit
</a> </div> </div> <div class="cc-nav-theme"> <span class="cc-nav-theme-label">Theme</span> <starlight-theme-toggle> <button type="button" class="cc-theme-toggle" role="switch" aria-checked="false" aria-label="Toggle dark mode" title="Toggle dark mode"> <span class="cc-theme-active" aria-hidden="true"></span> <span class="cc-theme-icon-side cc-theme-icon-sun" aria-hidden="true"> <svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="1.8" stroke-linecap="round" stroke-linejoin="round"> <circle cx="12" cy="12" r="4"></circle> <path d="M12 2v2"></path><path d="M12 20v2"></path> <path d="m4.93 4.93 1.41 1.41"></path><path d="m17.66 17.66 1.41 1.41"></path> <path d="M2 12h2"></path><path d="M20 12h2"></path> <path d="m6.34 17.66-1.41 1.41"></path><path d="m19.07 4.93-1.41 1.41"></path> </svg> </span> <span class="cc-theme-icon-side cc-theme-icon-moon" aria-hidden="true"> <svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="1.8" stroke-linecap="round" stroke-linejoin="round"> <path d="M12 3a6 6 0 0 0 9 9 9 9 0 1 1-9-9Z"></path> </svg> </span> </button> </starlight-theme-toggle> <script type="module" src="/_astro/ThemeSelect.astro_astro_type_script_index_0_lang.Bo6WLquL.js"></script> </div> </div> </nav> <script type="module" src="/_astro/Links.astro_astro_type_script_index_0_lang.BujyuUnY.js"></script> </div> <starlight-theme-toggle> <button type="button" class="cc-theme-toggle" role="switch" aria-checked="false" aria-label="Toggle dark mode" title="Toggle dark mode"> <span class="cc-theme-active" aria-hidden="true"></span> <span class="cc-theme-icon-side cc-theme-icon-sun" aria-hidden="true"> <svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="1.8" stroke-linecap="round" stroke-linejoin="round"> <circle cx="12" cy="12" r="4"></circle> <path d="M12 2v2"></path><path d="M12 20v2"></path> <path d="m4.93 4.93 1.41 1.41"></path><path d="m17.66 17.66 1.41 1.41"></path> <path d="M2 12h2"></path><path d="M20 12h2"></path> <path d="m6.34 17.66-1.41 1.41"></path><path d="m19.07 4.93-1.41 1.41"></path> </svg> </span> <span class="cc-theme-icon-side cc-theme-icon-moon" aria-hidden="true"> <svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="1.8" stroke-linecap="round" stroke-linejoin="round"> <path d="M12 3a6 6 0 0 0 9 9 9 9 0 1 1-9-9Z"></path> </svg> </span> </button> </starlight-theme-toggle> <script type="module" src="/_astro/LanguageSelect.astro_astro_type_script_index_0_lang.Ce-i7NLC.js"></script> </div> </div> </header> <nav class="sidebar print:hidden astro-4toy2ex6" aria-label="Main"> <starlight-menu-button class="print:hidden astro-5jxskpfx"> <button aria-expanded="false" aria-label="Menu" aria-controls="starlight__sidebar" class="sl-flex md:sl-hidden astro-5jxskpfx"> <svg aria-hidden="true" class="open-menu astro-5jxskpfx astro-cbi3kmoi" width="16" height="16" viewBox="0 0 24 24" fill="currentColor" style="--sl-icon-size: 1em;"><path d="M3 8h18a1 1 0 1 0 0-2H3a1 1 0 0 0 0 2Zm18 8H3a1 1 0 0 0 0 2h18a1 1 0 0 0 0-2Zm0-5H3a1 1 0 0 0 0 2h18a1 1 0 0 0 0-2Z"/></svg> <svg aria-hidden="true" class="close-menu astro-5jxskpfx astro-cbi3kmoi" width="16" height="16" viewBox="0 0 24 24" fill="currentColor" style="--sl-icon-size: 1em;"><path d="m13.41 12 6.3-6.29a1.004 1.004 0 1 0-1.42-1.42L12 10.59l-6.29-6.3a1.004 1.004 0 0 0-1.42 1.42l6.3 6.29-6.3 6.29a1 1 0 0 0 0 1.42.998.998 0 0 0 1.42 0l6.29-6.3 6.29 6.3a.999.999 0 0 0 1.42 0 1 1 0 0 0 0-1.42L13.41 12Z"/></svg> </button> </starlight-menu-button> <script type="module" src="/_astro/MobileMenuToggle.astro_astro_type_script_index_0_lang.CsfLbggW.js"></script> <div id="starlight__sidebar" class="sidebar-pane astro-4toy2ex6"> <div class="sidebar-content sl-flex astro-4toy2ex6"> <ul class="starlight-sidebar-topics astro-mqaomtvk"> <li class="astro-mqaomtvk"> <a href="/quickstart/" class="starlight-sidebar-topics-current astro-mqaomtvk"> <div class="astro-mqaomtvk"> Guides </div> </a> </li><li class="astro-mqaomtvk"> <a href="/web-api/" class="astro-mqaomtvk"> <div class="astro-mqaomtvk"> Reference </div> </a> </li><li class="astro-mqaomtvk"> <a href="/blog/codeceptjs-4/" class="astro-mqaomtvk"> <div class="astro-mqaomtvk"> Blog </div> </a> </li> </ul> <sl-sidebar-state-persist data-hash="0cfxxxo" class="astro-xy2ooljs"> <script aria-hidden="true">
(() => {
try {
if (!matchMedia('(min-width: 50em)').matches) return;
/** @type {HTMLElement | null} */
const target = document.querySelector('sl-sidebar-state-persist');
const state = JSON.parse(sessionStorage.getItem('sl-sidebar-state') || '0');
if (!target || !state || target.dataset.hash !== state.hash) return;
window._starlightScrollRestore = state.scroll;
customElements.define(
'sl-sidebar-restore',
class SidebarRestore extends HTMLElement {
connectedCallback() {
try {
const idx = parseInt(this.dataset.index || '');
const details = this.closest('details');
if (details && typeof state.open[idx] === 'boolean') details.open = state.open[idx];
} catch {}
}
}
);
} catch {}
})();
</script> <ul class="top-level astro-ova6qb4d"> <li class="astro-ova6qb4d"> <details open class="astro-ova6qb4d"> <summary class="astro-ova6qb4d"> <span class="group-label astro-ova6qb4d"> <span class="large astro-ova6qb4d">Setup</span> </span> <svg aria-hidden="true" class="caret astro-ova6qb4d astro-cbi3kmoi" width="16" height="16" viewBox="0 0 24 24" fill="currentColor" style="--sl-icon-size: 1.25rem;"><path d="m14.83 11.29-4.24-4.24a1 1 0 1 0-1.42 1.41L12.71 12l-3.54 3.54a1 1 0 0 0 0 1.41 1 1 0 0 0 .71.29 1 1 0 0 0 .71-.29l4.24-4.24a1.002 1.002 0 0 0 0-1.42Z"/></svg> </summary> <sl-sidebar-restore data-index="0"></sl-sidebar-restore> <ul class="astro-ova6qb4d"> <li class="astro-ova6qb4d"> <a href="/quickstart" aria-current="false" class="astro-ova6qb4d"> <span class="astro-ova6qb4d">Quickstart</span> </a> </li><li class="astro-ova6qb4d"> <a href="/installation" aria-current="false" class="astro-ova6qb4d"> <span class="astro-ova6qb4d">Installation</span> </a> </li><li class="astro-ova6qb4d"> <a href="/migration-4" aria-current="false" class="astro-ova6qb4d"> <span class="astro-ova6qb4d">Upgrade</span> </a> </li><li class="astro-ova6qb4d"> <a href="/migrate-from-cypress" aria-current="false" class="astro-ova6qb4d"> <span class="astro-ova6qb4d">Migrate from Cypress.io</span> </a> </li><li class="astro-ova6qb4d"> <a href="/migrate-from-java" aria-current="false" class="astro-ova6qb4d"> <span class="astro-ova6qb4d">Migrate from Java Selenium</span> </a> </li><li class="astro-ova6qb4d"> <a href="/migrate-from-testcafe" aria-current="false" class="astro-ova6qb4d"> <span class="astro-ova6qb4d">Migrate from TestCafe</span> </a> </li><li class="astro-ova6qb4d"> <a href="/migrate-from-protractor" aria-current="false" class="astro-ova6qb4d"> <span class="astro-ova6qb4d">Migrate from Protractor</span> </a> </li> </ul> </details> </li><li class="astro-ova6qb4d"> <details open class="astro-ova6qb4d"> <summary class="astro-ova6qb4d"> <span class="group-label astro-ova6qb4d"> <span class="large astro-ova6qb4d">Basics</span> </span> <svg aria-hidden="true" class="caret astro-ova6qb4d astro-cbi3kmoi" width="16" height="16" viewBox="0 0 24 24" fill="currentColor" style="--sl-icon-size: 1.25rem;"><path d="m14.83 11.29-4.24-4.24a1 1 0 1 0-1.42 1.41L12.71 12l-3.54 3.54a1 1 0 0 0 0 1.41 1 1 0 0 0 .71.29 1 1 0 0 0 .71-.29l4.24-4.24a1.002 1.002 0 0 0 0-1.42Z"/></svg> </summary> <sl-sidebar-restore data-index="1"></sl-sidebar-restore> <ul class="astro-ova6qb4d"> <li class="astro-ova6qb4d"> <a href="/basics" aria-current="false" class="astro-ova6qb4d"> <span class="astro-ova6qb4d">Web Basics</span> </a> </li><li class="astro-ova6qb4d"> <a href="/agents" aria-current="false" class="astro-ova6qb4d"> <span class="astro-ova6qb4d">Agentic Testing</span> </a> </li><li class="astro-ova6qb4d"> <a href="/test-structure" aria-current="false" class="astro-ova6qb4d"> <span class="astro-ova6qb4d">Test Structure</span> </a> </li><li class="astro-ova6qb4d"> <a href="/locators" aria-current="false" class="astro-ova6qb4d"> <span class="astro-ova6qb4d">Locators</span> </a> </li><li class="astro-ova6qb4d"> <a href="/assertions" aria-current="false" class="astro-ova6qb4d"> <span class="astro-ova6qb4d">Assertions</span> </a> </li><li class="astro-ova6qb4d"> <a href="/element-based-testing" aria-current="false" class="astro-ova6qb4d"> <span class="astro-ova6qb4d">Element Testing</span> </a> </li><li class="astro-ova6qb4d"> <a href="/debugging" aria-current="false" class="astro-ova6qb4d"> <span class="astro-ova6qb4d">Debugging</span> </a> </li> </ul> </details> </li><li class="astro-ova6qb4d"> <details open class="astro-ova6qb4d"> <summary class="astro-ova6qb4d"> <span class="group-label astro-ova6qb4d"> <span class="large astro-ova6qb4d">Web & Mobile Testing</span> </span> <svg aria-hidden="true" class="caret astro-ova6qb4d astro-cbi3kmoi" width="16" height="16" viewBox="0 0 24 24" fill="currentColor" style="--sl-icon-size: 1.25rem;"><path d="m14.83 11.29-4.24-4.24a1 1 0 1 0-1.42 1.41L12.71 12l-3.54 3.54a1 1 0 0 0 0 1.41 1 1 0 0 0 .71.29 1 1 0 0 0 .71-.29l4.24-4.24a1.002 1.002 0 0 0 0-1.42Z"/></svg> </summary> <sl-sidebar-restore data-index="2"></sl-sidebar-restore> <ul class="astro-ova6qb4d"> <li class="astro-ova6qb4d"> <a href="/playwright" aria-current="false" class="astro-ova6qb4d"> <span class="astro-ova6qb4d">Testing with Playwright</span> </a> </li><li class="astro-ova6qb4d"> <a href="/webdriver" aria-current="false" class="astro-ova6qb4d"> <span class="astro-ova6qb4d">Testing with WebDriver</span> </a> </li><li class="astro-ova6qb4d"> <a href="/puppeteer" aria-current="false" class="astro-ova6qb4d"> <span class="astro-ova6qb4d">Testing with Puppeteer</span> </a> </li><li class="astro-ova6qb4d"> <a href="/mobile" aria-current="false" class="astro-ova6qb4d"> <span class="astro-ova6qb4d">Mobile Testing with Appium</span> </a> </li><li class="astro-ova6qb4d"> <a href="/detox" aria-current="false" class="astro-ova6qb4d"> <span class="astro-ova6qb4d">Testing React Native with Detox</span> </a> </li> </ul> </details> </li><li class="astro-ova6qb4d"> <details open class="astro-ova6qb4d"> <summary class="astro-ova6qb4d"> <span class="group-label astro-ova6qb4d"> <span class="large astro-ova6qb4d">Organizing Tests</span> </span> <svg aria-hidden="true" class="caret astro-ova6qb4d astro-cbi3kmoi" width="16" height="16" viewBox="0 0 24 24" fill="currentColor" style="--sl-icon-size: 1.25rem;"><path d="m14.83 11.29-4.24-4.24a1 1 0 1 0-1.42 1.41L12.71 12l-3.54 3.54a1 1 0 0 0 0 1.41 1 1 0 0 0 .71.29 1 1 0 0 0 .71-.29l4.24-4.24a1.002 1.002 0 0 0 0-1.42Z"/></svg> </summary> <sl-sidebar-restore data-index="3"></sl-sidebar-restore> <ul class="astro-ova6qb4d"> <li class="astro-ova6qb4d"> <a href="/pageobjects" aria-current="page" class="astro-ova6qb4d"> <span class="astro-ova6qb4d">Page Objects</span> </a> </li><li class="astro-ova6qb4d"> <a href="/data" aria-current="false" class="astro-ova6qb4d"> <span class="astro-ova6qb4d">Data Management</span> </a> </li><li class="astro-ova6qb4d"> <a href="/bdd" aria-current="false" class="astro-ova6qb4d"> <span class="astro-ova6qb4d">Behavior Driven Development</span> </a> </li><li class="astro-ova6qb4d"> <a href="/best" aria-current="false" class="astro-ova6qb4d"> <span class="astro-ova6qb4d">Best Practices</span> </a> </li> </ul> </details> </li><li class="astro-ova6qb4d"> <details open class="astro-ova6qb4d"> <summary class="astro-ova6qb4d"> <span class="group-label astro-ova6qb4d"> <span class="large astro-ova6qb4d">Advanced Usage</span> </span> <svg aria-hidden="true" class="caret astro-ova6qb4d astro-cbi3kmoi" width="16" height="16" viewBox="0 0 24 24" fill="currentColor" style="--sl-icon-size: 1.25rem;"><path d="m14.83 11.29-4.24-4.24a1 1 0 1 0-1.42 1.41L12.71 12l-3.54 3.54a1 1 0 0 0 0 1.41 1 1 0 0 0 .71.29 1 1 0 0 0 .71-.29l4.24-4.24a1.002 1.002 0 0 0 0-1.42Z"/></svg> </summary> <sl-sidebar-restore data-index="4"></sl-sidebar-restore> <ul class="astro-ova6qb4d"> <li class="astro-ova6qb4d"> <a href="/api" aria-current="false" class="astro-ova6qb4d"> <span class="astro-ova6qb4d">API Testing</span> </a> </li><li class="astro-ova6qb4d"> <a href="/architecture" aria-current="false" class="astro-ova6qb4d"> <span class="astro-ova6qb4d">Architecture</span> </a> </li><li class="astro-ova6qb4d"> <a href="/bootstrap" aria-current="false" class="astro-ova6qb4d"> <span class="astro-ova6qb4d">Bootstrap</span> </a> </li><li class="astro-ova6qb4d"> <a href="/continuous-integration" aria-current="false" class="astro-ova6qb4d"> <span class="astro-ova6qb4d">Continuous Integration</span> </a> </li><li class="astro-ova6qb4d"> <a href="/docker" aria-current="false" class="astro-ova6qb4d"> <span class="astro-ova6qb4d">Docker</span> </a> </li><li class="astro-ova6qb4d"> <a href="/effects" aria-current="false" class="astro-ova6qb4d"> <span class="astro-ova6qb4d">Effects</span> </a> </li><li class="astro-ova6qb4d"> <a href="/element-selection" aria-current="false" class="astro-ova6qb4d"> <span class="astro-ova6qb4d">Element Selection</span> </a> </li><li class="astro-ova6qb4d"> <a href="/ai" aria-current="false" class="astro-ova6qb4d"> <span class="astro-ova6qb4d">Enable AI</span> </a> </li><li class="astro-ova6qb4d"> <a href="/hooks" aria-current="false" class="astro-ova6qb4d"> <span class="astro-ova6qb4d">Extending</span> </a> </li><li class="astro-ova6qb4d"> <a href="/parallel" aria-current="false" class="astro-ova6qb4d"> <span class="astro-ova6qb4d">Parallel Execution</span> </a> </li><li class="astro-ova6qb4d"> <a href="/reports" aria-current="false" class="astro-ova6qb4d"> <span class="astro-ova6qb4d">Reporting</span> </a> </li><li class="astro-ova6qb4d"> <a href="/retry" aria-current="false" class="astro-ova6qb4d"> <span class="astro-ova6qb4d">Retries</span> </a> </li><li class="astro-ova6qb4d"> <a href="/secrets" aria-current="false" class="astro-ova6qb4d"> <span class="astro-ova6qb4d">Secrets</span> </a> </li><li class="astro-ova6qb4d"> <a href="/heal" aria-current="false" class="astro-ova6qb4d"> <span class="astro-ova6qb4d">Self-Healing</span> </a> </li><li class="astro-ova6qb4d"> <a href="/timeouts" aria-current="false" class="astro-ova6qb4d"> <span class="astro-ova6qb4d">Timeouts</span> </a> </li><li class="astro-ova6qb4d"> <a href="/typescript" aria-current="false" class="astro-ova6qb4d"> <span class="astro-ova6qb4d">TypeScript</span> </a> </li><li class="astro-ova6qb4d"> <a href="/within" aria-current="false" class="astro-ova6qb4d"> <span class="astro-ova6qb4d">Within</span> </a> </li><li class="astro-ova6qb4d"> <a href="/shadow" aria-current="false" class="astro-ova6qb4d"> <span class="astro-ova6qb4d">Shadow DOM</span> </a> </li><li class="astro-ova6qb4d"> <a href="/translation" aria-current="false" class="astro-ova6qb4d"> <span class="astro-ova6qb4d">Translation</span> </a> </li> </ul> </details> </li><li class="astro-ova6qb4d"> <details open class="astro-ova6qb4d"> <summary class="astro-ova6qb4d"> <span class="group-label astro-ova6qb4d"> <span class="large astro-ova6qb4d">Resources</span> </span> <svg aria-hidden="true" class="caret astro-ova6qb4d astro-cbi3kmoi" width="16" height="16" viewBox="0 0 24 24" fill="currentColor" style="--sl-icon-size: 1.25rem;"><path d="m14.83 11.29-4.24-4.24a1 1 0 1 0-1.42 1.41L12.71 12l-3.54 3.54a1 1 0 0 0 0 1.41 1 1 0 0 0 .71.29 1 1 0 0 0 .71-.29l4.24-4.24a1.002 1.002 0 0 0 0-1.42Z"/></svg> </summary> <sl-sidebar-restore data-index="5"></sl-sidebar-restore> <ul class="astro-ova6qb4d"> <li class="astro-ova6qb4d"> <a href="/tutorial" aria-current="false" class="astro-ova6qb4d"> <span class="astro-ova6qb4d">Tutorial</span> </a> </li><li class="astro-ova6qb4d"> <a href="/cheatsheet" aria-current="false" class="astro-ova6qb4d"> <span class="astro-ova6qb4d">Cheatsheet</span> </a> </li><li class="astro-ova6qb4d"> <a href="/media-kit" aria-current="false" class="astro-ova6qb4d"> <span class="astro-ova6qb4d">Media Kit</span> </a> </li><li class="astro-ova6qb4d"> <a href="/release" aria-current="false" class="astro-ova6qb4d"> <span class="astro-ova6qb4d">Release Notes</span> </a> </li> </ul> </details> </li> </ul> <script aria-hidden="true">
(() => {
const scroller = document.getElementById('starlight__sidebar');
if (!window._starlightScrollRestore || !scroller) return;
scroller.scrollTop = window._starlightScrollRestore;
delete window._starlightScrollRestore;
})();
</script> </sl-sidebar-state-persist> <div class="md:sl-hidden"> <div class="mobile-preferences sl-flex astro-yw2ikf7j"> <div class="social-icons astro-yw2ikf7j"> <nav class="cc-header-nav" aria-label="Main navigation"> <button type="button" class="cc-nav-burger" aria-expanded="false" aria-label="Open navigation menu"> <svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" class="cc-nav-burger-icon cc-nav-burger-open"><line x1="4" x2="20" y1="6" y2="6"></line><line x1="4" x2="20" y1="12" y2="12"></line><line x1="4" x2="20" y1="18" y2="18"></line></svg> <svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" class="cc-nav-burger-icon cc-nav-burger-close"><line x1="18" x2="6" y1="6" y2="18"></line><line x1="6" x2="18" y1="6" y2="18"></line></svg> </button> <div class="cc-nav-links"> <a href="/quickstart/" class="cc-nav-link no-underline text-gray-700 dark:text-gray-300 hover:text-black dark:hover:text-gray-200 transition-colors">
Guides
</a> <a href="/web-api/" class="cc-nav-link no-underline text-gray-700 dark:text-gray-300 hover:text-black dark:hover:text-gray-200 transition-colors">
API
</a> <a href="/blog/codeceptjs-4/" class="cc-nav-link no-underline text-gray-700 dark:text-gray-300 hover:text-black dark:hover:text-gray-200 transition-colors">
Blog
</a> <div class="cc-nav-dropdown"> <button type="button" class="cc-nav-link cc-nav-dropdown-btn text-gray-700 dark:text-gray-300 hover:text-black dark:hover:text-gray-200 transition-colors" aria-expanded="false">
Community
<svg xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" class="cc-nav-chevron lucide lucide-chevron-down"><path d="m6 9 6 6 6-6"></path></svg> </button> <div class="cc-nav-dropdown-panel bg-white dark:bg-gray-800"> <a href="https://github.com/codeceptjs/CodeceptJS" class="cc-nav-dropdown-item no-underline text-gray-700 dark:text-gray-300 hover:text-black dark:hover:text-gray-200"> <svg xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" class="lucide lucide-github"><path d="M15 22v-4a4.8 4.8 0 0 0-1-3.5c3 0 6-2 6-5.5.08-1.25-.27-2.48-1-3.5.28-1.15.28-2.35 0-3.5 0 0-1 0-3 1.5-2.64-.5-5.36-.5-8 0C6 2 5 2 5 2c-.3 1.15-.3 2.35 0 3.5A5.403 5.403 0 0 0 4 9c0 3.5 3 5.5 6 5.5-.39.49-.68 1.05-.85 1.65-.17.6-.22 1.23-.15 1.85v4"></path><path d="M9 18c-4.51 2-5-2-7-2"></path></svg>
GitHub
</a> <a href="https://github.com/codeceptjs/CodeceptJS/discussions" class="cc-nav-dropdown-item no-underline text-gray-700 dark:text-gray-300 hover:text-black dark:hover:text-gray-200"> <svg xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" class="lucide lucide-message-circle"><path d="M7.9 20A9 9 0 1 0 4 16.1L2 22Z"></path></svg>
Discussions
</a> <a href="https://github.com/codeceptjs/CodeceptJS/issues" class="cc-nav-dropdown-item no-underline text-gray-700 dark:text-gray-300 hover:text-black dark:hover:text-gray-200"> <svg xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" class="lucide lucide-circle-dot"><circle cx="12" cy="12" r="10"></circle><circle cx="12" cy="12" r="1"></circle></svg>
Issues
</a> <a href="https://bit.ly/chat-codeceptjs" class="cc-nav-dropdown-item no-underline text-gray-700 dark:text-gray-300 hover:text-black dark:hover:text-gray-200"> <svg xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" class="lucide lucide-message-circle"><path d="M7.9 20A9 9 0 1 0 4 16.1L2 22Z"></path></svg>
Slack
</a> <div class="cc-nav-dropdown-divider"></div> <a href="/release" class="cc-nav-dropdown-item no-underline text-gray-700 dark:text-gray-300 hover:text-black dark:hover:text-gray-200"> <svg xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" class="lucide lucide-tag"><path d="M12.586 2.586A2 2 0 0 0 11.172 2H4a2 2 0 0 0-2 2v7.172a2 2 0 0 0 .586 1.414l8.704 8.704a2.426 2.426 0 0 0 3.42 0l6.58-6.58a2.426 2.426 0 0 0 0-3.42z"></path><circle cx="7.5" cy="7.5" r=".5" fill="currentColor"></circle></svg>
Releases
</a> <a href="/media-kit" class="cc-nav-dropdown-item no-underline text-gray-700 dark:text-gray-300 hover:text-black dark:hover:text-gray-200"> <img src="/logo/icon.svg" alt="" width="16" height="16">
Media Kit
</a> </div> </div> <div class="cc-nav-theme"> <span class="cc-nav-theme-label">Theme</span> <starlight-theme-toggle> <button type="button" class="cc-theme-toggle" role="switch" aria-checked="false" aria-label="Toggle dark mode" title="Toggle dark mode"> <span class="cc-theme-active" aria-hidden="true"></span> <span class="cc-theme-icon-side cc-theme-icon-sun" aria-hidden="true"> <svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="1.8" stroke-linecap="round" stroke-linejoin="round"> <circle cx="12" cy="12" r="4"></circle> <path d="M12 2v2"></path><path d="M12 20v2"></path> <path d="m4.93 4.93 1.41 1.41"></path><path d="m17.66 17.66 1.41 1.41"></path> <path d="M2 12h2"></path><path d="M20 12h2"></path> <path d="m6.34 17.66-1.41 1.41"></path><path d="m19.07 4.93-1.41 1.41"></path> </svg> </span> <span class="cc-theme-icon-side cc-theme-icon-moon" aria-hidden="true"> <svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="1.8" stroke-linecap="round" stroke-linejoin="round"> <path d="M12 3a6 6 0 0 0 9 9 9 9 0 1 1-9-9Z"></path> </svg> </span> </button> </starlight-theme-toggle> </div> </div> </nav> </div> <starlight-theme-toggle> <button type="button" class="cc-theme-toggle" role="switch" aria-checked="false" aria-label="Toggle dark mode" title="Toggle dark mode"> <span class="cc-theme-active" aria-hidden="true"></span> <span class="cc-theme-icon-side cc-theme-icon-sun" aria-hidden="true"> <svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="1.8" stroke-linecap="round" stroke-linejoin="round"> <circle cx="12" cy="12" r="4"></circle> <path d="M12 2v2"></path><path d="M12 20v2"></path> <path d="m4.93 4.93 1.41 1.41"></path><path d="m17.66 17.66 1.41 1.41"></path> <path d="M2 12h2"></path><path d="M20 12h2"></path> <path d="m6.34 17.66-1.41 1.41"></path><path d="m19.07 4.93-1.41 1.41"></path> </svg> </span> <span class="cc-theme-icon-side cc-theme-icon-moon" aria-hidden="true"> <svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="1.8" stroke-linecap="round" stroke-linejoin="round"> <path d="M12 3a6 6 0 0 0 9 9 9 9 0 1 1-9-9Z"></path> </svg> </span> </button> </starlight-theme-toggle> </div> </div> </div> </div> </nav> <div class="main-frame astro-4toy2ex6"> <script type="module" src="/_astro/Page.astro_astro_type_script_index_0_lang.BHQeG8Vj.js"></script> <div class="lg:sl-flex astro-4deqquyo"> <aside class="right-sidebar-container print:hidden astro-4deqquyo"> <div class="right-sidebar astro-4deqquyo"> <div class="lg:sl-hidden astro-nzouaqej"><mobile-starlight-toc data-min-h="2" data-max-h="3" class="astro-g3oy5c6o"><nav aria-labelledby="starlight__on-this-page--mobile" class="astro-g3oy5c6o"><details id="starlight__mobile-toc" class="astro-g3oy5c6o"><summary id="starlight__on-this-page--mobile" class="sl-flex astro-g3oy5c6o"><span class="toggle sl-flex astro-g3oy5c6o">On this page<svg aria-hidden="true" class="caret astro-g3oy5c6o astro-cbi3kmoi" width="16" height="16" viewBox="0 0 24 24" fill="currentColor" style="--sl-icon-size: 1rem;"><path d="m14.83 11.29-4.24-4.24a1 1 0 1 0-1.42 1.41L12.71 12l-3.54 3.54a1 1 0 0 0 0 1.41 1 1 0 0 0 .71.29 1 1 0 0 0 .71-.29l4.24-4.24a1.002 1.002 0 0 0 0-1.42Z"/></svg></span><span class="display-current astro-g3oy5c6o"></span></summary><div class="dropdown astro-g3oy5c6o"><ul class="isMobile astro-vfcvrjst" style="--depth: 0;"> <li class="astro-vfcvrjst" style="--depth: 0;"> <a href="#_top" class="astro-vfcvrjst" style="--depth: 0;"> <span class="astro-vfcvrjst" style="--depth: 0;">Overview</span> </a> </li><li class="astro-vfcvrjst" style="--depth: 0;"> <a href="#dependency-injection" class="astro-vfcvrjst" style="--depth: 0;"> <span class="astro-vfcvrjst" style="--depth: 0;">Dependency Injection</span> </a> </li><li class="astro-vfcvrjst" style="--depth: 0;"> <a href="#actor" class="astro-vfcvrjst" style="--depth: 0;"> <span class="astro-vfcvrjst" style="--depth: 0;">Actor</span> </a> </li><li class="astro-vfcvrjst" style="--depth: 0;"> <a href="#pageobject" class="astro-vfcvrjst" style="--depth: 0;"> <span class="astro-vfcvrjst" style="--depth: 0;">PageObject</span> </a> </li><li class="astro-vfcvrjst" style="--depth: 0;"> <a href="#page-object-lifecycle-hooks" class="astro-vfcvrjst" style="--depth: 0;"> <span class="astro-vfcvrjst" style="--depth: 0;">Page Object Lifecycle Hooks</span> </a> </li><li class="astro-vfcvrjst" style="--depth: 0;"> <a href="#page-fragments" class="astro-vfcvrjst" style="--depth: 0;"> <span class="astro-vfcvrjst" style="--depth: 0;">Page Fragments</span> </a> </li><li class="astro-vfcvrjst" style="--depth: 0;"> <a href="#stepobjects" class="astro-vfcvrjst" style="--depth: 0;"> <span class="astro-vfcvrjst" style="--depth: 0;">StepObjects</span> </a> </li><li class="astro-vfcvrjst" style="--depth: 0;"> <a href="#data-objects" class="astro-vfcvrjst" style="--depth: 0;"> <span class="astro-vfcvrjst" style="--depth: 0;">Data Objects</span> </a> </li><li class="astro-vfcvrjst" style="--depth: 0;"> <a href="#dynamic-injection" class="astro-vfcvrjst" style="--depth: 0;"> <span class="astro-vfcvrjst" style="--depth: 0;">Dynamic Injection</span> </a> </li><li class="astro-vfcvrjst" style="--depth: 0;"> <a href="#plain-object-page-objects-legacy" class="astro-vfcvrjst" style="--depth: 0;"> <span class="astro-vfcvrjst" style="--depth: 0;">Plain Object Page Objects (Legacy)</span> </a> </li> </ul> </div></details></nav></mobile-starlight-toc><script type="module" src="/_astro/MobileTableOfContents.astro_astro_type_script_index_0_lang.C181hMzK.js"></script></div><div class="right-sidebar-panel sl-hidden lg:sl-block astro-nzouaqej"><div class="sl-container astro-nzouaqej"><starlight-toc data-min-h="2" data-max-h="3"><nav aria-labelledby="starlight__on-this-page"><h2 id="starlight__on-this-page">On this page</h2><ul class="astro-vfcvrjst" style="--depth: 0;"> <li class="astro-vfcvrjst" style="--depth: 0;"> <a href="#_top" class="astro-vfcvrjst" style="--depth: 0;"> <span class="astro-vfcvrjst" style="--depth: 0;">Overview</span> </a> </li><li class="astro-vfcvrjst" style="--depth: 0;"> <a href="#dependency-injection" class="astro-vfcvrjst" style="--depth: 0;"> <span class="astro-vfcvrjst" style="--depth: 0;">Dependency Injection</span> </a> </li><li class="astro-vfcvrjst" style="--depth: 0;"> <a href="#actor" class="astro-vfcvrjst" style="--depth: 0;"> <span class="astro-vfcvrjst" style="--depth: 0;">Actor</span> </a> </li><li class="astro-vfcvrjst" style="--depth: 0;"> <a href="#pageobject" class="astro-vfcvrjst" style="--depth: 0;"> <span class="astro-vfcvrjst" style="--depth: 0;">PageObject</span> </a> </li><li class="astro-vfcvrjst" style="--depth: 0;"> <a href="#page-object-lifecycle-hooks" class="astro-vfcvrjst" style="--depth: 0;"> <span class="astro-vfcvrjst" style="--depth: 0;">Page Object Lifecycle Hooks</span> </a> </li><li class="astro-vfcvrjst" style="--depth: 0;"> <a href="#page-fragments" class="astro-vfcvrjst" style="--depth: 0;"> <span class="astro-vfcvrjst" style="--depth: 0;">Page Fragments</span> </a> </li><li class="astro-vfcvrjst" style="--depth: 0;"> <a href="#stepobjects" class="astro-vfcvrjst" style="--depth: 0;"> <span class="astro-vfcvrjst" style="--depth: 0;">StepObjects</span> </a> </li><li class="astro-vfcvrjst" style="--depth: 0;"> <a href="#data-objects" class="astro-vfcvrjst" style="--depth: 0;"> <span class="astro-vfcvrjst" style="--depth: 0;">Data Objects</span> </a> </li><li class="astro-vfcvrjst" style="--depth: 0;"> <a href="#dynamic-injection" class="astro-vfcvrjst" style="--depth: 0;"> <span class="astro-vfcvrjst" style="--depth: 0;">Dynamic Injection</span> </a> </li><li class="astro-vfcvrjst" style="--depth: 0;"> <a href="#plain-object-page-objects-legacy" class="astro-vfcvrjst" style="--depth: 0;"> <span class="astro-vfcvrjst" style="--depth: 0;">Plain Object Page Objects (Legacy)</span> </a> </li> </ul> </nav></starlight-toc><script type="module" src="/_astro/TableOfContents.astro_astro_type_script_index_0_lang.CKWWgpjV.js"></script></div></div> </div> </aside> <div class="main-pane astro-4deqquyo"> <main data-pagefind-body class="astro-qj6ycuyd" lang="en" dir="ltr"> <div class="content-panel astro-7vmgf2si"> <div class="sl-container astro-7vmgf2si"> <div class="astro-guvttfii"></div><h1 id="_top" class="astro-bmayhthr">Page Objects</h1> </div> </div> <div class="content-panel astro-7vmgf2si"> <div class="sl-container astro-7vmgf2si"> <starlight-image-zoom> <template class="astro-qteoq5nn"> <dialog class="starlight-image-zoom-dialog astro-qteoq5nn"> <button aria-label="Unzoom image" class="starlight-image-zoom-control astro-qteoq5nn"> <svg aria-hidden="true" fill="currentColor" viewBox="0 0 24 24" class="astro-qteoq5nn"> <use href="#starlight-image-zoom-icon-unzoom" class="astro-qteoq5nn"></use> </svg> </button> <figure class="astro-qteoq5nn"></figure> </dialog> </template> <svg xmlns="http://www.w3.org/2000/svg" style="display:none" class="astro-qteoq5nn"> <symbol id="starlight-image-zoom-icon-zoom" viewBox="0 0 24 24" class="astro-qteoq5nn"> <path d="M9.79 12.79 4 18.59V17a1 1 0 0 0-2 0v4a1 1 0 0 0 .08.38 1 1 0 0 0 .54.54A1 1 0 0 0 3 22h4a1 1 0 0 0 0-2H5.41l5.8-5.79a1 1 0 0 0-1.42-1.42ZM21.92 2.62a1 1 0 0 0-.54-.54A1 1 0 0 0 21 2h-4a1 1 0 0 0 0 2h1.59l-5.8 5.79a1 1 0 0 0 0 1.42 1 1 0 0 0 1.42 0L20 5.41V7a1 1 0 0 0 2 0V3a1 1 0 0 0-.08-.38Z" class="astro-qteoq5nn"></path> </symbol> <symbol id="starlight-image-zoom-icon-unzoom" viewBox="0 0 24 24" class="astro-qteoq5nn"> <path d="M21.71 2.29a1 1 0 0 0-1.42 0l-5.79 5.8V6.5a1 1 0 0 0-2 0v4a1 1 0 0 0 .08.38 1 1 0 0 0 .54.54 1 1 0 0 0 .38.08h4a1 1 0 0 0 0-2h-1.59l5.8-5.79a1 1 0 0 0 0-1.42ZM10.88 12.58a1 1 0 0 0-.38-.08h-4a1 1 0 0 0 0 2h1.59l-5.8 5.79a1 1 0 0 0 0 1.42 1 1 0 0 0 1.42 0l5.79-5.8v1.59a1 1 0 0 0 2 0v-4a1 1 0 0 0-.08-.38 1 1 0 0 0-.54-.54Z" class="astro-qteoq5nn"></path> </symbol> </svg> </starlight-image-zoom> <script type="module" src="/_astro/ImageZoom.astro_astro_type_script_index_0_lang.J1tlcSSG.js"></script> <div class="sl-markdown-content"> <!-- Auto-generated by scripts/sync-codeceptjs-docs.mjs from codeceptjs/CodeceptJS@4.x. Do not edit. -->
<p>The UI of your web application has interaction areas which can be shared across different tests.
To avoid code duplication you can put common locators and methods in one place.</p>
<figure class="injected-figure"><starlight-image-zoom-zoomable><svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 1290 420" class="injected-fig-pageobjects-di" role="img" aria-label="CodeceptJS dependency injection: register page objects in codecept.conf.js, inject them by name into Scenarios or other page objects via inject(); inject() returns lazy proxies, so circular page-object references that a plain import would leave undefined resolve at call time."><title>CodeceptJS dependency injection: register page objects in codecept.conf.js, inject them by name into Scenarios or other page objects via inject(); inject() returns lazy proxies, so circular page-object references that a plain import would leave undefined resolve at call time.</title>
<defs>
<filter id="ambient" x="-10%" y="-10%" width="120%" height="120%">
<feDropShadow dx="0" dy="4" stdDeviation="9" flood-color="#1a1d28" flood-opacity="0.12"></feDropShadow>
</filter>
<marker id="arrowGold" viewBox="0 0 10 10" refX="9" refY="5" markerWidth="7" markerHeight="7" orient="auto" visibility="visible">
<path d="M0,1 L9,5 L0,9 z" fill="#C88800" visibility="visible"></path>
</marker>
</defs>
<style>@import url('https://fonts.googleapis.com/css2?family=Bricolage+Grotesque:opsz,wght@12..96,400..800&family=Inter:wght@400;500;600;700&family=JetBrains+Mono:wght@400..700&display=swap');
.injected-fig-pageobjects-di .title { font-family: 'Bricolage Grotesque', sans-serif; font-weight: 700; font-size: 26px; letter-spacing: -0.025em; font-feature-settings: 'ss01','ss02'; }
.injected-fig-pageobjects-di .body { font-family: 'Inter', sans-serif; font-weight: 500; font-size: 14px; letter-spacing: -0.005em; }
.injected-fig-pageobjects-di .mono { font-family: 'JetBrains Mono', monospace; font-weight: 500; font-size: 11.5px; }</style>
<!-- ── Card 1 — Register ───────────────────────────── -->
<g transform="translate(50, 50)">
<path d="M0,0 L370,0 L384,-8 L14,-8 Z" fill="#F1F5F9"></path>
<path d="M370,0 L384,-8 L384,312 L370,320 Z" fill="#E2E8F0"></path>
<rect width="370" height="320" fill="#FFFFFF" filter="url(#ambient)"></rect>
<text x="24" y="52" class="title" fill="#111827">Register</text>
<rect x="24" y="74" width="322" height="136" rx="8" fill="#FAFAF7" stroke="#F0EDE5" stroke-width="1"></rect>
<text x="40" y="98" class="mono" fill="#9CA3AF">// codecept.conf.js</text>
<text x="40" y="120" class="mono"><tspan fill="#C88800">include</tspan><tspan fill="#1F2937">: {</tspan></text>
<text x="40" y="142" class="mono"><tspan fill="#1F2937"> </tspan><tspan fill="#C88800">loginPage</tspan><tspan fill="#1F2937">: './pages/Login.js',</tspan></text>
<text x="40" y="164" class="mono"><tspan fill="#1F2937"> </tspan><tspan fill="#C88800">registerPage</tspan><tspan fill="#1F2937">: './pages/Register.js',</tspan></text>
<text x="40" y="186" class="mono" fill="#1F2937">}</text>
<text x="24" y="246" class="body" fill="#4B5563">A name maps to a module — built once</text>
<text x="24" y="268" class="body" fill="#4B5563">and shared everywhere by that name.</text>
</g>
<!-- ── Card 2 — Inject by name ─────────────────────── -->
<g transform="translate(460, 50)">
<path d="M0,0 L370,0 L384,-8 L14,-8 Z" fill="#F1F5F9"></path>
<path d="M370,0 L384,-8 L384,312 L370,320 Z" fill="#E2E8F0"></path>
<rect width="370" height="320" fill="#FFFFFF" filter="url(#ambient)"></rect>
<text x="24" y="52" class="title" fill="#111827">Inject by name</text>
<rect x="24" y="74" width="322" height="136" rx="8" fill="#FAFAF7" stroke="#F0EDE5" stroke-width="1"></rect>
<text x="40" y="98" class="mono" fill="#9CA3AF">// in a Scenario:</text>
<text x="40" y="120" class="mono"><tspan fill="#1F2937">Scenario('login', ({ I, </tspan><tspan fill="#C88800">loginPage</tspan><tspan fill="#1F2937"> }) => …)</tspan></text>
<text x="40" y="164" class="mono" fill="#9CA3AF">// in a Page Object:</text>
<text x="40" y="186" class="mono"><tspan fill="#1F2937">const { I, </tspan><tspan fill="#C88800">registerPage</tspan><tspan fill="#1F2937"> } = </tspan><tspan fill="#C88800">inject</tspan><tspan fill="#1F2937">()</tspan></text>
<text x="24" y="246" class="body" fill="#4B5563">Scenario params and inject() read one</text>
<text x="24" y="268" class="body" fill="#4B5563">container — no new, no manual wiring.</text>
</g>
<!-- ── Card 3 — Lazy proxies (circular refs) ───────── -->
<g transform="translate(870, 50)">
<path d="M0,0 L370,0 L384,-8 L14,-8 Z" fill="#F1F5F9"></path>
<path d="M370,0 L384,-8 L384,312 L370,320 Z" fill="#E2E8F0"></path>
<rect width="370" height="320" fill="#FFFFFF" filter="url(#ambient)"></rect>
<text x="24" y="52" class="title" fill="#111827">Lazy proxies</text>
<rect x="24" y="74" width="322" height="136" rx="8" fill="#FAFAF7" stroke="#F0EDE5" stroke-width="1"></rect>
<text x="40" y="98" class="mono" fill="#9CA3AF">// pages/Login.js</text>
<text x="40" y="120" class="mono"><tspan fill="#1F2937">const { I, </tspan><tspan fill="#C88800">registerPage</tspan><tspan fill="#1F2937"> } = </tspan><tspan fill="#C88800">inject</tspan><tspan fill="#1F2937">()</tspan></text>
<text x="40" y="164" class="mono" fill="#9CA3AF">// pages/Register.js (circular!)</text>
<text x="40" y="186" class="mono"><tspan fill="#1F2937">const { I, </tspan><tspan fill="#C88800">loginPage</tspan><tspan fill="#1F2937"> } = </tspan><tspan fill="#C88800">inject</tspan><tspan fill="#1F2937">()</tspan></text>
<text x="24" y="246" class="body" fill="#4B5563">A plain import here would be undefined —</text>
<text x="24" y="268" class="body" fill="#4B5563">inject()'s lazy proxy resolves at call time.</text>
</g>
<!-- ── Arrows ──────────────────────────────────────── -->
<g stroke="#C88800" stroke-width="2.2" fill="none" stroke-linecap="round" marker-end="url(#arrowGold)">
<line x1="426" y1="210" x2="454" y2="210"></line>
<line x1="836" y1="210" x2="864" y2="210"></line>
</g>
</svg><button aria-label="Zoom image: CodeceptJS dependency injection: register page objects in codecept.conf.js, inject them by name into Scenarios or other page objects via inject(); inject() returns lazy proxies, so circular page-object references that a plain import would leave undefined resolve at call time." class="starlight-image-zoom-control"><svg aria-hidden="true" fill="currentColor" viewBox="0 0 24 24"><use href="#starlight-image-zoom-icon-zoom"></use></svg></button></starlight-image-zoom-zoomable></figure><div class="sl-heading-wrapper level-h2"><h2 id="dependency-injection">Dependency Injection</h2><a class="sl-anchor-link" href="#dependency-injection"><span aria-hidden="true" class="sl-anchor-icon"><svg width="16" height="16" viewBox="0 0 24 24"><path fill="currentcolor" d="m12.11 15.39-3.88 3.88a2.52 2.52 0 0 1-3.5 0 2.47 2.47 0 0 1 0-3.5l3.88-3.88a1 1 0 0 0-1.42-1.42l-3.88 3.89a4.48 4.48 0 0 0 6.33 6.33l3.89-3.88a1 1 0 1 0-1.42-1.42Zm8.58-12.08a4.49 4.49 0 0 0-6.33 0l-3.89 3.88a1 1 0 0 0 1.42 1.42l3.88-3.88a2.52 2.52 0 0 1 3.5 0 2.47 2.47 0 0 1 0 3.5l-3.88 3.88a1 1 0 1 0 1.42 1.42l3.88-3.89a4.49 4.49 0 0 0 0-6.33ZM8.83 15.17a1 1 0 0 0 1.1.22 1 1 0 0 0 .32-.22l4.92-4.92a1 1 0 0 0-1.42-1.42l-4.92 4.92a1 1 0 0 0 0 1.42Z"></path></svg></span><span class="sr-only">Section titled “Dependency Injection”</span></a></div>
<p>All objects described here are injected via Dependency Injection, in a similar way AngularJS does. If you want an object to be injected in a scenario by its name, you can add it to the configuration:</p>
<div class="expressive-code"><link rel="stylesheet" href="/_astro/ec.y86o5.css"><script type="module" src="/_astro/ec.p1z7b.js"></script><figure class="frame not-content"><figcaption class="header"></figcaption><pre data-language="js"><code><div class="ec-line"><div class="code"><span class="indent"><span style="--0:#C9D1D9;--1:#1F2937"> </span></span><span style="--0:#C9D1D9;--1:#1F2937">include</span><span style="--0:#9CA3AF;--1:#4B5563">:</span><span style="--0:#C9D1D9;--1:#1F2937"> </span><span style="--0:#9CA3AF;--1:#4B5563">{</span></div></div><div class="ec-line"><div class="code"><span class="indent"> </span><span style="--0:#db2777;--0fw:bold;--1:#db2777;--1fw:bold">I</span><span style="--0:#9CA3AF;--1:#4B5563">:</span><span style="--0:#C9D1D9;--1:#1F2937"> </span><span style="--0:#7DD3FC;--1:#0369A1">"./custom_steps.js"</span><span style="--0:#9CA3AF;--1:#4B5563">,</span></div></div><div class="ec-line"><div class="code"><span class="indent"><span style="--0:#C9D1D9;--1:#1F2937"> </span></span><span style="--0:#C9D1D9;--1:#1F2937">Smth</span><span style="--0:#9CA3AF;--1:#4B5563">:</span><span style="--0:#C9D1D9;--1:#1F2937"> </span><span style="--0:#7DD3FC;--1:#0369A1">"./pages/Smth.js"</span><span style="--0:#9CA3AF;--1:#4B5563">,</span></div></div><div class="ec-line"><div class="code"><span class="indent"><span style="--0:#C9D1D9;--1:#1F2937"> </span></span><span style="--0:#C9D1D9;--1:#1F2937">loginPage</span><span style="--0:#9CA3AF;--1:#4B5563">:</span><span style="--0:#C9D1D9;--1:#1F2937"> </span><span style="--0:#7DD3FC;--1:#0369A1">"./pages/Login.js"</span><span style="--0:#9CA3AF;--1:#4B5563">,</span></div></div><div class="ec-line"><div class="code"><span class="indent"><span style="--0:#C9D1D9;--1:#1F2937"> </span></span><span style="--0:#C9D1D9;--1:#1F2937">signinFragment</span><span style="--0:#9CA3AF;--1:#4B5563">:</span><span style="--0:#C9D1D9;--1:#1F2937"> </span><span style="--0:#7DD3FC;--1:#0369A1">"./fragments/Signin.js"</span></div></div><div class="ec-line"><div class="code"><span class="indent"> </span><span style="--0:#9CA3AF;--1:#4B5563">}</span></div></div></code></pre><div class="copy"><button title="Copy to clipboard" data-copied="Copied!" data-code=" include: { I: "./custom_steps.js", Smth: "./pages/Smth.js", loginPage: "./pages/Login.js", signinFragment: "./fragments/Signin.js" }"><div></div></button></div></figure></div>
<p>These objects can now be retrieved by the name specified in the configuration.</p>
<p>Required objects can be obtained via parameters in tests or via a global <code dir="auto">inject()</code> call.</p>
<div class="expressive-code"><figure class="frame not-content"><figcaption class="header"></figcaption><pre data-language="js"><code><div class="ec-line"><div class="code"><span style="--0:#8B949E;--0fs:italic;--1:#636976;--1fs:italic">// globally inject objects by name</span></div></div><div class="ec-line"><div class="code"><span style="--0:#D8B4FE;--1:#7C3AED">const</span><span style="--0:#C9D1D9;--1:#1F2937"> </span><span style="--0:#9CA3AF;--1:#4B5563">{</span><span style="--0:#C9D1D9;--1:#1F2937"> </span><span style="--0:#db2777;--0fw:bold;--1:#db2777;--1fw:bold">I</span><span style="--0:#9CA3AF;--1:#4B5563">,</span><span style="--0:#C9D1D9;--1:#1F2937"> myPage</span><span style="--0:#9CA3AF;--1:#4B5563">,</span><span style="--0:#C9D1D9;--1:#1F2937"> mySteps </span><span style="--0:#9CA3AF;--1:#4B5563">}</span><span style="--0:#C9D1D9;--1:#1F2937"> </span><span style="--0:#9CA3AF;--1:#4B5563">=</span><span style="--1:#1F2937"><span style="--0:#C9D1D9"> </span><span style="--0:#E5E7EB">inject</span></span><span style="--0:#9CA3AF;--1:#4B5563">();</span></div></div><div class="ec-line"><div class="code">
</div></div><div class="ec-line"><div class="code"><span style="--0:#8B949E;--0fs:italic;--1:#636976;--1fs:italic">// inject objects for a test by name</span></div></div><div class="ec-line"><div class="code"><span style="--0:#7c3aed;--0fw:bold;--1:#7c3aed;--1fw:bold">Scenario</span><span style="--0:#9CA3AF;--1:#4B5563">(</span><span style="--0:#7DD3FC;--1:#0369A1">'sample test'</span><span style="--0:#9CA3AF;--1:#4B5563">,</span><span style="--0:#C9D1D9;--1:#1F2937"> </span><span style="--0:#9CA3AF;--1:#4B5563">({</span><span style="--0:#C9D1D9;--1:#1F2937"> </span><span style="--0:#db2777;--0fw:bold;--1:#db2777;--1fw:bold">I</span><span style="--0:#9CA3AF;--1:#4B5563">,</span><span style="--0:#C9D1D9;--1:#1F2937"> myPage</span><span style="--0:#9CA3AF;--1:#4B5563">,</span><span style="--0:#C9D1D9;--1:#1F2937"> mySteps </span><span style="--0:#9CA3AF;--1:#4B5563">})</span><span style="--0:#C9D1D9;--1:#1F2937"> </span><span style="--0:#D8B4FE;--1:#7C3AED">=></span><span style="--0:#C9D1D9;--1:#1F2937"> </span><span style="--0:#9CA3AF;--1:#4B5563">{</span></div></div><div class="ec-line"><div class="code"><span class="indent"> </span><span style="--0:#8B949E;--0fs:italic;--1:#636976;--1fs:italic">// ...</span></div></div><div class="ec-line"><div class="code"><span style="--0:#9CA3AF;--1:#4B5563">});</span></div></div></code></pre><div class="copy"><button title="Copy to clipboard" data-copied="Copied!" data-code="// globally inject objects by nameconst { I, myPage, mySteps } = inject();// inject objects for a test by nameScenario('sample test', ({ I, myPage, mySteps }) => { // ...});"><div></div></button></div></figure></div>
<div class="sl-heading-wrapper level-h2"><h2 id="actor">Actor</h2><a class="sl-anchor-link" href="#actor"><span aria-hidden="true" class="sl-anchor-icon"><svg width="16" height="16" viewBox="0 0 24 24"><path fill="currentcolor" d="m12.11 15.39-3.88 3.88a2.52 2.52 0 0 1-3.5 0 2.47 2.47 0 0 1 0-3.5l3.88-3.88a1 1 0 0 0-1.42-1.42l-3.88 3.89a4.48 4.48 0 0 0 6.33 6.33l3.89-3.88a1 1 0 1 0-1.42-1.42Zm8.58-12.08a4.49 4.49 0 0 0-6.33 0l-3.89 3.88a1 1 0 0 0 1.42 1.42l3.88-3.88a2.52 2.52 0 0 1 3.5 0 2.47 2.47 0 0 1 0 3.5l-3.88 3.88a1 1 0 1 0 1.42 1.42l3.88-3.89a4.49 4.49 0 0 0 0-6.33ZM8.83 15.17a1 1 0 0 0 1.1.22 1 1 0 0 0 .32-.22l4.92-4.92a1 1 0 0 0-1.42-1.42l-4.92 4.92a1 1 0 0 0 0 1.42Z"></path></svg></span><span class="sr-only">Section titled “Actor”</span></a></div>
<p>During initialization, you were asked to create a custom steps file. If you accepted this option, you are now able to use the <code dir="auto">custom_steps.js</code> file to extend <code dir="auto">I</code>. See how the <code dir="auto">login</code> method can be added to <code dir="auto">I</code>:</p>
<div class="expressive-code"><figure class="frame not-content"><figcaption class="header"></figcaption><pre data-language="js"><code><div class="ec-line"><div class="code"><span style="--0:#D8B4FE;--1:#7C3AED">export</span><span style="--0:#C9D1D9;--1:#1F2937"> </span><span style="--0:#D8B4FE;--1:#7C3AED">default</span><span style="--0:#C9D1D9;--1:#1F2937"> </span><span style="--0:#D8B4FE;--1:#7C3AED">function</span><span style="--0:#9CA3AF;--1:#4B5563">()</span><span style="--0:#C9D1D9;--1:#1F2937"> </span><span style="--0:#9CA3AF;--1:#4B5563">{</span></div></div><div class="ec-line"><div class="code"><span class="indent"> </span><span style="--0:#D8B4FE;--1:#7C3AED">return</span><span style="--1:#1F2937"><span style="--0:#C9D1D9"> </span><span style="--0:#E5E7EB">actor</span></span><span style="--0:#9CA3AF;--1:#4B5563">({</span></div></div><div class="ec-line"><div class="code"><span class="indent"><span style="--1:#1F2937"> </span></span><span style="--0:#E5E7EB;--1:#1F2937">login</span><span style="--0:#9CA3AF;--1:#4B5563">:</span><span style="--0:#C9D1D9;--1:#1F2937"> </span><span style="--0:#D8B4FE;--1:#7C3AED">function</span><span style="--0:#9CA3AF;--1:#4B5563">(</span><span style="--0:#C9D1D9;--1:#1F2937">email</span><span style="--0:#9CA3AF;--1:#4B5563">,</span><span style="--0:#C9D1D9;--1:#1F2937"> password</span><span style="--0:#9CA3AF;--1:#4B5563">)</span><span style="--0:#C9D1D9;--1:#1F2937"> </span><span style="--0:#9CA3AF;--1:#4B5563">{</span></div></div><div class="ec-line"><div class="code"><span class="indent"><span style="--0:#C9D1D9;--1:#1F2937"> </span></span><span style="--0:#C9D1D9;--1:#1F2937">this</span><span style="--0:#9CA3AF;--1:#4B5563">.</span><span style="--0:#E5E7EB;--1:#1F2937">fillField</span><span style="--0:#9CA3AF;--1:#4B5563">(</span><span style="--0:#7DD3FC;--1:#0369A1">'Email'</span><span style="--0:#9CA3AF;--1:#4B5563">,</span><span style="--0:#C9D1D9;--1:#1F2937"> email</span><span style="--0:#9CA3AF;--1:#4B5563">);</span></div></div><div class="ec-line"><div class="code"><span class="indent"><span style="--0:#C9D1D9;--1:#1F2937"> </span></span><span style="--0:#C9D1D9;--1:#1F2937">this</span><span style="--0:#9CA3AF;--1:#4B5563">.</span><span style="--0:#E5E7EB;--1:#1F2937">fillField</span><span style="--0:#9CA3AF;--1:#4B5563">(</span><span style="--0:#7DD3FC;--1:#0369A1">'Password'</span><span style="--0:#9CA3AF;--1:#4B5563">,</span><span style="--0:#C9D1D9;--1:#1F2937"> password</span><span style="--0:#9CA3AF;--1:#4B5563">);</span></div></div><div class="ec-line"><div class="code"><span class="indent"><span style="--0:#C9D1D9;--1:#1F2937"> </span></span><span style="--0:#C9D1D9;--1:#1F2937">this</span><span style="--0:#9CA3AF;--1:#4B5563">.</span><span style="--0:#E5E7EB;--1:#1F2937">click</span><span style="--0:#9CA3AF;--1:#4B5563">(</span><span style="--0:#7DD3FC;--1:#0369A1">'Submit'</span><span style="--0:#9CA3AF;--1:#4B5563">);</span></div></div><div class="ec-line"><div class="code"><span class="indent"> </span><span style="--0:#9CA3AF;--1:#4B5563">}</span></div></div><div class="ec-line"><div class="code"><span class="indent"> </span><span style="--0:#9CA3AF;--1:#4B5563">});</span></div></div><div class="ec-line"><div class="code"><span style="--0:#9CA3AF;--1:#4B5563">}</span></div></div></code></pre><div class="copy"><button title="Copy to clipboard" data-copied="Copied!" data-code="export default function() { return actor({ login: function(email, password) { this.fillField('Email', email); this.fillField('Password', password); this.click('Submit'); } });}"><div></div></button></div></figure></div>
<blockquote>
<p>Instead of <code dir="auto">I</code> you should use <code dir="auto">this</code> in the current context.</p>
</blockquote>
<div class="sl-heading-wrapper level-h2"><h2 id="pageobject">PageObject</h2><a class="sl-anchor-link" href="#pageobject"><span aria-hidden="true" class="sl-anchor-icon"><svg width="16" height="16" viewBox="0 0 24 24"><path fill="currentcolor" d="m12.11 15.39-3.88 3.88a2.52 2.52 0 0 1-3.5 0 2.47 2.47 0 0 1 0-3.5l3.88-3.88a1 1 0 0 0-1.42-1.42l-3.88 3.89a4.48 4.48 0 0 0 6.33 6.33l3.89-3.88a1 1 0 1 0-1.42-1.42Zm8.58-12.08a4.49 4.49 0 0 0-6.33 0l-3.89 3.88a1 1 0 0 0 1.42 1.42l3.88-3.88a2.52 2.52 0 0 1 3.5 0 2.47 2.47 0 0 1 0 3.5l-3.88 3.88a1 1 0 1 0 1.42 1.42l3.88-3.89a4.49 4.49 0 0 0 0-6.33ZM8.83 15.17a1 1 0 0 0 1.1.22 1 1 0 0 0 .32-.22l4.92-4.92a1 1 0 0 0-1.42-1.42l-4.92 4.92a1 1 0 0 0 0 1.42Z"></path></svg></span><span class="sr-only">Section titled “PageObject”</span></a></div>
<p>If an application has different pages (login, admin, etc) you should use a page object.
CodeceptJS can generate a template for it with the following command:</p>
<div class="expressive-code"><figure class="frame is-terminal not-content"><figcaption class="header"><span class="title"></span><span class="sr-only">Terminal window</span></figcaption><pre data-language="sh"><code><div class="ec-line"><div class="code"><span style="--1:#1F2937"><span style="--0:#E5E7EB">npx</span><span style="--0:#C9D1D9"> </span></span><span style="--0:#7DD3FC;--1:#0369A1">codeceptjs</span><span style="--0:#C9D1D9;--1:#1F2937"> </span><span style="--0:#7DD3FC;--1:#0369A1">gpo</span></div></div></code></pre><div class="copy"><button title="Copy to clipboard" data-copied="Copied!" data-code="npx codeceptjs gpo"><div></div></button></div></figure></div>
<p>This will create a sample template for a page object and include it in the <code dir="auto">codecept.json</code> config file.</p>
<p><strong>Page objects should be classes.</strong> Use <code dir="auto">const { I } = inject()</code> at the top of the file to access <code dir="auto">I</code> and other page objects. Export the class itself — the DI container will auto-instantiate it.</p>
<div class="expressive-code"><figure class="frame not-content"><figcaption class="header"></figcaption><pre data-language="js"><code><div class="ec-line"><div class="code"><span style="--0:#D8B4FE;--1:#7C3AED">const</span><span style="--0:#C9D1D9;--1:#1F2937"> </span><span style="--0:#9CA3AF;--1:#4B5563">{</span><span style="--0:#C9D1D9;--1:#1F2937"> </span><span style="--0:#db2777;--0fw:bold;--1:#db2777;--1fw:bold">I</span><span style="--0:#9CA3AF;--1:#4B5563">,</span><span style="--0:#C9D1D9;--1:#1F2937"> registerPage </span><span style="--0:#9CA3AF;--1:#4B5563">}</span><span style="--0:#C9D1D9;--1:#1F2937"> </span><span style="--0:#9CA3AF;--1:#4B5563">=</span><span style="--1:#1F2937"><span style="--0:#C9D1D9"> </span><span style="--0:#E5E7EB">inject</span></span><span style="--0:#9CA3AF;--1:#4B5563">();</span></div></div><div class="ec-line"><div class="code">
</div></div><div class="ec-line"><div class="code"><span style="--0:#D8B4FE;--1:#7C3AED">class</span><span style="--0:#C9D1D9;--1:#1F2937"> </span><span style="--0:#F472B6;--1:#C3236A">LoginPage</span><span style="--0:#C9D1D9;--1:#1F2937"> </span><span style="--0:#9CA3AF;--1:#4B5563">{</span></div></div><div class="ec-line"><div class="code"><span class="indent"> </span><span style="--0:#8B949E;--0fs:italic;--1:#636976;--1fs:italic">// setting locators</span></div></div><div class="ec-line"><div class="code"><span class="indent"><span style="--0:#C9D1D9;--1:#1F2937"> </span></span><span style="--0:#C9D1D9;--1:#1F2937">fields </span><span style="--0:#9CA3AF;--1:#4B5563">=</span><span style="--0:#C9D1D9;--1:#1F2937"> </span><span style="--0:#9CA3AF;--1:#4B5563">{</span></div></div><div class="ec-line"><div class="code"><span class="indent"><span style="--0:#C9D1D9;--1:#1F2937"> </span></span><span style="--0:#C9D1D9;--1:#1F2937">email</span><span style="--0:#9CA3AF;--1:#4B5563">:</span><span style="--0:#C9D1D9;--1:#1F2937"> </span><span style="--0:#7DD3FC;--1:#0369A1">'#user_basic_email'</span><span style="--0:#9CA3AF;--1:#4B5563">,</span></div></div><div class="ec-line"><div class="code"><span class="indent"><span style="--0:#C9D1D9;--1:#1F2937"> </span></span><span style="--0:#C9D1D9;--1:#1F2937">password</span><span style="--0:#9CA3AF;--1:#4B5563">:</span><span style="--0:#C9D1D9;--1:#1F2937"> </span><span style="--0:#7DD3FC;--1:#0369A1">'#user_basic_password'</span></div></div><div class="ec-line"><div class="code"><span class="indent"> </span><span style="--0:#9CA3AF;--1:#4B5563">}</span></div></div><div class="ec-line"><div class="code"><span class="indent"><span style="--0:#C9D1D9;--1:#1F2937"> </span></span><span style="--0:#C9D1D9;--1:#1F2937">submitButton </span><span style="--0:#9CA3AF;--1:#4B5563">=</span><span style="--0:#C9D1D9;--1:#1F2937"> </span><span style="--0:#9CA3AF;--1:#4B5563">{</span><span style="--0:#C9D1D9;--1:#1F2937"> css</span><span style="--0:#9CA3AF;--1:#4B5563">:</span><span style="--0:#C9D1D9;--1:#1F2937"> </span><span style="--0:#7DD3FC;--1:#0369A1">'#new_user_basic input[type=submit]'</span><span style="--0:#C9D1D9;--1:#1F2937"> </span><span style="--0:#9CA3AF;--1:#4B5563">}</span></div></div><div class="ec-line"><div class="code">
</div></div><div class="ec-line"><div class="code"><span class="indent"> </span><span style="--0:#8B949E;--0fs:italic;--1:#636976;--1fs:italic">// introducing methods</span></div></div><div class="ec-line"><div class="code"><span class="indent"><span style="--1:#1F2937"> </span></span><span style="--0:#E5E7EB;--1:#1F2937">sendForm</span><span style="--0:#9CA3AF;--1:#4B5563">(</span><span style="--0:#C9D1D9;--1:#1F2937">email</span><span style="--0:#9CA3AF;--1:#4B5563">,</span><span style="--0:#C9D1D9;--1:#1F2937"> password</span><span style="--0:#9CA3AF;--1:#4B5563">)</span><span style="--0:#C9D1D9;--1:#1F2937"> </span><span style="--0:#9CA3AF;--1:#4B5563">{</span></div></div><div class="ec-line"><div class="code"><span class="indent"> </span><span style="--0:#db2777;--0fw:bold;--1:#db2777;--1fw:bold">I</span><span style="--0:#9CA3AF;--1:#4B5563">.</span><span style="--0:#E5E7EB;--1:#1F2937">fillField</span><span style="--0:#9CA3AF;--1:#4B5563">(</span><span style="--0:#C9D1D9;--1:#1F2937">this</span><span style="--0:#9CA3AF;--1:#4B5563">.</span><span style="--0:#C9D1D9;--1:#1F2937">fields</span><span style="--0:#9CA3AF;--1:#4B5563">.</span><span style="--0:#C9D1D9;--1:#1F2937">email</span><span style="--0:#9CA3AF;--1:#4B5563">,</span><span style="--0:#C9D1D9;--1:#1F2937"> email</span><span style="--0:#9CA3AF;--1:#4B5563">);</span></div></div><div class="ec-line"><div class="code"><span class="indent"> </span><span style="--0:#db2777;--0fw:bold;--1:#db2777;--1fw:bold">I</span><span style="--0:#9CA3AF;--1:#4B5563">.</span><span style="--0:#E5E7EB;--1:#1F2937">fillField</span><span style="--0:#9CA3AF;--1:#4B5563">(</span><span style="--0:#C9D1D9;--1:#1F2937">this</span><span style="--0:#9CA3AF;--1:#4B5563">.</span><span style="--0:#C9D1D9;--1:#1F2937">fields</span><span style="--0:#9CA3AF;--1:#4B5563">.</span><span style="--0:#C9D1D9;--1:#1F2937">password</span><span style="--0:#9CA3AF;--1:#4B5563">,</span><span style="--0:#C9D1D9;--1:#1F2937"> password</span><span style="--0:#9CA3AF;--1:#4B5563">);</span></div></div><div class="ec-line"><div class="code"><span class="indent"> </span><span style="--0:#db2777;--0fw:bold;--1:#db2777;--1fw:bold">I</span><span style="--0:#9CA3AF;--1:#4B5563">.</span><span style="--0:#E5E7EB;--1:#1F2937">click</span><span style="--0:#9CA3AF;--1:#4B5563">(</span><span style="--0:#C9D1D9;--1:#1F2937">this</span><span style="--0:#9CA3AF;--1:#4B5563">.</span><span style="--0:#C9D1D9;--1:#1F2937">submitButton</span><span style="--0:#9CA3AF;--1:#4B5563">);</span></div></div><div class="ec-line"><div class="code"><span class="indent"> </span><span style="--0:#9CA3AF;--1:#4B5563">}</span></div></div><div class="ec-line"><div class="code">
</div></div><div class="ec-line"><div class="code"><span class="indent"><span style="--1:#1F2937"> </span></span><span style="--0:#E5E7EB;--1:#1F2937">register</span><span style="--0:#9CA3AF;--1:#4B5563">(</span><span style="--0:#C9D1D9;--1:#1F2937">email</span><span style="--0:#9CA3AF;--1:#4B5563">,</span><span style="--0:#C9D1D9;--1:#1F2937"> password</span><span style="--0:#9CA3AF;--1:#4B5563">)</span><span style="--0:#C9D1D9;--1:#1F2937"> </span><span style="--0:#9CA3AF;--1:#4B5563">{</span></div></div><div class="ec-line"><div class="code"><span class="indent"> </span><span style="--0:#8B949E;--0fs:italic;--1:#636976;--1fs:italic">// use another page object inside current one</span></div></div><div class="ec-line"><div class="code"><span class="indent"><span style="--0:#C9D1D9;--1:#1F2937"> </span></span><span style="--0:#C9D1D9;--1:#1F2937">registerPage</span><span style="--0:#9CA3AF;--1:#4B5563">.</span><span style="--0:#E5E7EB;--1:#1F2937">registerUser</span><span style="--0:#9CA3AF;--1:#4B5563">({</span><span style="--0:#C9D1D9;--1:#1F2937"> email</span><span style="--0:#9CA3AF;--1:#4B5563">,</span><span style="--0:#C9D1D9;--1:#1F2937"> password </span><span style="--0:#9CA3AF;--1:#4B5563">});</span></div></div><div class="ec-line"><div class="code"><span class="indent"> </span><span style="--0:#9CA3AF;--1:#4B5563">}</span></div></div><div class="ec-line"><div class="code"><span style="--0:#9CA3AF;--1:#4B5563">}</span></div></div><div class="ec-line"><div class="code">
</div></div><div class="ec-line"><div class="code"><span style="--0:#D8B4FE;--1:#7C3AED">export</span><span style="--0:#C9D1D9;--1:#1F2937"> </span><span style="--0:#D8B4FE;--1:#7C3AED">default</span><span style="--0:#C9D1D9;--1:#1F2937"> LoginPage</span></div></div></code></pre><div class="copy"><button title="Copy to clipboard" data-copied="Copied!" data-code="const { I, registerPage } = inject();class LoginPage { // setting locators fields = { email: '#user_basic_email', password: '#user_basic_password' } submitButton = { css: '#new_user_basic input[type=submit]' } // introducing methods sendForm(email, password) { I.fillField(this.fields.email, email); I.fillField(this.fields.password, password); I.click(this.submitButton); } register(email, password) { // use another page object inside current one registerPage.registerUser({ email, password }); }}export default LoginPage"><div></div></button></div></figure></div>
<blockquote>
<p>The <code dir="auto">inject()</code> call at the top returns a lazy proxy. <code dir="auto">I</code> and other page objects resolve at call time, so it’s safe to destructure before class definition.</p>
</blockquote>
<p>You can include this pageobject in a test by its name (defined in <code dir="auto">codecept.conf.js</code>). If you created a <code dir="auto">loginPage</code> object,
it should be added to the list of arguments to be included in the test:</p>
<div class="expressive-code"><figure class="frame not-content"><figcaption class="header"></figcaption><pre data-language="js"><code><div class="ec-line"><div class="code"><span style="--0:#7c3aed;--0fw:bold;--1:#7c3aed;--1fw:bold">Scenario</span><span style="--0:#9CA3AF;--1:#4B5563">(</span><span style="--0:#7DD3FC;--1:#0369A1">'login'</span><span style="--0:#9CA3AF;--1:#4B5563">,</span><span style="--0:#C9D1D9;--1:#1F2937"> </span><span style="--0:#9CA3AF;--1:#4B5563">({</span><span style="--0:#C9D1D9;--1:#1F2937"> </span><span style="--0:#db2777;--0fw:bold;--1:#db2777;--1fw:bold">I</span><span style="--0:#9CA3AF;--1:#4B5563">,</span><span style="--0:#C9D1D9;--1:#1F2937"> loginPage </span><span style="--0:#9CA3AF;--1:#4B5563">})</span><span style="--0:#C9D1D9;--1:#1F2937"> </span><span style="--0:#D8B4FE;--1:#7C3AED">=></span><span style="--0:#C9D1D9;--1:#1F2937"> </span><span style="--0:#9CA3AF;--1:#4B5563">{</span></div></div><div class="ec-line"><div class="code"><span class="indent"><span style="--0:#C9D1D9;--1:#1F2937"> </span></span><span style="--0:#C9D1D9;--1:#1F2937">loginPage</span><span style="--0:#9CA3AF;--1:#4B5563">.</span><span style="--0:#E5E7EB;--1:#1F2937">sendForm</span><span style="--0:#9CA3AF;--1:#4B5563">(</span><span style="--0:#7DD3FC;--1:#0369A1">'john@doe.com'</span><span style="--0:#9CA3AF;--1:#4B5563">,</span><span style="--0:#7DD3FC;--1:#0369A1">'123456'</span><span style="--0:#9CA3AF;--1:#4B5563">);</span></div></div><div class="ec-line"><div class="code"><span class="indent"> </span><span style="--0:#db2777;--0fw:bold;--1:#db2777;--1fw:bold">I</span><span style="--0:#9CA3AF;--1:#4B5563">.</span><span style="--0:#E5E7EB;--1:#1F2937">see</span><span style="--0:#9CA3AF;--1:#4B5563">(</span><span style="--0:#7DD3FC;--1:#0369A1">'Hello, John'</span><span style="--0:#9CA3AF;--1:#4B5563">);</span></div></div><div class="ec-line"><div class="code"><span style="--0:#9CA3AF;--1:#4B5563">});</span></div></div></code></pre><div class="copy"><button title="Copy to clipboard" data-copied="Copied!" data-code="Scenario('login', ({ I, loginPage }) => { loginPage.sendForm('john@doe.com','123456'); I.see('Hello, John');});"><div></div></button></div></figure></div>
<p>You can use <code dir="auto">async/await</code> inside a Page Object:</p>
<div class="expressive-code"><figure class="frame not-content"><figcaption class="header"></figcaption><pre data-language="js"><code><div class="ec-line"><div class="code"><span style="--0:#D8B4FE;--1:#7C3AED">const</span><span style="--0:#C9D1D9;--1:#1F2937"> </span><span style="--0:#9CA3AF;--1:#4B5563">{</span><span style="--0:#C9D1D9;--1:#1F2937"> </span><span style="--0:#db2777;--0fw:bold;--1:#db2777;--1fw:bold">I</span><span style="--0:#C9D1D9;--1:#1F2937"> </span><span style="--0:#9CA3AF;--1:#4B5563">}</span><span style="--0:#C9D1D9;--1:#1F2937"> </span><span style="--0:#9CA3AF;--1:#4B5563">=</span><span style="--1:#1F2937"><span style="--0:#C9D1D9"> </span><span style="--0:#E5E7EB">inject</span></span><span style="--0:#9CA3AF;--1:#4B5563">();</span></div></div><div class="ec-line"><div class="code">
</div></div><div class="ec-line"><div class="code"><span style="--0:#D8B4FE;--1:#7C3AED">class</span><span style="--0:#C9D1D9;--1:#1F2937"> </span><span style="--0:#F472B6;--1:#C3236A">MainPage</span><span style="--0:#C9D1D9;--1:#1F2937"> </span><span style="--0:#9CA3AF;--1:#4B5563">{</span></div></div><div class="ec-line"><div class="code"><span class="indent"> </span><span style="--0:#8B949E;--0fs:italic;--1:#636976;--1fs:italic">// setting locators</span></div></div><div class="ec-line"><div class="code"><span class="indent"><span style="--0:#C9D1D9;--1:#1F2937"> </span></span><span style="--0:#C9D1D9;--1:#1F2937">container </span><span style="--0:#9CA3AF;--1:#4B5563">=</span><span style="--0:#C9D1D9;--1:#1F2937"> </span><span style="--0:#7DD3FC;--1:#0369A1">"//div[@class = 'numbers']"</span></div></div><div class="ec-line"><div class="code"><span class="indent"><span style="--0:#C9D1D9;--1:#1F2937"> </span></span><span style="--0:#C9D1D9;--1:#1F2937">mainItem </span><span style="--0:#9CA3AF;--1:#4B5563">=</span><span style="--0:#C9D1D9;--1:#1F2937"> </span><span style="--0:#9CA3AF;--1:#4B5563">{</span></div></div><div class="ec-line"><div class="code"><span class="indent"><span style="--0:#C9D1D9;--1:#1F2937"> </span></span><span style="--0:#C9D1D9;--1:#1F2937">number</span><span style="--0:#9CA3AF;--1:#4B5563">:</span><span style="--0:#C9D1D9;--1:#1F2937"> </span><span style="--0:#7DD3FC;--1:#0369A1">".//div[contains(@class, 'numbers__main-number')]"</span><span style="--0:#9CA3AF;--1:#4B5563">,</span></div></div><div class="ec-line"><div class="code"><span class="indent"><span style="--0:#C9D1D9;--1:#1F2937"> </span></span><span style="--0:#C9D1D9;--1:#1F2937">title</span><span style="--0:#9CA3AF;--1:#4B5563">:</span><span style="--0:#C9D1D9;--1:#1F2937"> </span><span style="--0:#7DD3FC;--1:#0369A1">".//div[contains(@class, 'numbers__main-title-block')]"</span></div></div><div class="ec-line"><div class="code"><span class="indent"> </span><span style="--0:#9CA3AF;--1:#4B5563">}</span></div></div><div class="ec-line"><div class="code">
</div></div><div class="ec-line"><div class="code"><span class="indent"> </span><span style="--0:#8B949E;--0fs:italic;--1:#636976;--1fs:italic">// introducing methods</span></div></div><div class="ec-line"><div class="code"><span class="indent"> </span><span style="--0:#D8B4FE;--1:#7C3AED">async</span><span style="--1:#1F2937"><span style="--0:#C9D1D9"> </span><span style="--0:#E5E7EB">openMainArticle</span></span><span style="--0:#9CA3AF;--1:#4B5563">()</span><span style="--0:#C9D1D9;--1:#1F2937"> </span><span style="--0:#9CA3AF;--1:#4B5563">{</span></div></div><div class="ec-line"><div class="code"><span class="indent"> </span><span style="--0:#db2777;--0fw:bold;--1:#db2777;--1fw:bold">I</span><span style="--0:#9CA3AF;--1:#4B5563">.</span><span style="--0:#E5E7EB;--1:#1F2937">waitForVisible</span><span style="--0:#9CA3AF;--1:#4B5563">(</span><span style="--0:#C9D1D9;--1:#1F2937">this</span><span style="--0:#9CA3AF;--1:#4B5563">.</span><span style="--0:#C9D1D9;--1:#1F2937">container</span><span style="--0:#9CA3AF;--1:#4B5563">)</span></div></div><div class="ec-line"><div class="code"><span class="indent"> </span><span style="--0:#D8B4FE;--1:#7C3AED">let</span><span style="--0:#C9D1D9;--1:#1F2937"> _this </span><span style="--0:#9CA3AF;--1:#4B5563">=</span><span style="--0:#C9D1D9;--1:#1F2937"> this</span></div></div><div class="ec-line"><div class="code"><span class="indent"> </span><span style="--0:#D8B4FE;--1:#7C3AED">let</span><span style="--0:#C9D1D9;--1:#1F2937"> title</span><span style="--0:#9CA3AF;--1:#4B5563">;</span></div></div><div class="ec-line"><div class="code"><span class="indent"> </span><span style="--0:#D8B4FE;--1:#7C3AED">await</span><span style="--1:#1F2937"><span style="--0:#C9D1D9"> </span><span style="--0:#E5E7EB">within</span></span><span style="--0:#9CA3AF;--1:#4B5563">(</span><span style="--0:#C9D1D9;--1:#1F2937">this</span><span style="--0:#9CA3AF;--1:#4B5563">.</span><span style="--0:#C9D1D9;--1:#1F2937">container</span><span style="--0:#9CA3AF;--1:#4B5563">,</span><span style="--0:#C9D1D9;--1:#1F2937"> </span><span style="--0:#D8B4FE;--1:#7C3AED">async</span><span style="--0:#C9D1D9;--1:#1F2937"> </span><span style="--0:#9CA3AF;--1:#4B5563">()</span><span style="--0:#C9D1D9;--1:#1F2937"> </span><span style="--0:#D8B4FE;--1:#7C3AED">=></span><span style="--0:#C9D1D9;--1:#1F2937"> </span><span style="--0:#9CA3AF;--1:#4B5563">{</span></div></div><div class="ec-line"><div class="code"><span class="indent"><span style="--0:#C9D1D9;--1:#1F2937"> </span></span><span style="--0:#C9D1D9;--1:#1F2937">title </span><span style="--0:#9CA3AF;--1:#4B5563">=</span><span style="--0:#C9D1D9;--1:#1F2937"> </span><span style="--0:#D8B4FE;--1:#7C3AED">await</span><span style="--0:#C9D1D9;--1:#1F2937"> </span><span style="--0:#db2777;--0fw:bold;--1:#db2777;--1fw:bold">I</span><span style="--0:#9CA3AF;--1:#4B5563">.</span><span style="--0:#E5E7EB;--1:#1F2937">grabTextFrom</span><span style="--0:#9CA3AF;--1:#4B5563">(</span><span style="--0:#C9D1D9;--1:#1F2937">_this</span><span style="--0:#9CA3AF;--1:#4B5563">.</span><span style="--0:#C9D1D9;--1:#1F2937">mainItem</span><span style="--0:#9CA3AF;--1:#4B5563">.</span><span style="--0:#C9D1D9;--1:#1F2937">number</span><span style="--0:#9CA3AF;--1:#4B5563">);</span></div></div><div class="ec-line"><div class="code"><span class="indent"> </span><span style="--0:#D8B4FE;--1:#7C3AED">let</span><span style="--0:#C9D1D9;--1:#1F2937"> subtitle </span><span style="--0:#9CA3AF;--1:#4B5563">=</span><span style="--0:#C9D1D9;--1:#1F2937"> </span><span style="--0:#D8B4FE;--1:#7C3AED">await</span><span style="--0:#C9D1D9;--1:#1F2937"> </span><span style="--0:#db2777;--0fw:bold;--1:#db2777;--1fw:bold">I</span><span style="--0:#9CA3AF;--1:#4B5563">.</span><span style="--0:#E5E7EB;--1:#1F2937">grabTextFrom</span><span style="--0:#9CA3AF;--1:#4B5563">(</span><span style="--0:#C9D1D9;--1:#1F2937">_this</span><span style="--0:#9CA3AF;--1:#4B5563">.</span><span style="--0:#C9D1D9;--1:#1F2937">mainItem</span><span style="--0:#9CA3AF;--1:#4B5563">.</span><span style="--0:#C9D1D9;--1:#1F2937">title</span><span style="--0:#9CA3AF;--1:#4B5563">);</span></div></div><div class="ec-line"><div class="code"><span class="indent"><span style="--0:#C9D1D9;--1:#1F2937"> </span></span><span style="--0:#C9D1D9;--1:#1F2937">title </span><span style="--0:#9CA3AF;--1:#4B5563">=</span><span style="--0:#C9D1D9;--1:#1F2937"> title </span><span style="--0:#9CA3AF;--1:#4B5563">+</span><span style="--0:#C9D1D9;--1:#1F2937"> </span><span style="--0:#7DD3FC;--1:#0369A1">" "</span><span style="--0:#C9D1D9;--1:#1F2937"> </span><span style="--0:#9CA3AF;--1:#4B5563">+</span><span style="--0:#C9D1D9;--1:#1F2937"> subtitle</span><span style="--0:#9CA3AF;--1:#4B5563">.</span><span style="--0:#E5E7EB;--1:#1F2937">charAt</span><span style="--0:#9CA3AF;--1:#4B5563">(</span><span style="--0:#FBBF24;--1:#A94E08">0</span><span style="--0:#9CA3AF;--1:#4B5563">).</span><span style="--0:#E5E7EB;--1:#1F2937">toLowerCase</span><span style="--0:#9CA3AF;--1:#4B5563">()</span><span style="--0:#C9D1D9;--1:#1F2937"> </span><span style="--0:#9CA3AF;--1:#4B5563">+</span><span style="--0:#C9D1D9;--1:#1F2937"> subtitle</span><span style="--0:#9CA3AF;--1:#4B5563">.</span><span style="--0:#E5E7EB;--1:#1F2937">slice</span><span style="--0:#9CA3AF;--1:#4B5563">(</span><span style="--0:#FBBF24;--1:#A94E08">1</span><span style="--0:#9CA3AF;--1:#4B5563">);</span></div></div><div class="ec-line"><div class="code"><span class="indent"> </span><span style="--0:#D8B4FE;--1:#7C3AED">await</span><span style="--0:#C9D1D9;--1:#1F2937"> </span><span style="--0:#db2777;--0fw:bold;--1:#db2777;--1fw:bold">I</span><span style="--0:#9CA3AF;--1:#4B5563">.</span><span style="--0:#E5E7EB;--1:#1F2937">click</span><span style="--0:#9CA3AF;--1:#4B5563">(</span><span style="--0:#C9D1D9;--1:#1F2937">_this</span><span style="--0:#9CA3AF;--1:#4B5563">.</span><span style="--0:#C9D1D9;--1:#1F2937">mainItem</span><span style="--0:#9CA3AF;--1:#4B5563">.</span><span style="--0:#C9D1D9;--1:#1F2937">title</span><span style="--0:#9CA3AF;--1:#4B5563">)</span></div></div><div class="ec-line"><div class="code"><span class="indent"> </span><span style="--0:#9CA3AF;--1:#4B5563">})</span></div></div><div class="ec-line"><div class="code"><span class="indent"> </span><span style="--0:#D8B4FE;--1:#7C3AED">return</span><span style="--0:#C9D1D9;--1:#1F2937"> title</span><span style="--0:#9CA3AF;--1:#4B5563">;</span></div></div><div class="ec-line"><div class="code"><span class="indent"> </span><span style="--0:#9CA3AF;--1:#4B5563">}</span></div></div><div class="ec-line"><div class="code"><span style="--0:#9CA3AF;--1:#4B5563">}</span></div></div><div class="ec-line"><div class="code">
</div></div><div class="ec-line"><div class="code"><span style="--0:#D8B4FE;--1:#7C3AED">export</span><span style="--0:#C9D1D9;--1:#1F2937"> </span><span style="--0:#D8B4FE;--1:#7C3AED">default</span><span style="--0:#C9D1D9;--1:#1F2937"> MainPage</span></div></div></code></pre><div class="copy"><button title="Copy to clipboard" data-copied="Copied!" data-code="const { I } = inject();class MainPage { // setting locators container = "//div[@class = 'numbers']" mainItem = { number: ".//div[contains(@class, 'numbers__main-number')]", title: ".//div[contains(@class, 'numbers__main-title-block')]" } // introducing methods async openMainArticle() { I.waitForVisible(this.container) let _this = this let title; await within(this.container, async () => { title = await I.grabTextFrom(_this.mainItem.number); let subtitle = await I.grabTextFrom(_this.mainItem.title); title = title + " " + subtitle.charAt(0).toLowerCase() + subtitle.slice(1); await I.click(_this.mainItem.title) }) return title; }}export default MainPage"><div></div></button></div></figure></div>
<p>and use them in your tests:</p>
<div class="expressive-code"><figure class="frame not-content"><figcaption class="header"></figcaption><pre data-language="js"><code><div class="ec-line"><div class="code"><span style="--0:#7c3aed;--0fw:bold;--1:#7c3aed;--1fw:bold">Scenario</span><span style="--0:#9CA3AF;--1:#4B5563">(</span><span style="--0:#7DD3FC;--1:#0369A1">'open article'</span><span style="--0:#9CA3AF;--1:#4B5563">,</span><span style="--0:#C9D1D9;--1:#1F2937"> </span><span style="--0:#D8B4FE;--1:#7C3AED">async</span><span style="--0:#C9D1D9;--1:#1F2937"> </span><span style="--0:#9CA3AF;--1:#4B5563">({</span><span style="--0:#C9D1D9;--1:#1F2937"> </span><span style="--0:#db2777;--0fw:bold;--1:#db2777;--1fw:bold">I</span><span style="--0:#9CA3AF;--1:#4B5563">,</span><span style="--0:#C9D1D9;--1:#1F2937"> mainPage</span><span style="--0:#9CA3AF;--1:#4B5563">,</span><span style="--0:#C9D1D9;--1:#1F2937"> basePage </span><span style="--0:#9CA3AF;--1:#4B5563">})</span><span style="--0:#C9D1D9;--1:#1F2937"> </span><span style="--0:#D8B4FE;--1:#7C3AED">=></span><span style="--0:#C9D1D9;--1:#1F2937"> </span><span style="--0:#9CA3AF;--1:#4B5563">{</span></div></div><div class="ec-line"><div class="code"><span class="indent"> </span><span style="--0:#D8B4FE;--1:#7C3AED">let</span><span style="--0:#C9D1D9;--1:#1F2937"> title </span><span style="--0:#9CA3AF;--1:#4B5563">=</span><span style="--0:#C9D1D9;--1:#1F2937"> </span><span style="--0:#D8B4FE;--1:#7C3AED">await</span><span style="--0:#C9D1D9;--1:#1F2937"> mainPage</span><span style="--0:#9CA3AF;--1:#4B5563">.</span><span style="--0:#E5E7EB;--1:#1F2937">openMainArticle</span><span style="--0:#9CA3AF;--1:#4B5563">()</span></div></div><div class="ec-line"><div class="code"><span class="indent"><span style="--0:#C9D1D9;--1:#1F2937"> </span></span><span style="--0:#C9D1D9;--1:#1F2937">basePage</span><span style="--0:#9CA3AF;--1:#4B5563">.</span><span style="--0:#E5E7EB;--1:#1F2937">pageShouldBeOpened</span><span style="--0:#9CA3AF;--1:#4B5563">(</span><span style="--0:#C9D1D9;--1:#1F2937">title</span><span style="--0:#9CA3AF;--1:#4B5563">)</span></div></div><div class="ec-line"><div class="code"><span style="--0:#9CA3AF;--1:#4B5563">});</span></div></div></code></pre><div class="copy"><button title="Copy to clipboard" data-copied="Copied!" data-code="Scenario('open article', async ({ I, mainPage, basePage }) => { let title = await mainPage.openMainArticle() basePage.pageShouldBeOpened(title)});"><div></div></button></div></figure></div>
<p>Page objects can also be extended via class inheritance:</p>
<div class="expressive-code"><figure class="frame not-content"><figcaption class="header"></figcaption><pre data-language="js"><code><div class="ec-line"><div class="code"><span style="--0:#D8B4FE;--1:#7C3AED">const</span><span style="--0:#C9D1D9;--1:#1F2937"> </span><span style="--0:#9CA3AF;--1:#4B5563">{</span><span style="--0:#C9D1D9;--1:#1F2937"> </span><span style="--0:#db2777;--0fw:bold;--1:#db2777;--1fw:bold">I</span><span style="--0:#C9D1D9;--1:#1F2937"> </span><span style="--0:#9CA3AF;--1:#4B5563">}</span><span style="--0:#C9D1D9;--1:#1F2937"> </span><span style="--0:#9CA3AF;--1:#4B5563">=</span><span style="--1:#1F2937"><span style="--0:#C9D1D9"> </span><span style="--0:#E5E7EB">inject</span></span><span style="--0:#9CA3AF;--1:#4B5563">();</span></div></div><div class="ec-line"><div class="code">
</div></div><div class="ec-line"><div class="code"><span style="--0:#D8B4FE;--1:#7C3AED">class</span><span style="--0:#C9D1D9;--1:#1F2937"> </span><span style="--0:#F472B6;--1:#C3236A">AttachFile</span><span style="--0:#C9D1D9;--1:#1F2937"> </span><span style="--0:#9CA3AF;--1:#4B5563">{</span></div></div><div class="ec-line"><div class="code"><span class="indent"><span style="--0:#C9D1D9;--1:#1F2937"> </span></span><span style="--0:#C9D1D9;--1:#1F2937">inputFileField </span><span style="--0:#9CA3AF;--1:#4B5563">=</span><span style="--0:#C9D1D9;--1:#1F2937"> </span><span style="--0:#7DD3FC;--1:#0369A1">'input[name=fileUpload]'</span></div></div><div class="ec-line"><div class="code"><span class="indent"><span style="--0:#C9D1D9;--1:#1F2937"> </span></span><span style="--0:#C9D1D9;--1:#1F2937">fileSize </span><span style="--0:#9CA3AF;--1:#4B5563">=</span><span style="--0:#C9D1D9;--1:#1F2937"> </span><span style="--0:#7DD3FC;--1:#0369A1">'.file-size'</span></div></div><div class="ec-line"><div class="code"><span class="indent"><span style="--0:#C9D1D9;--1:#1F2937"> </span></span><span style="--0:#C9D1D9;--1:#1F2937">fileName </span><span style="--0:#9CA3AF;--1:#4B5563">=</span><span style="--0:#C9D1D9;--1:#1F2937"> </span><span style="--0:#7DD3FC;--1:#0369A1">'.file-name'</span></div></div><div class="ec-line"><div class="code">
</div></div><div class="ec-line"><div class="code"><span class="indent"> </span><span style="--0:#D8B4FE;--1:#7C3AED">async</span><span style="--1:#1F2937"><span style="--0:#C9D1D9"> </span><span style="--0:#E5E7EB">attachFileFrom</span></span><span style="--0:#9CA3AF;--1:#4B5563">(</span><span style="--0:#C9D1D9;--1:#1F2937">path</span><span style="--0:#9CA3AF;--1:#4B5563">)</span><span style="--0:#C9D1D9;--1:#1F2937"> </span><span style="--0:#9CA3AF;--1:#4B5563">{</span></div></div><div class="ec-line"><div class="code"><span class="indent"> </span><span style="--0:#D8B4FE;--1:#7C3AED">await</span><span style="--0:#C9D1D9;--1:#1F2937"> </span><span style="--0:#db2777;--0fw:bold;--1:#db2777;--1fw:bold">I</span><span style="--0:#9CA3AF;--1:#4B5563">.</span><span style="--0:#E5E7EB;--1:#1F2937">waitForVisible</span><span style="--0:#9CA3AF;--1:#4B5563">(</span><span style="--0:#C9D1D9;--1:#1F2937">this</span><span style="--0:#9CA3AF;--1:#4B5563">.</span><span style="--0:#C9D1D9;--1:#1F2937">inputFileField</span><span style="--0:#9CA3AF;--1:#4B5563">)</span></div></div><div class="ec-line"><div class="code"><span class="indent"> </span><span style="--0:#D8B4FE;--1:#7C3AED">await</span><span style="--0:#C9D1D9;--1:#1F2937"> </span><span style="--0:#db2777;--0fw:bold;--1:#db2777;--1fw:bold">I</span><span style="--0:#9CA3AF;--1:#4B5563">.</span><span style="--0:#E5E7EB;--1:#1F2937">attachFile</span><span style="--0:#9CA3AF;--1:#4B5563">(</span><span style="--0:#C9D1D9;--1:#1F2937">this</span><span style="--0:#9CA3AF;--1:#4B5563">.</span><span style="--0:#C9D1D9;--1:#1F2937">inputFileField</span><span style="--0:#9CA3AF;--1:#4B5563">,</span><span style="--0:#C9D1D9;--1:#1F2937"> path</span><span style="--0:#9CA3AF;--1:#4B5563">)</span></div></div><div class="ec-line"><div class="code"><span class="indent"> </span><span style="--0:#9CA3AF;--1:#4B5563">}</span></div></div><div class="ec-line"><div class="code">
</div></div><div class="ec-line"><div class="code"><span class="indent"> </span><span style="--0:#D8B4FE;--1:#7C3AED">async</span><span style="--1:#1F2937"><span style="--0:#C9D1D9"> </span><span style="--0:#E5E7EB">hasFileSize</span></span><span style="--0:#9CA3AF;--1:#4B5563">(</span><span style="--0:#C9D1D9;--1:#1F2937">fileSizeText</span><span style="--0:#9CA3AF;--1:#4B5563">)</span><span style="--0:#C9D1D9;--1:#1F2937"> </span><span style="--0:#9CA3AF;--1:#4B5563">{</span></div></div><div class="ec-line"><div class="code"><span class="indent"> </span><span style="--0:#D8B4FE;--1:#7C3AED">await</span><span style="--0:#C9D1D9;--1:#1F2937"> </span><span style="--0:#db2777;--0fw:bold;--1:#db2777;--1fw:bold">I</span><span style="--0:#9CA3AF;--1:#4B5563">.</span><span style="--0:#E5E7EB;--1:#1F2937">waitForElement</span><span style="--0:#9CA3AF;--1:#4B5563">(</span><span style="--0:#C9D1D9;--1:#1F2937">this</span><span style="--0:#9CA3AF;--1:#4B5563">.</span><span style="--0:#C9D1D9;--1:#1F2937">fileSize</span><span style="--0:#9CA3AF;--1:#4B5563">)</span></div></div><div class="ec-line"><div class="code"><span class="indent"> </span><span style="--0:#D8B4FE;--1:#7C3AED">const</span><span style="--0:#C9D1D9;--1:#1F2937"> size </span><span style="--0:#9CA3AF;--1:#4B5563">=</span><span style="--0:#C9D1D9;--1:#1F2937"> </span><span style="--0:#D8B4FE;--1:#7C3AED">await</span><span style="--0:#C9D1D9;--1:#1F2937"> </span><span style="--0:#db2777;--0fw:bold;--1:#db2777;--1fw:bold">I</span><span style="--0:#9CA3AF;--1:#4B5563">.</span><span style="--0:#E5E7EB;--1:#1F2937">grabTextFrom</span><span style="--0:#9CA3AF;--1:#4B5563">(</span><span style="--0:#C9D1D9;--1:#1F2937">this</span><span style="--0:#9CA3AF;--1:#4B5563">.</span><span style="--0:#C9D1D9;--1:#1F2937">fileSize</span><span style="--0:#9CA3AF;--1:#4B5563">)</span></div></div><div class="ec-line"><div class="code"><span class="indent"><span style="--1:#1F2937"> </span></span><span style="--0:#E5E7EB;--1:#1F2937">expect</span><span style="--0:#9CA3AF;--1:#4B5563">(</span><span style="--0:#C9D1D9;--1:#1F2937">size</span><span style="--0:#9CA3AF;--1:#4B5563">).</span><span style="--0:#E5E7EB;--1:#1F2937">toEqual</span><span style="--0:#9CA3AF;--1:#4B5563">(</span><span style="--0:#C9D1D9;--1:#1F2937">fileSizeText</span><span style="--0:#9CA3AF;--1:#4B5563">)</span></div></div><div class="ec-line"><div class="code"><span class="indent"> </span><span style="--0:#9CA3AF;--1:#4B5563">}</span></div></div><div class="ec-line"><div class="code"><span style="--0:#9CA3AF;--1:#4B5563">}</span></div></div><div class="ec-line"><div class="code">
</div></div><div class="ec-line"><div class="code"><span style="--0:#8B949E;--0fs:italic;--1:#636976;--1fs:italic">// Export class for auto-instantiation</span></div></div><div class="ec-line"><div class="code"><span style="--0:#D8B4FE;--1:#7C3AED">export</span><span style="--0:#C9D1D9;--1:#1F2937"> </span><span style="--0:#D8B4FE;--1:#7C3AED">default</span><span style="--0:#C9D1D9;--1:#1F2937"> AttachFile</span></div></div></code></pre><div class="copy"><button title="Copy to clipboard" data-copied="Copied!" data-code="const { I } = inject();class AttachFile { inputFileField = 'input[name=fileUpload]' fileSize = '.file-size' fileName = '.file-name' async attachFileFrom(path) { await I.waitForVisible(this.inputFileField) await I.attachFile(this.inputFileField, path) } async hasFileSize(fileSizeText) { await I.waitForElement(this.fileSize) const size = await I.grabTextFrom(this.fileSize) expect(size).toEqual(fileSizeText) }}// Export class for auto-instantiationexport default AttachFile"><div></div></button></div></figure></div>
<blockquote>
<p>While building complex page objects it is important to keep all <code dir="auto">async</code> functions to be called with <code dir="auto">await</code>. While CodeceptJS allows to run commands synchronously if async function has <code dir="auto">I.grab*</code> or any custom function that returns a promise it must be called with <code dir="auto">await</code>. If you see <code dir="auto">UnhandledPromiseRejectionWarning</code> it might be caused by async page object function that was called without <code dir="auto">await</code>.</p>
</blockquote>
<div class="sl-heading-wrapper level-h2"><h2 id="page-object-lifecycle-hooks">Page Object Lifecycle Hooks</h2><a class="sl-anchor-link" href="#page-object-lifecycle-hooks"><span aria-hidden="true" class="sl-anchor-icon"><svg width="16" height="16" viewBox="0 0 24 24"><path fill="currentcolor" d="m12.11 15.39-3.88 3.88a2.52 2.52 0 0 1-3.5 0 2.47 2.47 0 0 1 0-3.5l3.88-3.88a1 1 0 0 0-1.42-1.42l-3.88 3.89a4.48 4.48 0 0 0 6.33 6.33l3.89-3.88a1 1 0 1 0-1.42-1.42Zm8.58-12.08a4.49 4.49 0 0 0-6.33 0l-3.89 3.88a1 1 0 0 0 1.42 1.42l3.88-3.88a2.52 2.52 0 0 1 3.5 0 2.47 2.47 0 0 1 0 3.5l-3.88 3.88a1 1 0 1 0 1.42 1.42l3.88-3.89a4.49 4.49 0 0 0 0-6.33ZM8.83 15.17a1 1 0 0 0 1.1.22 1 1 0 0 0 .32-.22l4.92-4.92a1 1 0 0 0-1.42-1.42l-4.92 4.92a1 1 0 0 0 0 1.42Z"></path></svg></span><span class="sr-only">Section titled “Page Object Lifecycle Hooks”</span></a></div>
<p>Page objects support lifecycle hooks that mirror the helper hook system. These methods are called automatically by the framework:</p>
<table><thead><tr><th>Hook</th><th>When it runs</th></tr></thead><tbody><tr><td><code dir="auto">_before()</code></td><td>Before the first method call on this page object in a test (lazy, per-test)</td></tr><tr><td><code dir="auto">_after()</code></td><td>After each test, but only if the page object was used in that test</td></tr><tr><td><code dir="auto">_beforeSuite()</code></td><td>Before each Feature/suite (for all page objects that define it)</td></tr><tr><td><code dir="auto">_afterSuite()</code></td><td>After each Feature/suite (for all page objects that define it)</td></tr></tbody></table>
<div class="expressive-code"><figure class="frame not-content"><figcaption class="header"></figcaption><pre data-language="js"><code><div class="ec-line"><div class="code"><span style="--0:#D8B4FE;--1:#7C3AED">const</span><span style="--0:#C9D1D9;--1:#1F2937"> </span><span style="--0:#9CA3AF;--1:#4B5563">{</span><span style="--0:#C9D1D9;--1:#1F2937"> </span><span style="--0:#db2777;--0fw:bold;--1:#db2777;--1fw:bold">I</span><span style="--0:#C9D1D9;--1:#1F2937"> </span><span style="--0:#9CA3AF;--1:#4B5563">}</span><span style="--0:#C9D1D9;--1:#1F2937"> </span><span style="--0:#9CA3AF;--1:#4B5563">=</span><span style="--1:#1F2937"><span style="--0:#C9D1D9"> </span><span style="--0:#E5E7EB">inject</span></span><span style="--0:#9CA3AF;--1:#4B5563">();</span></div></div><div class="ec-line"><div class="code">
</div></div><div class="ec-line"><div class="code"><span style="--0:#D8B4FE;--1:#7C3AED">class</span><span style="--0:#C9D1D9;--1:#1F2937"> </span><span style="--0:#F472B6;--1:#C3236A">DashboardPage</span><span style="--0:#C9D1D9;--1:#1F2937"> </span><span style="--0:#9CA3AF;--1:#4B5563">{</span></div></div><div class="ec-line"><div class="code"><span class="indent"><span style="--1:#1F2937"> </span></span><span style="--0:#E5E7EB;--1:#1F2937">_before</span><span style="--0:#9CA3AF;--1:#4B5563">()</span><span style="--0:#C9D1D9;--1:#1F2937"> </span><span style="--0:#9CA3AF;--1:#4B5563">{</span></div></div><div class="ec-line"><div class="code"><span class="indent"> </span><span style="--0:#db2777;--0fw:bold;--1:#db2777;--1fw:bold">I</span><span style="--0:#9CA3AF;--1:#4B5563">.</span><span style="--0:#E5E7EB;--1:#1F2937">amOnPage</span><span style="--0:#9CA3AF;--1:#4B5563">(</span><span style="--0:#7DD3FC;--1:#0369A1">'/dashboard'</span><span style="--0:#9CA3AF;--1:#4B5563">);</span></div></div><div class="ec-line"><div class="code"><span class="indent"> </span><span style="--0:#db2777;--0fw:bold;--1:#db2777;--1fw:bold">I</span><span style="--0:#9CA3AF;--1:#4B5563">.</span><span style="--0:#E5E7EB;--1:#1F2937">waitForElement</span><span style="--0:#9CA3AF;--1:#4B5563">(</span><span style="--0:#7DD3FC;--1:#0369A1">'.dashboard-loaded'</span><span style="--0:#9CA3AF;--1:#4B5563">);</span></div></div><div class="ec-line"><div class="code"><span class="indent"> </span><span style="--0:#9CA3AF;--1:#4B5563">}</span></div></div><div class="ec-line"><div class="code">
</div></div><div class="ec-line"><div class="code"><span class="indent"><span style="--1:#1F2937"> </span></span><span style="--0:#E5E7EB;--1:#1F2937">_after</span><span style="--0:#9CA3AF;--1:#4B5563">()</span><span style="--0:#C9D1D9;--1:#1F2937"> </span><span style="--0:#9CA3AF;--1:#4B5563">{</span></div></div><div class="ec-line"><div class="code"><span class="indent"> </span><span style="--0:#db2777;--0fw:bold;--1:#db2777;--1fw:bold">I</span><span style="--0:#9CA3AF;--1:#4B5563">.</span><span style="--0:#E5E7EB;--1:#1F2937">clearCookie</span><span style="--0:#9CA3AF;--1:#4B5563">();</span></div></div><div class="ec-line"><div class="code"><span class="indent"> </span><span style="--0:#9CA3AF;--1:#4B5563">}</span></div></div><div class="ec-line"><div class="code">
</div></div><div class="ec-line"><div class="code"><span class="indent"><span style="--1:#1F2937"> </span></span><span style="--0:#E5E7EB;--1:#1F2937">_afterSuite</span><span style="--0:#9CA3AF;--1:#4B5563">()</span><span style="--0:#C9D1D9;--1:#1F2937"> </span><span style="--0:#9CA3AF;--1:#4B5563">{</span></div></div><div class="ec-line"><div class="code"><span class="indent"> </span><span style="--0:#db2777;--0fw:bold;--1:#db2777;--1fw:bold">I</span><span style="--0:#9CA3AF;--1:#4B5563">.</span><span style="--0:#E5E7EB;--1:#1F2937">sendDeleteRequest</span><span style="--0:#9CA3AF;--1:#4B5563">(</span><span style="--0:#7DD3FC;--1:#0369A1">'/api/test-data/cleanup'</span><span style="--0:#9CA3AF;--1:#4B5563">);</span></div></div><div class="ec-line"><div class="code"><span class="indent"> </span><span style="--0:#9CA3AF;--1:#4B5563">}</span></div></div><div class="ec-line"><div class="code">
</div></div><div class="ec-line"><div class="code"><span class="indent"><span style="--1:#1F2937"> </span></span><span style="--0:#E5E7EB;--1:#1F2937">grabStats</span><span style="--0:#9CA3AF;--1:#4B5563">()</span><span style="--0:#C9D1D9;--1:#1F2937"> </span><span style="--0:#9CA3AF;--1:#4B5563">{</span></div></div><div class="ec-line"><div class="code"><span class="indent"> </span><span style="--0:#D8B4FE;--1:#7C3AED">return</span><span style="--0:#C9D1D9;--1:#1F2937"> </span><span style="--0:#db2777;--0fw:bold;--1:#db2777;--1fw:bold">I</span><span style="--0:#9CA3AF;--1:#4B5563">.</span><span style="--0:#E5E7EB;--1:#1F2937">grabTextFrom</span><span style="--0:#9CA3AF;--1:#4B5563">(</span><span style="--0:#7DD3FC;--1:#0369A1">'.stats'</span><span style="--0:#9CA3AF;--1:#4B5563">);</span></div></div><div class="ec-line"><div class="code"><span class="indent"> </span><span style="--0:#9CA3AF;--1:#4B5563">}</span></div></div><div class="ec-line"><div class="code">
</div></div><div class="ec-line"><div class="code"><span class="indent"><span style="--1:#1F2937"> </span></span><span style="--0:#E5E7EB;--1:#1F2937">seeWelcomeMessage</span><span style="--0:#9CA3AF;--1:#4B5563">(</span><span style="--0:#C9D1D9;--1:#1F2937">name</span><span style="--0:#9CA3AF;--1:#4B5563">)</span><span style="--0:#C9D1D9;--1:#1F2937"> </span><span style="--0:#9CA3AF;--1:#4B5563">{</span></div></div><div class="ec-line"><div class="code"><span class="indent"> </span><span style="--0:#db2777;--0fw:bold;--1:#db2777;--1fw:bold">I</span><span style="--0:#9CA3AF;--1:#4B5563">.</span><span style="--0:#E5E7EB;--1:#1F2937">see</span><span style="--0:#9CA3AF;--1:#4B5563">(</span><span style="--0:#7DD3FC;--1:#0369A1">`Welcome, </span><span style="--0:#9CA3AF;--1:#4B5563">${</span><span style="--0:#7DD3FC;--1:#0369A1">name</span><span style="--0:#9CA3AF;--1:#4B5563">}</span><span style="--0:#7DD3FC;--1:#0369A1">`</span><span style="--0:#9CA3AF;--1:#4B5563">,</span><span style="--0:#C9D1D9;--1:#1F2937"> </span><span style="--0:#7DD3FC;--1:#0369A1">'.header'</span><span style="--0:#9CA3AF;--1:#4B5563">);</span></div></div><div class="ec-line"><div class="code"><span class="indent"> </span><span style="--0:#9CA3AF;--1:#4B5563">}</span></div></div><div class="ec-line"><div class="code"><span style="--0:#9CA3AF;--1:#4B5563">}</span></div></div><div class="ec-line"><div class="code">
</div></div><div class="ec-line"><div class="code"><span style="--0:#D8B4FE;--1:#7C3AED">export</span><span style="--0:#C9D1D9;--1:#1F2937"> </span><span style="--0:#D8B4FE;--1:#7C3AED">default</span><span style="--0:#C9D1D9;--1:#1F2937"> DashboardPage</span></div></div></code></pre><div class="copy"><button title="Copy to clipboard" data-copied="Copied!" data-code="const { I } = inject();class DashboardPage { _before() { I.amOnPage('/dashboard'); I.waitForElement('.dashboard-loaded'); } _after() { I.clearCookie(); } _afterSuite() { I.sendDeleteRequest('/api/test-data/cleanup'); } grabStats() { return I.grabTextFrom('.stats'); } seeWelcomeMessage(name) { I.see(`Welcome, ${name}`, '.header'); }}export default DashboardPage"><div></div></button></div></figure></div>
<div class="expressive-code"><figure class="frame not-content"><figcaption class="header"></figcaption><pre data-language="js"><code><div class="ec-line"><div class="code"><span style="--0:#7c3aed;--0fw:bold;--1:#7c3aed;--1fw:bold">Scenario</span><span style="--0:#9CA3AF;--1:#4B5563">(</span><span style="--0:#7DD3FC;--1:#0369A1">'see dashboard stats'</span><span style="--0:#9CA3AF;--1:#4B5563">,</span><span style="--0:#C9D1D9;--1:#1F2937"> </span><span style="--0:#D8B4FE;--1:#7C3AED">async</span><span style="--0:#C9D1D9;--1:#1F2937"> </span><span style="--0:#9CA3AF;--1:#4B5563">({</span><span style="--0:#C9D1D9;--1:#1F2937"> </span><span style="--0:#db2777;--0fw:bold;--1:#db2777;--1fw:bold">I</span><span style="--0:#9CA3AF;--1:#4B5563">,</span><span style="--0:#C9D1D9;--1:#1F2937"> dashboardPage </span><span style="--0:#9CA3AF;--1:#4B5563">})</span><span style="--0:#C9D1D9;--1:#1F2937"> </span><span style="--0:#D8B4FE;--1:#7C3AED">=></span><span style="--0:#C9D1D9;--1:#1F2937"> </span><span style="--0:#9CA3AF;--1:#4B5563">{</span></div></div><div class="ec-line"><div class="code"><span class="indent"> </span><span style="--0:#8B949E;--0fs:italic;--1:#636976;--1fs:italic">// dashboardPage._before() runs automatically before this line</span></div></div><div class="ec-line"><div class="code"><span class="indent"><span style="--0:#C9D1D9;--1:#1F2937"> </span></span><span style="--0:#C9D1D9;--1:#1F2937">dashboardPage</span><span style="--0:#9CA3AF;--1:#4B5563">.</span><span style="--0:#E5E7EB;--1:#1F2937">seeWelcomeMessage</span><span style="--0:#9CA3AF;--1:#4B5563">(</span><span style="--0:#7DD3FC;--1:#0369A1">'John'</span><span style="--0:#9CA3AF;--1:#4B5563">);</span></div></div><div class="ec-line"><div class="code"><span class="indent"> </span><span style="--0:#D8B4FE;--1:#7C3AED">const</span><span style="--0:#C9D1D9;--1:#1F2937"> stats </span><span style="--0:#9CA3AF;--1:#4B5563">=</span><span style="--0:#C9D1D9;--1:#1F2937"> </span><span style="--0:#D8B4FE;--1:#7C3AED">await</span><span style="--0:#C9D1D9;--1:#1F2937"> dashboardPage</span><span style="--0:#9CA3AF;--1:#4B5563">.</span><span style="--0:#E5E7EB;--1:#1F2937">grabStats</span><span style="--0:#9CA3AF;--1:#4B5563">();</span></div></div><div class="ec-line"><div class="code"><span class="indent"> </span><span style="--0:#db2777;--0fw:bold;--1:#db2777;--1fw:bold">I</span><span style="--0:#9CA3AF;--1:#4B5563">.</span><span style="--0:#E5E7EB;--1:#1F2937">say</span><span style="--0:#9CA3AF;--1:#4B5563">(</span><span style="--0:#7DD3FC;--1:#0369A1">`Stats: </span><span style="--0:#9CA3AF;--1:#4B5563">${</span><span style="--0:#7DD3FC;--1:#0369A1">stats</span><span style="--0:#9CA3AF;--1:#4B5563">}</span><span style="--0:#7DD3FC;--1:#0369A1">`</span><span style="--0:#9CA3AF;--1:#4B5563">);</span></div></div><div class="ec-line"><div class="code"><span class="indent"> </span><span style="--0:#8B949E;--0fs:italic;--1:#636976;--1fs:italic">// dashboardPage._after() runs automatically after test ends</span></div></div><div class="ec-line"><div class="code"><span style="--0:#9CA3AF;--1:#4B5563">});</span></div></div></code></pre><div class="copy"><button title="Copy to clipboard" data-copied="Copied!" data-code="Scenario('see dashboard stats', async ({ I, dashboardPage }) => { // dashboardPage._before() runs automatically before this line dashboardPage.seeWelcomeMessage('John'); const stats = await dashboardPage.grabStats(); I.say(`Stats: ${stats}`); // dashboardPage._after() runs automatically after test ends});"><div></div></button></div></figure></div>
<p>Key behaviors:</p>
<ul>
<li><code dir="auto">_before()</code> runs <strong>lazily</strong> — only when the page object is first used in a test, not when it’s injected</li>
<li><code dir="auto">_before()</code> runs <strong>once per test</strong> — calling multiple methods does not re-trigger it</li>
<li><code dir="auto">_after()</code> is <strong>skipped</strong> for page objects that were never used in the test</li>
<li><code dir="auto">_beforeSuite()</code> and <code dir="auto">_afterSuite()</code> run for <strong>all</strong> page objects that define them, regardless of usage</li>
<li>Hook methods are <strong>not</strong> shown as test steps in the output</li>
</ul>
<div class="sl-heading-wrapper level-h2"><h2 id="page-fragments">Page Fragments</h2><a class="sl-anchor-link" href="#page-fragments"><span aria-hidden="true" class="sl-anchor-icon"><svg width="16" height="16" viewBox="0 0 24 24"><path fill="currentcolor" d="m12.11 15.39-3.88 3.88a2.52 2.52 0 0 1-3.5 0 2.47 2.47 0 0 1 0-3.5l3.88-3.88a1 1 0 0 0-1.42-1.42l-3.88 3.89a4.48 4.48 0 0 0 6.33 6.33l3.89-3.88a1 1 0 1 0-1.42-1.42Zm8.58-12.08a4.49 4.49 0 0 0-6.33 0l-3.89 3.88a1 1 0 0 0 1.42 1.42l3.88-3.88a2.52 2.52 0 0 1 3.5 0 2.47 2.47 0 0 1 0 3.5l-3.88 3.88a1 1 0 1 0 1.42 1.42l3.88-3.89a4.49 4.49 0 0 0 0-6.33ZM8.83 15.17a1 1 0 0 0 1.1.22 1 1 0 0 0 .32-.22l4.92-4.92a1 1 0 0 0-1.42-1.42l-4.92 4.92a1 1 0 0 0 0 1.42Z"></path></svg></span><span class="sr-only">Section titled “Page Fragments”</span></a></div>
<p>Similarly, CodeceptJS allows you to generate <strong>PageFragments</strong> and any other abstractions
by running the <code dir="auto">go</code> command with <code dir="auto">--type</code> (or <code dir="auto">-t</code>) option:</p>
<div class="expressive-code"><figure class="frame is-terminal not-content"><figcaption class="header"><span class="title"></span><span class="sr-only">Terminal window</span></figcaption><pre data-language="sh"><code><div class="ec-line"><div class="code"><span style="--1:#1F2937"><span style="--0:#E5E7EB">npx</span><span style="--0:#C9D1D9"> </span></span><span style="--0:#7DD3FC;--1:#0369A1">codeceptjs</span><span style="--0:#C9D1D9;--1:#1F2937"> </span><span style="--0:#7DD3FC;--1:#0369A1">go</span><span style="--0:#C9D1D9;--1:#1F2937"> </span><span style="--0:#7DD3FC;--1:#0369A1">--type</span><span style="--0:#C9D1D9;--1:#1F2937"> </span><span style="--0:#7DD3FC;--1:#0369A1">fragment</span></div></div></code></pre><div class="copy"><button title="Copy to clipboard" data-copied="Copied!" data-code="npx codeceptjs go --type fragment"><div></div></button></div></figure></div>
<p>Page Fragments represent autonomous parts of a page, like modal boxes, components, widgets.
Technically, they are the same as PageObject but conceptually they are a bit different.
For instance, it is recommended that Page Fragment includes a root locator of a component.
Methods of page fragments can use <code dir="auto">within</code> block to narrow scope to a root locator:</p>
<div class="expressive-code"><figure class="frame not-content"><figcaption class="header"></figcaption><pre data-language="js"><code><div class="ec-line"><div class="code"><span style="--0:#D8B4FE;--1:#7C3AED">const</span><span style="--0:#C9D1D9;--1:#1F2937"> </span><span style="--0:#9CA3AF;--1:#4B5563">{</span><span style="--0:#C9D1D9;--1:#1F2937"> </span><span style="--0:#db2777;--0fw:bold;--1:#db2777;--1fw:bold">I</span><span style="--0:#C9D1D9;--1:#1F2937"> </span><span style="--0:#9CA3AF;--1:#4B5563">}</span><span style="--0:#C9D1D9;--1:#1F2937"> </span><span style="--0:#9CA3AF;--1:#4B5563">=</span><span style="--1:#1F2937"><span style="--0:#C9D1D9"> </span><span style="--0:#E5E7EB">inject</span></span><span style="--0:#9CA3AF;--1:#4B5563">();</span></div></div><div class="ec-line"><div class="code">
</div></div><div class="ec-line"><div class="code"><span style="--0:#D8B4FE;--1:#7C3AED">class</span><span style="--0:#C9D1D9;--1:#1F2937"> </span><span style="--0:#F472B6;--1:#C3236A">Modal</span><span style="--0:#C9D1D9;--1:#1F2937"> </span><span style="--0:#9CA3AF;--1:#4B5563">{</span></div></div><div class="ec-line"><div class="code"><span class="indent"><span style="--0:#C9D1D9;--1:#1F2937"> </span></span><span style="--0:#C9D1D9;--1:#1F2937">root </span><span style="--0:#9CA3AF;--1:#4B5563">=</span><span style="--0:#C9D1D9;--1:#1F2937"> </span><span style="--0:#7DD3FC;--1:#0369A1">'#modal'</span></div></div><div class="ec-line"><div class="code">
</div></div><div class="ec-line"><div class="code"><span class="indent"><span style="--1:#1F2937"> </span></span><span style="--0:#E5E7EB;--1:#1F2937">accept</span><span style="--0:#9CA3AF;--1:#4B5563">()</span><span style="--0:#C9D1D9;--1:#1F2937"> </span><span style="--0:#9CA3AF;--1:#4B5563">{</span></div></div><div class="ec-line"><div class="code"><span class="indent"><span style="--1:#1F2937"> </span></span><span style="--0:#E5E7EB;--1:#1F2937">within</span><span style="--0:#9CA3AF;--1:#4B5563">(</span><span style="--0:#C9D1D9;--1:#1F2937">this</span><span style="--0:#9CA3AF;--1:#4B5563">.</span><span style="--0:#C9D1D9;--1:#1F2937">root</span><span style="--0:#9CA3AF;--1:#4B5563">,</span><span style="--0:#C9D1D9;--1:#1F2937"> </span><span style="--0:#D8B4FE;--1:#7C3AED">function</span><span style="--0:#9CA3AF;--1:#4B5563">()</span><span style="--0:#C9D1D9;--1:#1F2937"> </span><span style="--0:#9CA3AF;--1:#4B5563">{</span></div></div><div class="ec-line"><div class="code"><span class="indent"> </span><span style="--0:#db2777;--0fw:bold;--1:#db2777;--1fw:bold">I</span><span style="--0:#9CA3AF;--1:#4B5563">.</span><span style="--0:#E5E7EB;--1:#1F2937">click</span><span style="--0:#9CA3AF;--1:#4B5563">(</span><span style="--0:#7DD3FC;--1:#0369A1">'Accept'</span><span style="--0:#9CA3AF;--1:#4B5563">);</span></div></div><div class="ec-line"><div class="code"><span class="indent"> </span><span style="--0:#9CA3AF;--1:#4B5563">});</span></div></div><div class="ec-line"><div class="code"><span class="indent"> </span><span style="--0:#9CA3AF;--1:#4B5563">}</span></div></div><div class="ec-line"><div class="code"><span style="--0:#9CA3AF;--1:#4B5563">}</span></div></div><div class="ec-line"><div class="code">
</div></div><div class="ec-line"><div class="code"><span style="--0:#D8B4FE;--1:#7C3AED">export</span><span style="--0:#C9D1D9;--1:#1F2937"> </span><span style="--0:#D8B4FE;--1:#7C3AED">default</span><span style="--0:#C9D1D9;--1:#1F2937"> Modal</span></div></div></code></pre><div class="copy"><button title="Copy to clipboard" data-copied="Copied!" data-code="const { I } = inject();class Modal { root = '#modal' accept() { within(this.root, function() { I.click('Accept'); }); }}export default Modal"><div></div></button></div></figure></div>
<p>To use a Page Fragment within a Test Scenario, just inject it into your Scenario:</p>
<div class="expressive-code"><figure class="frame not-content"><figcaption class="header"></figcaption><pre data-language="js"><code><div class="ec-line"><div class="code"><span style="--0:#7c3aed;--0fw:bold;--1:#7c3aed;--1fw:bold">Scenario</span><span style="--0:#9CA3AF;--1:#4B5563">(</span><span style="--0:#7DD3FC;--1:#0369A1">'failed_login'</span><span style="--0:#9CA3AF;--1:#4B5563">,</span><span style="--0:#C9D1D9;--1:#1F2937"> </span><span style="--0:#D8B4FE;--1:#7C3AED">async</span><span style="--0:#C9D1D9;--1:#1F2937"> </span><span style="--0:#9CA3AF;--1:#4B5563">({</span><span style="--0:#C9D1D9;--1:#1F2937"> </span><span style="--0:#db2777;--0fw:bold;--1:#db2777;--1fw:bold">I</span><span style="--0:#9CA3AF;--1:#4B5563">,</span><span style="--0:#C9D1D9;--1:#1F2937"> loginPage</span><span style="--0:#9CA3AF;--1:#4B5563">,</span><span style="--0:#C9D1D9;--1:#1F2937"> modal </span><span style="--0:#9CA3AF;--1:#4B5563">})</span><span style="--0:#C9D1D9;--1:#1F2937"> </span><span style="--0:#D8B4FE;--1:#7C3AED">=></span><span style="--0:#C9D1D9;--1:#1F2937"> </span><span style="--0:#9CA3AF;--1:#4B5563">{</span></div></div><div class="ec-line"><div class="code"><span class="indent"><span style="--0:#C9D1D9;--1:#1F2937"> </span></span><span style="--0:#C9D1D9;--1:#1F2937">loginPage</span><span style="--0:#9CA3AF;--1:#4B5563">.</span><span style="--0:#E5E7EB;--1:#1F2937">sendForm</span><span style="--0:#9CA3AF;--1:#4B5563">(</span><span style="--0:#7DD3FC;--1:#0369A1">'john@doe.com'</span><span style="--0:#9CA3AF;--1:#4B5563">,</span><span style="--0:#7DD3FC;--1:#0369A1">'wrong password'</span><span style="--0:#9CA3AF;--1:#4B5563">);</span></div></div><div class="ec-line"><div class="code"><span class="indent"> </span><span style="--0:#db2777;--0fw:bold;--1:#db2777;--1fw:bold">I</span><span style="--0:#9CA3AF;--1:#4B5563">.</span><span style="--0:#E5E7EB;--1:#1F2937">waitForVisible</span><span style="--0:#9CA3AF;--1:#4B5563">(</span><span style="--0:#C9D1D9;--1:#1F2937">modal</span><span style="--0:#9CA3AF;--1:#4B5563">.</span><span style="--0:#C9D1D9;--1:#1F2937">root</span><span style="--0:#9CA3AF;--1:#4B5563">);</span></div></div><div class="ec-line"><div class="code"><span class="indent"><span style="--1:#1F2937"> </span></span><span style="--0:#E5E7EB;--1:#1F2937">within</span><span style="--0:#9CA3AF;--1:#4B5563">(</span><span style="--0:#C9D1D9;--1:#1F2937">modal</span><span style="--0:#9CA3AF;--1:#4B5563">.</span><span style="--0:#C9D1D9;--1:#1F2937">root</span><span style="--0:#9CA3AF;--1:#4B5563">,</span><span style="--0:#C9D1D9;--1:#1F2937"> </span><span style="--0:#D8B4FE;--1:#7C3AED">function</span><span style="--0:#C9D1D9;--1:#1F2937"> </span><span style="--0:#9CA3AF;--1:#4B5563">()</span><span style="--0:#C9D1D9;--1:#1F2937"> </span><span style="--0:#9CA3AF;--1:#4B5563">{</span></div></div><div class="ec-line"><div class="code"><span class="indent"> </span><span style="--0:#db2777;--0fw:bold;--1:#db2777;--1fw:bold">I</span><span style="--0:#9CA3AF;--1:#4B5563">.</span><span style="--0:#E5E7EB;--1:#1F2937">see</span><span style="--0:#9CA3AF;--1:#4B5563">(</span><span style="--0:#7DD3FC;--1:#0369A1">'Login failed'</span><span style="--0:#9CA3AF;--1:#4B5563">);</span></div></div><div class="ec-line"><div class="code"><span class="indent"> </span><span style="--0:#9CA3AF;--1:#4B5563">})</span></div></div><div class="ec-line"><div class="code"><span style="--0:#9CA3AF;--1:#4B5563">});</span></div></div></code></pre><div class="copy"><button title="Copy to clipboard" data-copied="Copied!" data-code="Scenario('failed_login', async ({ I, loginPage, modal }) => { loginPage.sendForm('john@doe.com','wrong password'); I.waitForVisible(modal.root); within(modal.root, function () { I.see('Login failed'); })});"><div></div></button></div></figure></div>
<p>To use a Page Fragment within a Page Object, you can use <code dir="auto">inject</code> method to get it by its name.</p>
<div class="expressive-code"><figure class="frame not-content"><figcaption class="header"></figcaption><pre data-language="js"><code><div class="ec-line"><div class="code"><span style="--0:#D8B4FE;--1:#7C3AED">const</span><span style="--0:#C9D1D9;--1:#1F2937"> </span><span style="--0:#9CA3AF;--1:#4B5563">{</span><span style="--0:#C9D1D9;--1:#1F2937"> </span><span style="--0:#db2777;--0fw:bold;--1:#db2777;--1fw:bold">I</span><span style="--0:#9CA3AF;--1:#4B5563">,</span><span style="--0:#C9D1D9;--1:#1F2937"> modal </span><span style="--0:#9CA3AF;--1:#4B5563">}</span><span style="--0:#C9D1D9;--1:#1F2937"> </span><span style="--0:#9CA3AF;--1:#4B5563">=</span><span style="--1:#1F2937"><span style="--0:#C9D1D9"> </span><span style="--0:#E5E7EB">inject</span></span><span style="--0:#9CA3AF;--1:#4B5563">();</span></div></div><div class="ec-line"><div class="code">
</div></div><div class="ec-line"><div class="code"><span style="--0:#D8B4FE;--1:#7C3AED">class</span><span style="--0:#C9D1D9;--1:#1F2937"> </span><span style="--0:#F472B6;--1:#C3236A">CheckoutPage</span><span style="--0:#C9D1D9;--1:#1F2937"> </span><span style="--0:#9CA3AF;--1:#4B5563">{</span></div></div><div class="ec-line"><div class="code"><span class="indent"><span style="--1:#1F2937"> </span></span><span style="--0:#E5E7EB;--1:#1F2937">confirmOrder</span><span style="--0:#9CA3AF;--1:#4B5563">()</span><span style="--0:#C9D1D9;--1:#1F2937"> </span><span style="--0:#9CA3AF;--1:#4B5563">{</span></div></div><div class="ec-line"><div class="code"><span class="indent"> </span><span style="--0:#db2777;--0fw:bold;--1:#db2777;--1fw:bold">I</span><span style="--0:#9CA3AF;--1:#4B5563">.</span><span style="--0:#E5E7EB;--1:#1F2937">click</span><span style="--0:#9CA3AF;--1:#4B5563">(</span><span style="--0:#7DD3FC;--1:#0369A1">'Place Order'</span><span style="--0:#9CA3AF;--1:#4B5563">);</span></div></div><div class="ec-line"><div class="code"><span class="indent"><span style="--0:#C9D1D9;--1:#1F2937"> </span></span><span style="--0:#C9D1D9;--1:#1F2937">modal</span><span style="--0:#9CA3AF;--1:#4B5563">.</span><span style="--0:#E5E7EB;--1:#1F2937">accept</span><span style="--0:#9CA3AF;--1:#4B5563">();</span></div></div><div class="ec-line"><div class="code"><span class="indent"> </span><span style="--0:#9CA3AF;--1:#4B5563">}</span></div></div><div class="ec-line"><div class="code"><span style="--0:#9CA3AF;--1:#4B5563">}</span></div></div><div class="ec-line"><div class="code">
</div></div><div class="ec-line"><div class="code"><span style="--0:#D8B4FE;--1:#7C3AED">export</span><span style="--0:#C9D1D9;--1:#1F2937"> </span><span style="--0:#D8B4FE;--1:#7C3AED">default</span><span style="--0:#C9D1D9;--1:#1F2937"> CheckoutPage</span></div></div></code></pre><div class="copy"><button title="Copy to clipboard" data-copied="Copied!" data-code="const { I, modal } = inject();class CheckoutPage { confirmOrder() { I.click('Place Order'); modal.accept(); }}export default CheckoutPage"><div></div></button></div></figure></div>
<blockquote>
<p>PageObject and PageFragment names are declared inside <code dir="auto">include</code> section of <code dir="auto">codecept.conf.js</code>. See <a href="#dependency-injection">Dependency Injection</a></p>
</blockquote>
<div class="sl-heading-wrapper level-h2"><h2 id="stepobjects">StepObjects</h2><a class="sl-anchor-link" href="#stepobjects"><span aria-hidden="true" class="sl-anchor-icon"><svg width="16" height="16" viewBox="0 0 24 24"><path fill="currentcolor" d="m12.11 15.39-3.88 3.88a2.52 2.52 0 0 1-3.5 0 2.47 2.47 0 0 1 0-3.5l3.88-3.88a1 1 0 0 0-1.42-1.42l-3.88 3.89a4.48 4.48 0 0 0 6.33 6.33l3.89-3.88a1 1 0 1 0-1.42-1.42Zm8.58-12.08a4.49 4.49 0 0 0-6.33 0l-3.89 3.88a1 1 0 0 0 1.42 1.42l3.88-3.88a2.52 2.52 0 0 1 3.5 0 2.47 2.47 0 0 1 0 3.5l-3.88 3.88a1 1 0 1 0 1.42 1.42l3.88-3.89a4.49 4.49 0 0 0 0-6.33ZM8.83 15.17a1 1 0 0 0 1.1.22 1 1 0 0 0 .32-.22l4.92-4.92a1 1 0 0 0-1.42-1.42l-4.92 4.92a1 1 0 0 0 0 1.42Z"></path></svg></span><span class="sr-only">Section titled “StepObjects”</span></a></div>
<p>StepObjects represent complex actions which involve the usage of multiple web pages. For instance, creating users in the backend, changing permissions, etc.
StepObject can be created similarly to PageObjects or PageFragments:</p>
<div class="expressive-code"><figure class="frame is-terminal not-content"><figcaption class="header"><span class="title"></span><span class="sr-only">Terminal window</span></figcaption><pre data-language="sh"><code><div class="ec-line"><div class="code"><span style="--1:#1F2937"><span style="--0:#E5E7EB">npx</span><span style="--0:#C9D1D9"> </span></span><span style="--0:#7DD3FC;--1:#0369A1">codeceptjs</span><span style="--0:#C9D1D9;--1:#1F2937"> </span><span style="--0:#7DD3FC;--1:#0369A1">go</span><span style="--0:#C9D1D9;--1:#1F2937"> </span><span style="--0:#7DD3FC;--1:#0369A1">--type</span><span style="--0:#C9D1D9;--1:#1F2937"> </span><span style="--0:#7DD3FC;--1:#0369A1">step</span></div></div></code></pre><div class="copy"><button title="Copy to clipboard" data-copied="Copied!" data-code="npx codeceptjs go --type step"><div></div></button></div></figure></div>
<p>Technically, they are the same as PageObjects. StepObjects can inject PageObjects and use multiple POs to make a complex scenarios:</p>
<div class="expressive-code"><figure class="frame not-content"><figcaption class="header"></figcaption><pre data-language="js"><code><div class="ec-line"><div class="code"><span style="--0:#D8B4FE;--1:#7C3AED">const</span><span style="--0:#C9D1D9;--1:#1F2937"> </span><span style="--0:#9CA3AF;--1:#4B5563">{</span><span style="--0:#C9D1D9;--1:#1F2937"> </span><span style="--0:#db2777;--0fw:bold;--1:#db2777;--1fw:bold">I</span><span style="--0:#9CA3AF;--1:#4B5563">,</span><span style="--0:#C9D1D9;--1:#1F2937"> userPage</span><span style="--0:#9CA3AF;--1:#4B5563">,</span><span style="--0:#C9D1D9;--1:#1F2937"> permissionPage </span><span style="--0:#9CA3AF;--1:#4B5563">}</span><span style="--0:#C9D1D9;--1:#1F2937"> </span><span style="--0:#9CA3AF;--1:#4B5563">=</span><span style="--1:#1F2937"><span style="--0:#C9D1D9"> </span><span style="--0:#E5E7EB">inject</span></span><span style="--0:#9CA3AF;--1:#4B5563">();</span></div></div><div class="ec-line"><div class="code">
</div></div><div class="ec-line"><div class="code"><span style="--0:#D8B4FE;--1:#7C3AED">class</span><span style="--0:#C9D1D9;--1:#1F2937"> </span><span style="--0:#F472B6;--1:#C3236A">AdminSteps</span><span style="--0:#C9D1D9;--1:#1F2937"> </span><span style="--0:#9CA3AF;--1:#4B5563">{</span></div></div><div class="ec-line"><div class="code"><span class="indent"><span style="--1:#1F2937"> </span></span><span style="--0:#E5E7EB;--1:#1F2937">createUser</span><span style="--0:#9CA3AF;--1:#4B5563">(</span><span style="--0:#C9D1D9;--1:#1F2937">name</span><span style="--0:#9CA3AF;--1:#4B5563">)</span><span style="--0:#C9D1D9;--1:#1F2937"> </span><span style="--0:#9CA3AF;--1:#4B5563">{</span></div></div><div class="ec-line"><div class="code"><span class="indent"> </span><span style="--0:#8B949E;--0fs:italic;--1:#636976;--1fs:italic">// action composed from actions of page objects</span></div></div><div class="ec-line"><div class="code"><span class="indent"><span style="--0:#C9D1D9;--1:#1F2937"> </span></span><span style="--0:#C9D1D9;--1:#1F2937">userPage</span><span style="--0:#9CA3AF;--1:#4B5563">.</span><span style="--0:#E5E7EB;--1:#1F2937">open</span><span style="--0:#9CA3AF;--1:#4B5563">();</span></div></div><div class="ec-line"><div class="code"><span class="indent"><span style="--0:#C9D1D9;--1:#1F2937"> </span></span><span style="--0:#C9D1D9;--1:#1F2937">userPage</span><span style="--0:#9CA3AF;--1:#4B5563">.</span><span style="--0:#E5E7EB;--1:#1F2937">create</span><span style="--0:#9CA3AF;--1:#4B5563">(</span><span style="--0:#C9D1D9;--1:#1F2937">name</span><span style="--0:#9CA3AF;--1:#4B5563">);</span></div></div><div class="ec-line"><div class="code"><span class="indent"><span style="--0:#C9D1D9;--1:#1F2937"> </span></span><span style="--0:#C9D1D9;--1:#1F2937">permissionPage</span><span style="--0:#9CA3AF;--1:#4B5563">.</span><span style="--0:#E5E7EB;--1:#1F2937">activate</span><span style="--0:#9CA3AF;--1:#4B5563">(</span><span style="--0:#C9D1D9;--1:#1F2937">name</span><span style="--0:#9CA3AF;--1:#4B5563">);</span></div></div><div class="ec-line"><div class="code"><span class="indent"> </span><span style="--0:#9CA3AF;--1:#4B5563">}</span></div></div><div class="ec-line"><div class="code"><span style="--0:#9CA3AF;--1:#4B5563">}</span></div></div><div class="ec-line"><div class="code">
</div></div><div class="ec-line"><div class="code"><span style="--0:#D8B4FE;--1:#7C3AED">export</span><span style="--0:#C9D1D9;--1:#1F2937"> </span><span style="--0:#D8B4FE;--1:#7C3AED">default</span><span style="--0:#C9D1D9;--1:#1F2937"> AdminSteps</span></div></div></code></pre><div class="copy"><button title="Copy to clipboard" data-copied="Copied!" data-code="const { I, userPage, permissionPage } = inject();class AdminSteps { createUser(name) { // action composed from actions of page objects userPage.open(); userPage.create(name); permissionPage.activate(name); }}export default AdminSteps"><div></div></button></div></figure></div>
<div class="sl-heading-wrapper level-h2"><h2 id="data-objects">Data Objects</h2><a class="sl-anchor-link" href="#data-objects"><span aria-hidden="true" class="sl-anchor-icon"><svg width="16" height="16" viewBox="0 0 24 24"><path fill="currentcolor" d="m12.11 15.39-3.88 3.88a2.52 2.52 0 0 1-3.5 0 2.47 2.47 0 0 1 0-3.5l3.88-3.88a1 1 0 0 0-1.42-1.42l-3.88 3.89a4.48 4.48 0 0 0 6.33 6.33l3.89-3.88a1 1 0 1 0-1.42-1.42Zm8.58-12.08a4.49 4.49 0 0 0-6.33 0l-3.89 3.88a1 1 0 0 0 1.42 1.42l3.88-3.88a2.52 2.52 0 0 1 3.5 0 2.47 2.47 0 0 1 0 3.5l-3.88 3.88a1 1 0 1 0 1.42 1.42l3.88-3.89a4.49 4.49 0 0 0 0-6.33ZM8.83 15.17a1 1 0 0 0 1.1.22 1 1 0 0 0 .32-.22l4.92-4.92a1 1 0 0 0-1.42-1.42l-4.92 4.92a1 1 0 0 0 0 1.42Z"></path></svg></span><span class="sr-only">Section titled “Data Objects”</span></a></div>
<p>Page objects can also be used to manage test data via API. <strong>Data Objects</strong> are page object classes that create data using the REST helper and automatically clean it up via the <code dir="auto">_after()</code> hook.</p>
<div class="expressive-code"><figure class="frame not-content"><figcaption class="header"></figcaption><pre data-language="js"><code><div class="ec-line"><div class="code"><span style="--0:#7c3aed;--0fw:bold;--1:#7c3aed;--1fw:bold">Scenario</span><span style="--0:#9CA3AF;--1:#4B5563">(</span><span style="--0:#7DD3FC;--1:#0369A1">'user sees their profile'</span><span style="--0:#9CA3AF;--1:#4B5563">,</span><span style="--0:#C9D1D9;--1:#1F2937"> </span><span style="--0:#D8B4FE;--1:#7C3AED">async</span><span style="--0:#C9D1D9;--1:#1F2937"> </span><span style="--0:#9CA3AF;--1:#4B5563">({</span><span style="--0:#C9D1D9;--1:#1F2937"> </span><span style="--0:#db2777;--0fw:bold;--1:#db2777;--1fw:bold">I</span><span style="--0:#9CA3AF;--1:#4B5563">,</span><span style="--0:#C9D1D9;--1:#1F2937"> userData </span><span style="--0:#9CA3AF;--1:#4B5563">})</span><span style="--0:#C9D1D9;--1:#1F2937"> </span><span style="--0:#D8B4FE;--1:#7C3AED">=></span><span style="--0:#C9D1D9;--1:#1F2937"> </span><span style="--0:#9CA3AF;--1:#4B5563">{</span></div></div><div class="ec-line"><div class="code"><span class="indent"> </span><span style="--0:#D8B4FE;--1:#7C3AED">const</span><span style="--0:#C9D1D9;--1:#1F2937"> user </span><span style="--0:#9CA3AF;--1:#4B5563">=</span><span style="--0:#C9D1D9;--1:#1F2937"> </span><span style="--0:#D8B4FE;--1:#7C3AED">await</span><span style="--0:#C9D1D9;--1:#1F2937"> userData</span><span style="--0:#9CA3AF;--1:#4B5563">.</span><span style="--0:#E5E7EB;--1:#1F2937">createUser</span><span style="--0:#9CA3AF;--1:#4B5563">({</span><span style="--0:#C9D1D9;--1:#1F2937"> name</span><span style="--0:#9CA3AF;--1:#4B5563">:</span><span style="--0:#C9D1D9;--1:#1F2937"> </span><span style="--0:#7DD3FC;--1:#0369A1">'John Doe'</span><span style="--0:#C9D1D9;--1:#1F2937"> </span><span style="--0:#9CA3AF;--1:#4B5563">});</span></div></div><div class="ec-line"><div class="code"><span class="indent"> </span><span style="--0:#db2777;--0fw:bold;--1:#db2777;--1fw:bold">I</span><span style="--0:#9CA3AF;--1:#4B5563">.</span><span style="--0:#E5E7EB;--1:#1F2937">amOnPage</span><span style="--0:#9CA3AF;--1:#4B5563">(</span><span style="--0:#7DD3FC;--1:#0369A1">`/users/</span><span style="--0:#9CA3AF;--1:#4B5563">${</span><span style="--0:#7DD3FC;--1:#0369A1">user</span><span style="--0:#9CA3AF;--1:#4B5563">.</span><span style="--0:#C9D1D9;--1:#1F2937">id</span><span style="--0:#9CA3AF;--1:#4B5563">}</span><span style="--0:#7DD3FC;--1:#0369A1">`</span><span style="--0:#9CA3AF;--1:#4B5563">);</span></div></div><div class="ec-line"><div class="code"><span class="indent"> </span><span style="--0:#db2777;--0fw:bold;--1:#db2777;--1fw:bold">I</span><span style="--0:#9CA3AF;--1:#4B5563">.</span><span style="--0:#E5E7EB;--1:#1F2937">see</span><span style="--0:#9CA3AF;--1:#4B5563">(</span><span style="--0:#7DD3FC;--1:#0369A1">'John Doe'</span><span style="--0:#9CA3AF;--1:#4B5563">);</span></div></div><div class="ec-line"><div class="code"><span class="indent"> </span><span style="--0:#8B949E;--0fs:italic;--1:#636976;--1fs:italic">// userData._after() runs automatically — deletes the created user</span></div></div><div class="ec-line"><div class="code"><span style="--0:#9CA3AF;--1:#4B5563">});</span></div></div></code></pre><div class="copy"><button title="Copy to clipboard" data-copied="Copied!" data-code="Scenario('user sees their profile', async ({ I, userData }) => { const user = await userData.createUser({ name: 'John Doe' }); I.amOnPage(`/users/${user.id}`); I.see('John Doe'); // userData._after() runs automatically — deletes the created user});"><div></div></button></div></figure></div>
<p>This is useful for tests that need API-created data with automatic cleanup, without the overhead of factory configuration.</p>
<p><strong>Learn more:</strong> See <a href="/data#data-objects">Data Objects</a> for complete documentation, examples, and configuration.</p>
<div class="sl-heading-wrapper level-h2"><h2 id="dynamic-injection">Dynamic Injection</h2><a class="sl-anchor-link" href="#dynamic-injection"><span aria-hidden="true" class="sl-anchor-icon"><svg width="16" height="16" viewBox="0 0 24 24"><path fill="currentcolor" d="m12.11 15.39-3.88 3.88a2.52 2.52 0 0 1-3.5 0 2.47 2.47 0 0 1 0-3.5l3.88-3.88a1 1 0 0 0-1.42-1.42l-3.88 3.89a4.48 4.48 0 0 0 6.33 6.33l3.89-3.88a1 1 0 1 0-1.42-1.42Zm8.58-12.08a4.49 4.49 0 0 0-6.33 0l-3.89 3.88a1 1 0 0 0 1.42 1.42l3.88-3.88a2.52 2.52 0 0 1 3.5 0 2.47 2.47 0 0 1 0 3.5l-3.88 3.88a1 1 0 1 0 1.42 1.42l3.88-3.89a4.49 4.49 0 0 0 0-6.33ZM8.83 15.17a1 1 0 0 0 1.1.22 1 1 0 0 0 .32-.22l4.92-4.92a1 1 0 0 0-1.42-1.42l-4.92 4.92a1 1 0 0 0 0 1.42Z"></path></svg></span><span class="sr-only">Section titled “Dynamic Injection”</span></a></div>
<p>Sometimes you need to use a page object in only one or a few tests without adding it to global configuration. Use <code dir="auto">injectDependencies()</code> to inject page objects dynamically per test:</p>
<div class="expressive-code"><figure class="frame has-title not-content"><figcaption class="header"><span class="title">pages/searchPage.js</span></figcaption><pre data-language="js"><code><div class="ec-line"><div class="code"><span style="--0:#D8B4FE;--1:#7C3AED">const</span><span style="--0:#C9D1D9;--1:#1F2937"> </span><span style="--0:#9CA3AF;--1:#4B5563">{</span><span style="--0:#C9D1D9;--1:#1F2937"> </span><span style="--0:#db2777;--0fw:bold;--1:#db2777;--1fw:bold">I</span><span style="--0:#C9D1D9;--1:#1F2937"> </span><span style="--0:#9CA3AF;--1:#4B5563">}</span><span style="--0:#C9D1D9;--1:#1F2937"> </span><span style="--0:#9CA3AF;--1:#4B5563">=</span><span style="--1:#1F2937"><span style="--0:#C9D1D9"> </span><span style="--0:#E5E7EB">inject</span></span><span style="--0:#9CA3AF;--1:#4B5563">();</span></div></div><div class="ec-line"><div class="code">
</div></div><div class="ec-line"><div class="code"><span style="--0:#D8B4FE;--1:#7C3AED">class</span><span style="--0:#C9D1D9;--1:#1F2937"> </span><span style="--0:#F472B6;--1:#C3236A">SearchPage</span><span style="--0:#C9D1D9;--1:#1F2937"> </span><span style="--0:#9CA3AF;--1:#4B5563">{</span></div></div><div class="ec-line"><div class="code"><span class="indent"> </span><span style="--0:#D8B4FE;--1:#7C3AED">constructor</span><span style="--0:#9CA3AF;--1:#4B5563">()</span><span style="--0:#C9D1D9;--1:#1F2937"> </span><span style="--0:#9CA3AF;--1:#4B5563">{</span></div></div><div class="ec-line"><div class="code"><span class="indent"><span style="--0:#C9D1D9;--1:#1F2937"> </span></span><span style="--0:#C9D1D9;--1:#1F2937">this</span><span style="--0:#9CA3AF;--1:#4B5563">.</span><span style="--0:#C9D1D9;--1:#1F2937">searchField </span><span style="--0:#9CA3AF;--1:#4B5563">=</span><span style="--0:#C9D1D9;--1:#1F2937"> </span><span style="--0:#7DD3FC;--1:#0369A1">'#search-input'</span><span style="--0:#9CA3AF;--1:#4B5563">;</span></div></div><div class="ec-line"><div class="code"><span class="indent"><span style="--0:#C9D1D9;--1:#1F2937"> </span></span><span style="--0:#C9D1D9;--1:#1F2937">this</span><span style="--0:#9CA3AF;--1:#4B5563">.</span><span style="--0:#C9D1D9;--1:#1F2937">searchButton </span><span style="--0:#9CA3AF;--1:#4B5563">=</span><span style="--0:#C9D1D9;--1:#1F2937"> </span><span style="--0:#7DD3FC;--1:#0369A1">'button[type=submit]'</span><span style="--0:#9CA3AF;--1:#4B5563">;</span></div></div><div class="ec-line"><div class="code"><span class="indent"> </span><span style="--0:#9CA3AF;--1:#4B5563">}</span></div></div><div class="ec-line"><div class="code">
</div></div><div class="ec-line"><div class="code"><span class="indent"><span style="--1:#1F2937"> </span></span><span style="--0:#E5E7EB;--1:#1F2937">search</span><span style="--0:#9CA3AF;--1:#4B5563">(</span><span style="--0:#C9D1D9;--1:#1F2937">query</span><span style="--0:#9CA3AF;--1:#4B5563">)</span><span style="--0:#C9D1D9;--1:#1F2937"> </span><span style="--0:#9CA3AF;--1:#4B5563">{</span></div></div><div class="ec-line"><div class="code"><span class="indent"> </span><span style="--0:#db2777;--0fw:bold;--1:#db2777;--1fw:bold">I</span><span style="--0:#9CA3AF;--1:#4B5563">.</span><span style="--0:#E5E7EB;--1:#1F2937">fillField</span><span style="--0:#9CA3AF;--1:#4B5563">(</span><span style="--0:#C9D1D9;--1:#1F2937">this</span><span style="--0:#9CA3AF;--1:#4B5563">.</span><span style="--0:#C9D1D9;--1:#1F2937">searchField</span><span style="--0:#9CA3AF;--1:#4B5563">,</span><span style="--0:#C9D1D9;--1:#1F2937"> query</span><span style="--0:#9CA3AF;--1:#4B5563">);</span></div></div><div class="ec-line"><div class="code"><span class="indent"> </span><span style="--0:#db2777;--0fw:bold;--1:#db2777;--1fw:bold">I</span><span style="--0:#9CA3AF;--1:#4B5563">.</span><span style="--0:#E5E7EB;--1:#1F2937">click</span><span style="--0:#9CA3AF;--1:#4B5563">(</span><span style="--0:#C9D1D9;--1:#1F2937">this</span><span style="--0:#9CA3AF;--1:#4B5563">.</span><span style="--0:#C9D1D9;--1:#1F2937">searchButton</span><span style="--0:#9CA3AF;--1:#4B5563">);</span></div></div><div class="ec-line"><div class="code"><span class="indent"> </span><span style="--0:#9CA3AF;--1:#4B5563">}</span></div></div><div class="ec-line"><div class="code"><span style="--0:#9CA3AF;--1:#4B5563">}</span></div></div><div class="ec-line"><div class="code">
</div></div><div class="ec-line"><div class="code"><span style="--0:#D8B4FE;--1:#7C3AED">export</span><span style="--0:#C9D1D9;--1:#1F2937"> </span><span style="--0:#D8B4FE;--1:#7C3AED">default</span><span style="--0:#C9D1D9;--1:#1F2937"> </span><span style="--0:#D8B4FE;--1:#7C3AED">new</span><span style="--1:#1F2937"><span style="--0:#C9D1D9"> </span><span style="--0:#E5E7EB">SearchPage</span></span><span style="--0:#9CA3AF;--1:#4B5563">();</span></div></div></code></pre><div class="copy"><button title="Copy to clipboard" data-copied="Copied!" data-code="const { I } = inject();class SearchPage { constructor() { this.searchField = '#search-input'; this.searchButton = 'button[type=submit]'; } search(query) { I.fillField(this.searchField, query); I.click(this.searchButton); }}export default new SearchPage();"><div></div></button></div></figure></div>
<p>Inject the page object into a specific scenario:</p>
<div class="expressive-code"><figure class="frame not-content"><figcaption class="header"></figcaption><pre data-language="js"><code><div class="ec-line"><div class="code"><span style="--0:#D8B4FE;--1:#7C3AED">import</span><span style="--0:#C9D1D9;--1:#1F2937"> searchPage </span><span style="--0:#D8B4FE;--1:#7C3AED">from</span><span style="--0:#C9D1D9;--1:#1F2937"> </span><span style="--0:#7DD3FC;--1:#0369A1">'./pages/searchPage.js'</span></div></div><div class="ec-line"><div class="code">
</div></div><div class="ec-line"><div class="code"><span style="--0:#7c3aed;--0fw:bold;--1:#7c3aed;--1fw:bold">Scenario</span><span style="--0:#9CA3AF;--1:#4B5563">(</span><span style="--0:#7DD3FC;--1:#0369A1">'user searches for products'</span><span style="--0:#9CA3AF;--1:#4B5563">,</span><span style="--0:#C9D1D9;--1:#1F2937"> </span><span style="--0:#9CA3AF;--1:#4B5563">({</span><span style="--0:#C9D1D9;--1:#1F2937"> </span><span style="--0:#db2777;--0fw:bold;--1:#db2777;--1fw:bold">I</span><span style="--0:#9CA3AF;--1:#4B5563">,</span><span style="--0:#C9D1D9;--1:#1F2937"> searchPage </span><span style="--0:#9CA3AF;--1:#4B5563">})</span><span style="--0:#C9D1D9;--1:#1F2937"> </span><span style="--0:#D8B4FE;--1:#7C3AED">=></span><span style="--0:#C9D1D9;--1:#1F2937"> </span><span style="--0:#9CA3AF;--1:#4B5563">{</span></div></div><div class="ec-line"><div class="code"><span class="indent"> </span><span style="--0:#db2777;--0fw:bold;--1:#db2777;--1fw:bold">I</span><span style="--0:#9CA3AF;--1:#4B5563">.</span><span style="--0:#E5E7EB;--1:#1F2937">amOnPage</span><span style="--0:#9CA3AF;--1:#4B5563">(</span><span style="--0:#7DD3FC;--1:#0369A1">'/'</span><span style="--0:#9CA3AF;--1:#4B5563">);</span></div></div><div class="ec-line"><div class="code"><span class="indent"><span style="--0:#C9D1D9;--1:#1F2937"> </span></span><span style="--0:#C9D1D9;--1:#1F2937">searchPage</span><span style="--0:#9CA3AF;--1:#4B5563">.</span><span style="--0:#E5E7EB;--1:#1F2937">search</span><span style="--0:#9CA3AF;--1:#4B5563">(</span><span style="--0:#7DD3FC;--1:#0369A1">'laptop'</span><span style="--0:#9CA3AF;--1:#4B5563">);</span></div></div><div class="ec-line"><div class="code"><span class="indent"> </span><span style="--0:#db2777;--0fw:bold;--1:#db2777;--1fw:bold">I</span><span style="--0:#9CA3AF;--1:#4B5563">.</span><span style="--0:#E5E7EB;--1:#1F2937">see</span><span style="--0:#9CA3AF;--1:#4B5563">(</span><span style="--0:#7DD3FC;--1:#0369A1">'Search Results'</span><span style="--0:#9CA3AF;--1:#4B5563">);</span></div></div><div class="ec-line"><div class="code"><span style="--0:#9CA3AF;--1:#4B5563">}).</span><span style="--0:#E5E7EB;--1:#1F2937">injectDependencies</span><span style="--0:#9CA3AF;--1:#4B5563">({</span><span style="--0:#C9D1D9;--1:#1F2937"> searchPage </span><span style="--0:#9CA3AF;--1:#4B5563">});</span></div></div></code></pre><div class="copy"><button title="Copy to clipboard" data-copied="Copied!" data-code="import searchPage from './pages/searchPage.js'Scenario('user searches for products', ({ I, searchPage }) => { I.amOnPage('/'); searchPage.search('laptop'); I.see('Search Results');}).injectDependencies({ searchPage });"><div></div></button></div></figure></div>
<p><strong>Use cases:</strong></p>
<ul>
<li>Page objects needed only in a few tests</li>
<li>Test-specific configurations or data objects</li>
<li>Experimenting with new page objects before adding them globally</li>
</ul>
<p><strong>Note:</strong> For page objects used across multiple tests, add them to the <code dir="auto">include</code> section in <code dir="auto">codecept.conf.js</code> instead.</p>
<div class="sl-heading-wrapper level-h2"><h2 id="plain-object-page-objects-legacy">Plain Object Page Objects (Legacy)</h2><a class="sl-anchor-link" href="#plain-object-page-objects-legacy"><span aria-hidden="true" class="sl-anchor-icon"><svg width="16" height="16" viewBox="0 0 24 24"><path fill="currentcolor" d="m12.11 15.39-3.88 3.88a2.52 2.52 0 0 1-3.5 0 2.47 2.47 0 0 1 0-3.5l3.88-3.88a1 1 0 0 0-1.42-1.42l-3.88 3.89a4.48 4.48 0 0 0 6.33 6.33l3.89-3.88a1 1 0 1 0-1.42-1.42Zm8.58-12.08a4.49 4.49 0 0 0-6.33 0l-3.89 3.88a1 1 0 0 0 1.42 1.42l3.88-3.88a2.52 2.52 0 0 1 3.5 0 2.47 2.47 0 0 1 0 3.5l-3.88 3.88a1 1 0 1 0 1.42 1.42l3.88-3.89a4.49 4.49 0 0 0 0-6.33ZM8.83 15.17a1 1 0 0 0 1.1.22 1 1 0 0 0 .32-.22l4.92-4.92a1 1 0 0 0-1.42-1.42l-4.92 4.92a1 1 0 0 0 0 1.42Z"></path></svg></span><span class="sr-only">Section titled “Plain Object Page Objects (Legacy)”</span></a></div>
<p>Plain object page objects are still supported for backward compatibility:</p>
<div class="expressive-code"><figure class="frame not-content"><figcaption class="header"></figcaption><pre data-language="js"><code><div class="ec-line"><div class="code"><span style="--0:#D8B4FE;--1:#7C3AED">const</span><span style="--0:#C9D1D9;--1:#1F2937"> </span><span style="--0:#9CA3AF;--1:#4B5563">{</span><span style="--0:#C9D1D9;--1:#1F2937"> </span><span style="--0:#db2777;--0fw:bold;--1:#db2777;--1fw:bold">I</span><span style="--0:#C9D1D9;--1:#1F2937"> </span><span style="--0:#9CA3AF;--1:#4B5563">}</span><span style="--0:#C9D1D9;--1:#1F2937"> </span><span style="--0:#9CA3AF;--1:#4B5563">=</span><span style="--1:#1F2937"><span style="--0:#C9D1D9"> </span><span style="--0:#E5E7EB">inject</span></span><span style="--0:#9CA3AF;--1:#4B5563">();</span></div></div><div class="ec-line"><div class="code">
</div></div><div class="ec-line"><div class="code"><span style="--0:#D8B4FE;--1:#7C3AED">export</span><span style="--0:#C9D1D9;--1:#1F2937"> </span><span style="--0:#D8B4FE;--1:#7C3AED">default</span><span style="--0:#C9D1D9;--1:#1F2937"> </span><span style="--0:#9CA3AF;--1:#4B5563">{</span></div></div><div class="ec-line"><div class="code"><span class="indent"><span style="--0:#C9D1D9;--1:#1F2937"> </span></span><span style="--0:#C9D1D9;--1:#1F2937">fields</span><span style="--0:#9CA3AF;--1:#4B5563">:</span><span style="--0:#C9D1D9;--1:#1F2937"> </span><span style="--0:#9CA3AF;--1:#4B5563">{</span></div></div><div class="ec-line"><div class="code"><span class="indent"><span style="--0:#C9D1D9;--1:#1F2937"> </span></span><span style="--0:#C9D1D9;--1:#1F2937">email</span><span style="--0:#9CA3AF;--1:#4B5563">:</span><span style="--0:#C9D1D9;--1:#1F2937"> </span><span style="--0:#7DD3FC;--1:#0369A1">'#user_basic_email'</span><span style="--0:#9CA3AF;--1:#4B5563">,</span></div></div><div class="ec-line"><div class="code"><span class="indent"><span style="--0:#C9D1D9;--1:#1F2937"> </span></span><span style="--0:#C9D1D9;--1:#1F2937">password</span><span style="--0:#9CA3AF;--1:#4B5563">:</span><span style="--0:#C9D1D9;--1:#1F2937"> </span><span style="--0:#7DD3FC;--1:#0369A1">'#user_basic_password'</span></div></div><div class="ec-line"><div class="code"><span class="indent"> </span><span style="--0:#9CA3AF;--1:#4B5563">},</span></div></div><div class="ec-line"><div class="code"><span class="indent"><span style="--0:#C9D1D9;--1:#1F2937"> </span></span><span style="--0:#C9D1D9;--1:#1F2937">submitButton</span><span style="--0:#9CA3AF;--1:#4B5563">:</span><span style="--0:#C9D1D9;--1:#1F2937"> </span><span style="--0:#9CA3AF;--1:#4B5563">{</span><span style="--0:#C9D1D9;--1:#1F2937"> css</span><span style="--0:#9CA3AF;--1:#4B5563">:</span><span style="--0:#C9D1D9;--1:#1F2937"> </span><span style="--0:#7DD3FC;--1:#0369A1">'#new_user_basic input[type=submit]'</span><span style="--0:#C9D1D9;--1:#1F2937"> </span><span style="--0:#9CA3AF;--1:#4B5563">},</span></div></div><div class="ec-line"><div class="code">
</div></div><div class="ec-line"><div class="code"><span class="indent"><span style="--1:#1F2937"> </span></span><span style="--0:#E5E7EB;--1:#1F2937">sendForm</span><span style="--0:#9CA3AF;--1:#4B5563">(</span><span style="--0:#C9D1D9;--1:#1F2937">email</span><span style="--0:#9CA3AF;--1:#4B5563">,</span><span style="--0:#C9D1D9;--1:#1F2937"> password</span><span style="--0:#9CA3AF;--1:#4B5563">)</span><span style="--0:#C9D1D9;--1:#1F2937"> </span><span style="--0:#9CA3AF;--1:#4B5563">{</span></div></div><div class="ec-line"><div class="code"><span class="indent"> </span><span style="--0:#db2777;--0fw:bold;--1:#db2777;--1fw:bold">I</span><span style="--0:#9CA3AF;--1:#4B5563">.</span><span style="--0:#E5E7EB;--1:#1F2937">fillField</span><span style="--0:#9CA3AF;--1:#4B5563">(</span><span style="--0:#C9D1D9;--1:#1F2937">this</span><span style="--0:#9CA3AF;--1:#4B5563">.</span><span style="--0:#C9D1D9;--1:#1F2937">fields</span><span style="--0:#9CA3AF;--1:#4B5563">.</span><span style="--0:#C9D1D9;--1:#1F2937">email</span><span style="--0:#9CA3AF;--1:#4B5563">,</span><span style="--0:#C9D1D9;--1:#1F2937"> email</span><span style="--0:#9CA3AF;--1:#4B5563">);</span></div></div><div class="ec-line"><div class="code"><span class="indent"> </span><span style="--0:#db2777;--0fw:bold;--1:#db2777;--1fw:bold">I</span><span style="--0:#9CA3AF;--1:#4B5563">.</span><span style="--0:#E5E7EB;--1:#1F2937">fillField</span><span style="--0:#9CA3AF;--1:#4B5563">(</span><span style="--0:#C9D1D9;--1:#1F2937">this</span><span style="--0:#9CA3AF;--1:#4B5563">.</span><span style="--0:#C9D1D9;--1:#1F2937">fields</span><span style="--0:#9CA3AF;--1:#4B5563">.</span><span style="--0:#C9D1D9;--1:#1F2937">password</span><span style="--0:#9CA3AF;--1:#4B5563">,</span><span style="--0:#C9D1D9;--1:#1F2937"> password</span><span style="--0:#9CA3AF;--1:#4B5563">);</span></div></div><div class="ec-line"><div class="code"><span class="indent"> </span><span style="--0:#db2777;--0fw:bold;--1:#db2777;--1fw:bold">I</span><span style="--0:#9CA3AF;--1:#4B5563">.</span><span style="--0:#E5E7EB;--1:#1F2937">click</span><span style="--0:#9CA3AF;--1:#4B5563">(</span><span style="--0:#C9D1D9;--1:#1F2937">this</span><span style="--0:#9CA3AF;--1:#4B5563">.</span><span style="--0:#C9D1D9;--1:#1F2937">submitButton</span><span style="--0:#9CA3AF;--1:#4B5563">);</span></div></div><div class="ec-line"><div class="code"><span class="indent"> </span><span style="--0:#9CA3AF;--1:#4B5563">}</span></div></div><div class="ec-line"><div class="code"><span style="--0:#9CA3AF;--1:#4B5563">}</span></div></div></code></pre><div class="copy"><button title="Copy to clipboard" data-copied="Copied!" data-code="const { I } = inject();export default { fields: { email: '#user_basic_email', password: '#user_basic_password' }, submitButton: { css: '#new_user_basic input[type=submit]' }, sendForm(email, password) { I.fillField(this.fields.email, email); I.fillField(this.fields.password, password); I.click(this.submitButton); }}"><div></div></button></div></figure></div>
<blockquote>
<p>Class-based page objects are recommended for new code as they support lifecycle hooks and inheritance.</p>
</blockquote> </div> <footer class="sl-flex astro-4qzcips2"> <div class="meta sl-flex astro-4qzcips2"> </div> <div class="pagination-links print:hidden astro-c6pzuqbq" dir="ltr"> <a href="/detox" rel="prev" class="astro-c6pzuqbq"> <svg aria-hidden="true" class="astro-c6pzuqbq astro-cbi3kmoi" width="16" height="16" viewBox="0 0 24 24" fill="currentColor" style="--sl-icon-size: 1.5rem;"><path d="M17 11H9.41l3.3-3.29a1.004 1.004 0 1 0-1.42-1.42l-5 5a1 1 0 0 0-.21.33 1 1 0 0 0 0 .76 1 1 0 0 0 .21.33l5 5a1.002 1.002 0 0 0 1.639-.325 1 1 0 0 0-.219-1.095L9.41 13H17a1 1 0 0 0 0-2Z"/></svg> <span class="astro-c6pzuqbq"> Previous <br class="astro-c6pzuqbq"> <span class="link-title astro-c6pzuqbq">Testing React Native with Detox</span> </span> </a> <a href="/data" rel="next" class="astro-c6pzuqbq"> <svg aria-hidden="true" class="astro-c6pzuqbq astro-cbi3kmoi" width="16" height="16" viewBox="0 0 24 24" fill="currentColor" style="--sl-icon-size: 1.5rem;"><path d="M17.92 11.62a1.001 1.001 0 0 0-.21-.33l-5-5a1.003 1.003 0 1 0-1.42 1.42l3.3 3.29H7a1 1 0 0 0 0 2h7.59l-3.3 3.29a1.002 1.002 0 0 0 .325 1.639 1 1 0 0 0 1.095-.219l5-5a1 1 0 0 0 .21-.33 1 1 0 0 0 0-.76Z"/></svg> <span class="astro-c6pzuqbq"> Next <br class="astro-c6pzuqbq"> <span class="link-title astro-c6pzuqbq">Data Management</span> </span> </a> </div> </footer> </div> </div> </main> </div> </div> </div> <footer class="cc-footer !mt-0 py-16 relative overflow-hidden"> <div class="cc-footer-grain" aria-hidden="true"></div> <div class="max-w-7xl mx-auto px-4 sm:px-6 lg:px-8 relative"> <div class="grid grid-cols-1 md:grid-cols-2 lg:grid-cols-5 gap-8 mb-12"> <div class="lg:col-span-1"> <a href="/" class="inline-flex items-center mb-6" aria-label="CodeceptJS home"> <img src="/logo/logo-violet-dark.svg" alt="CodeceptJS" width="205" height="40" class="h-10 w-auto"> </a> <p class="cc-footer-body leading-relaxed mb-6">
Modern end-to-end testing framework that makes your tests readable, maintainable, and reliable.
</p> <div class="flex space-x-3"> <a href="https://github.com/codeceptjs/CodeceptJS" aria-label="GitHub" class="cc-footer-social"> <svg xmlns="http://www.w3.org/2000/svg" width="18" height="18" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="1.7" stroke-linecap="round" stroke-linejoin="round"><path d="M15 22v-4a4.8 4.8 0 0 0-1-3.5c3 0 6-2 6-5.5.08-1.25-.27-2.48-1-3.5.28-1.15.28-2.35 0-3.5 0 0-1 0-3 1.5-2.64-.5-5.36-.5-8 0C6 2 5 2 5 2c-.3 1.15-.3 2.35 0 3.5A5.403 5.403 0 0 0 4 9c0 3.5 3 5.5 6 5.5-.39.49-.68 1.05-.85 1.65-.17.6-.22 1.23-.15 1.85v4"></path><path d="M9 18c-4.51 2-5-2-7-2"></path></svg> </a> <a href="https://x.com/codeceptjs" aria-label="Twitter / X" class="cc-footer-social"> <svg xmlns="http://www.w3.org/2000/svg" width="18" height="18" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="1.7" stroke-linecap="round" stroke-linejoin="round"><path d="M22 4s-.7 2.1-2 3.4c1.6 10-9.4 17.3-18 11.6 2.2.1 4.4-.6 6-2C3 15.5.5 9.6 3 5c2.2 2.6 5.6 4.1 9 4-.9-4.2 4-6.6 7-3.8 1.1 0 3-1.2 3-1.2z"></path></svg> </a> <a href="https://bit.ly/chat-codeceptjs" aria-label="Slack" class="cc-footer-social"> <svg xmlns="http://www.w3.org/2000/svg" width="18" height="18" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="1.7" stroke-linecap="round" stroke-linejoin="round"><rect width="3" height="8" x="13" y="2" rx="1.5"></rect><path d="M19 8.5V10h1.5A1.5 1.5 0 1 0 19 8.5"></path><rect width="3" height="8" x="8" y="14" rx="1.5"></rect><path d="M5 15.5V14H3.5A1.5 1.5 0 1 0 5 15.5"></path><rect width="8" height="3" x="14" y="13" rx="1.5"></rect><path d="M15.5 19H14v1.5a1.5 1.5 0 1 0 1.5-1.5"></path><rect width="8" height="3" x="2" y="8" rx="1.5"></rect><path d="M8.5 5H10V3.5A1.5 1.5 0 1 0 8.5 5"></path></svg> </a> </div> </div> <div> <h3 class="cc-footer-heading">Get Started</h3> <ul class="space-y-3"> <li><a href="/quickstart" class="cc-footer-link">Quickstart</a></li> <li><a href="/basics" class="cc-footer-link">Web Basics</a></li> <li><a href="/mobile" class="cc-footer-link">Mobile Testing</a></li> <li><a href="/playwright" class="cc-footer-link">Playwright</a></li> <li><a href="/webdriver" class="cc-footer-link">WebDriver</a></li> <li><a href="/best" class="cc-footer-link">Best Practices</a></li> </ul> </div> <div> <h3 class="cc-footer-heading">Reference</h3> <ul class="space-y-3"> <li><a href="/web-api" class="cc-footer-link">Web API</a></li> <li><a href="/mobile-api" class="cc-footer-link">Mobile API</a></li> <li><a href="/locators" class="cc-footer-link">Locators</a></li> <li><a href="/pageobjects" class="cc-footer-link">Page Objects</a></li> <li><a href="/configuration" class="cc-footer-link">Configuration</a></li> <li><a href="/plugins" class="cc-footer-link">Plugins</a></li> </ul> </div> <div> <h3 class="cc-footer-heading">Community</h3> <ul class="space-y-3"> <li><a rel="nofollow" href="https://github.com/codeceptjs/CodeceptJS" class="cc-footer-link">GitHub</a></li> <li><a rel="nofollow" href="https://github.com/codeceptjs/CodeceptJS/discussions" class="cc-footer-link">GitHub Discussions</a></li> <li><a rel="nofollow" href="https://bit.ly/chat-codeceptjs" class="cc-footer-link">Slack Chat</a></li> <li><a rel="nofollow" href="https://github.com/codeceptjs/CodeceptJS/issues" class="cc-footer-link">Issues</a></li> <li><a rel="nofollow" href="https://x.com/codeceptjs" class="cc-footer-link">Twitter</a></li> <li><a href="/media-kit" class="cc-footer-link">Media Kit</a></li> </ul> </div> <div> <h3 class="cc-footer-heading">Try Testomat.io</h3> <a href="https://testomat.io" class="cc-footer-card no-underline"> <div class="cc-footer-card-head"> <span class="cc-footer-card-badge">T</span> <div> <div class="cc-footer-card-title">Testomat.io</div> <div class="cc-footer-card-meta">by CodeceptJS authors</div> </div> </div> <p class="cc-footer-card-body">
Test Case Management for CodeceptJS — built by the team behind it.
</p> <span class="cc-footer-card-cta">
Learn More
<span class="cc-footer-card-arrow" aria-hidden="true">→</span> </span> </a> </div> </div> <div class="cc-footer-bottom"> <div class="flex flex-col md:flex-row justify-between items-center gap-4"> <div class="cc-footer-copy">
© 2026 CodeceptJS · MIT licensed · built by people who actually test things.
</div> <div class="flex items-center gap-6 text-sm"> <a href="#" class="cc-footer-link">Privacy</a> <a href="#" class="cc-footer-link">Terms</a> <a href="https://github.com/codeceptjs/CodeceptJS/blob/4.x/LICENSE" class="cc-footer-link">License</a> </div> </div> </div> </div> </footer> </div> <script is:client="load">
const threshold = 64;
const activeClass = 'scrolled';
const homeClass = 'home-page';
let header;
const sidebarSelector = '#starlight__sidebar .sidebar-content a';
function normalizePath(pathname) {
if (!pathname || pathname === '/') return '/';
return pathname.endsWith('/') ? pathname.slice(0, -1) : pathname;
}
function syncSidebarCurrentLink() {
const links = document.querySelectorAll(sidebarSelector);
if (!links.length) return;
const currentPath = normalizePath(window.location.pathname);
let matched = null;
links.forEach((link) => {
if (!(link instanceof HTMLAnchorElement)) return;
link.removeAttribute('aria-current');
const linkPath = normalizePath(new URL(link.href, window.location.origin).pathname);
if (linkPath === currentPath) matched = link;
});
if (matched) matched.setAttribute('aria-current', 'page');
}
function update() {
if (!header) return;
if (window.scrollY > threshold) {
header.classList.add(activeClass);
} else {
header.classList.remove(activeClass);
}
}
function tagHeaderlessTables() {
// Markdown often produces tables with empty <th> cells when authors
// want a plain N-column grid. Tag those so the stylesheet can hide
// the empty band and force equal column widths.
const tables = document.querySelectorAll('.sl-markdown-content table');
tables.forEach((t) => {
const ths = t.querySelectorAll('thead th');
if (!ths.length) return;
const allEmpty = Array.from(ths).every((th) => !th.textContent || !th.textContent.trim());
t.classList.toggle('cc-table-no-header', allEmpty);
if (allEmpty) {
// Expose the column count so CSS can split width equally.
const firstRow = t.querySelector('tbody tr');
if (firstRow) t.style.setProperty('--col-count', String(firstRow.children.length));
}
});
}
function wrapTablesForScroll() {
// Wrap each docs table in a horizontal-scroll container so wide tables
// scroll inside their own box instead of overflowing the viewport on
// mobile. Idempotent — skips tables already wrapped (re-runs on nav).
const tables = document.querySelectorAll('.sl-markdown-content table');
tables.forEach((table) => {
const parent = table.parentElement;
if (parent && parent.classList.contains('cc-table-scroll')) return;
const wrapper = document.createElement('div');
wrapper.className = 'cc-table-scroll';
table.replaceWith(wrapper);
wrapper.appendChild(table);
});
}
function setup() {
header = document.querySelector('#site-header');
wrapTablesForScroll();
tagHeaderlessTables();
if (!header) return;
const isHome = window.location.pathname === '/';
// Always toggle home-page class on header
header.classList.toggle(homeClass, isHome);
// If home page → disable scroll behavior
if (isHome) {
header.classList.remove(activeClass);
window.removeEventListener('scroll', update);
return;
}
// Non-home pages
update();
syncSidebarCurrentLink();
// Prevent duplicate listeners
window.removeEventListener('scroll', update);
window.addEventListener('scroll', update);
}
// Initial load
setup();
// Re-run after Astro navigation
document.addEventListener('astro:after-swap', () => {
setup();
});
</script> </body></html>