Authorization
The auth plugin logs a user in once and reuses that session for every test that follows. After the first login it stores the cookies (in memory or in a file) and replays them on later tests. If the session expires, the plugin notices and logs in again.
Quick Start
Section titled “Quick Start”Enable the plugin in codecept.conf.js and define one user with login and check functions:
plugins: { auth: { enabled: true, users: { admin: { login: (I) => { I.amOnPage('/login') I.fillField('email', 'admin@site.com') I.fillField('password', secret('123456')) I.click('Sign in') }, check: (I) => { I.amOnPage('/') I.see('Admin', '.navbar') }, }, }, },}Inject login into a test and call it with the user name:
Feature('Dashboard')
Before(({ login }) => { login('admin')})
Scenario('admin sees the dashboard', ({ I }) => { I.amOnPage('/dashboard') I.see('Welcome, Admin')})How It Works
Section titled “How It Works”When you call login('admin'):
restoreopens a page and applies the saved cookies.checkverifies the user is signed in. If it throws or fails an assertion, the plugin assumes the session is dead.loginruns the sign-in flow whenrestore+checkfail (or no cookies exist yet).fetchreads the new cookies and stores them for the next test.
Defaults cover the common case: fetch calls I.grabCookie(), restore calls I.amOnPage('/') then I.setCookie(cookies), and check is a no-op. Override any of them when your app needs something different.
Configuration
Section titled “Configuration”| Option | Default | Purpose |
|---|---|---|
users | — | Map of session names to user definitions. |
inject | 'login' | Name of the function injected into tests. |
saveToFile | false | Write cookies to <output>/<name>_session.json. |
Each user accepts four functions:
login(I)— sign-in flow. Required.check(I, session)— verify the session is still valid. Throw to force a re-login.fetch(I)— return the cookies (or token) to store. Defaults toI.grabCookie().restore(I, session)— replay the stored session. Defaults toI.amOnPage('/')+I.setCookie().
When to Log In: Before vs BeforeSuite
Section titled “When to Log In: Before vs BeforeSuite”You can call login() in either hook. Pick based on how many users a suite touches.
Before — one login per test
Section titled “Before — one login per test”The default and the safe choice. Use it whenever a suite mixes users, or when you are not on Playwright.
Feature('Mixed users')
Scenario('admin can ban a user', ({ I, login }) => { login('admin') I.amOnPage('/users/42') I.click('Ban')})
Scenario('regular user cannot see the ban button', ({ I, login }) => { login('user') I.amOnPage('/users/42') I.dontSee('Ban')})When the user changes between tests, the plugin clears the previous user’s cookies before applying the new ones.
BeforeSuite — one login per suite (Playwright only)
Section titled “BeforeSuite — one login per suite (Playwright only)”Calling login() from BeforeSuite lets Playwright load cookies before it opens the browser, which removes the extra navigation that restore would otherwise need. Use this only when every test in the suite runs as the same user.
Feature('Admin reports')
BeforeSuite(({ login }) => { login('admin')})
Scenario('export sales report', ({ I }) => { I.amOnPage('/reports/sales') I.click('Export')})
Scenario('export traffic report', ({ I }) => { I.amOnPage('/reports/traffic') I.click('Export')})⚠ If a test inside the suite calls
login()with a different user, the plugin resets the cookies and signs in again. That cancels the speed-up. When the suite needs more than one user, preferBefore.
Persisting Sessions to a File
Section titled “Persisting Sessions to a File”Set saveToFile: true to keep sessions across test runs. The plugin writes one JSON file per user into the output directory and reloads them on the next start.
plugins: { auth: { enabled: true, saveToFile: true, users: { admin: { login: (I) => I.loginAsAdmin() } }, },}This is most useful while writing tests: you log in once, then iterate without paying the sign-in cost on every run. Delete the JSON file (or let it expire on the server) to force a fresh login.
Examples
Section titled “Examples”Reuse a steps_file.js helper
Section titled “Reuse a steps_file.js helper”Move the sign-in flow into a custom step and call it from the plugin:
plugins: { auth: { enabled: true, saveToFile: true, users: { admin: { login: (I) => I.loginAdmin(), check: (I) => { I.amOnPage('/') I.see('Admin') }, }, }, },}Multiple users with a custom inject name
Section titled “Multiple users with a custom inject name”Rename the injected function to loginAs for readability:
plugins: { auth: { enabled: true, inject: 'loginAs', users: { user: { login: (I) => { I.amOnPage('/login') I.fillField('email', 'user@site.com') I.fillField('password', secret('123456')) I.click('Login') }, check: (I) => I.see('User', '.navbar'), }, admin: { login: (I) => { I.amOnPage('/login') I.fillField('email', 'admin@site.com') I.fillField('password', secret('123456')) I.click('Login') }, check: (I) => I.see('Admin', '.navbar'), }, }, },}Inside a test:
Before(({ loginAs }) => loginAs('user'))Let the helper keep cookies, skip fetch/restore
Section titled “Let the helper keep cookies, skip fetch/restore”If your helper already keeps cookies between tests (e.g. WebDriver’s keepCookies: true), disable fetch and restore so the plugin only handles the first login:
helpers: { WebDriver: { keepCookies: true },},plugins: { auth: { enabled: true, users: { admin: { login: (I) => { I.amOnPage('/login') I.fillField('email', 'admin@site.com') I.fillField('password', secret('123456')) I.click('Login') }, check: (I) => { I.amOnPage('/dashboard') I.see('Admin', '.navbar') }, fetch: () => {}, restore: () => {}, }, }, },}Sessions stored in local storage
Section titled “Sessions stored in local storage”Override fetch and restore to read and write a token instead of cookies:
plugins: { auth: { enabled: true, users: { admin: { login: (I) => I.loginAsAdmin(), check: (I) => I.see('Admin', '.navbar'), fetch: (I) => I.executeScript(() => localStorage.getItem('session_id')), restore: (I, session) => { I.amOnPage('/') I.executeScript((s) => localStorage.setItem('session_id', s), session) }, }, }, },}Async login
Section titled “Async login”When login, check, restore, or fetch is async, the plugin awaits it. Inside your test, await the injected function:
plugins: { auth: { enabled: true, users: { admin: { login: async (I) => { const phrase = await I.grabTextFrom('#phrase') I.fillField('username', 'admin') I.fillField('password', secret('password')) I.fillField('phrase', phrase) }, check: (I) => { I.amOnPage('/') I.see('Admin') }, }, }, },}Scenario('login', async ({ login }) => { await login('admin')})Validate the session inside check
Section titled “Validate the session inside check”check receives the value returned by fetch as its second argument. Throw from check to force a fresh login:
plugins: { auth: { enabled: true, users: { admin: { login: (I) => I.loginAsAdmin(), check: (I, session) => { if (session.profile.email !== 'admin@site.com') { throw new Error('Wrong user signed in') } }, }, }, },}- Force a re-login by throwing inside
check— the plugin treats it as an expired session and runsloginagain. - Mask credentials with
secret()so passwords never appear in the test output. See Secrets. - Switch users mid-test with
session()when one scenario needs two browsers signed in as different users. See Multiple Sessions.