Сессия
Кэширование и восстановление куки, localStorage и sessionStorage для сокращения времени настройки тестов.
Экспериментальная функция
API
sessionв настоящее время находится на стадии разработки, и её можно включить, установив флагexperimentalSessionSupportв значениеtrueв конфигурации Cypress или с помощьюCypress.config()в начале файла спецификации.Включение этого флага выполняет следующие действия:
- Добавляет команду
cy.session()для использования в тестах.- Добавляет API
Cypress.session.- Добавляет следующие новые особенности (которые будут по умолчанию в будущих основных обновлениях Cypress) в начале каждого теста:
- Очистка страницы (установкой её в значение
about:blank).- Очистка всех активных данных сессии (куки,
localStorageиsessionStorage) по всем доменам.- Переопределяет методы
Cypress.Cookies.preserveOnce()иCypress.Cookies.defaults().Поскольку страница очищается перед каждым тестом, необходимо явным образом вызвать
cy.visit()в каждом тесте для перехода на страницу вашего приложения.
Синтаксис
cy.session(id, setup)
cy.session(id, setup, options)
Использование
Правильное использование
// Caching session when logging in via page visit
cy.session(name, () => {
cy.visit('/login')
cy.get('[data-test=name]').type(name)
cy.get('[data-test=password]').type('s3cr3t')
cy.get('form').contains('Log In').click()
cy.url().should('contain', '/login-successful')
})
// Caching session when logging in via API
cy.session([username, password], () => {
cy.request({
method: 'POST',
url: '/login',
body: { username, password },
}).then(({ body }) => {
window.localStorage.setItem('authToken', body.token)
})
})
Неправильное использование
// visiting before calling cy.session() is redundant, it needs to
// be done inside the setup function
cy.visit('/login')
cy.session(name, () => {
// need to call cy.visit() here because the page is blank when
// the setup function runs
cy.get('[data-test=name]').type(name)
cy.get('[data-test=password]').type('s3cr3t')
cy.get('form').contains('Log In').click()
// should assert that login was successful here to guarantee the
// login process completes before it is cached
})
// should have asserted this inside the cy.session() setup
// function because the page is blank here
cy.url().should('contain', '/login-successful')
Аргументы
id (Строка, Массив, Объект)
Уникальный идентификатор, который будет использоваться для кэширования и восстановления заданной сессии. В простых случаях достаточно значения String. Для упрощения создания более сложных идентификаторов, если вы передадите Array или Object, Cypress сгенерирует для вас идентификатор, детерминированно преобразуя переданное значение в строку. Например, если вы передадите ['Jane', '123', 'admin'], для вас будет сгенерирован идентификатор ["Jane","123","admin"].
См. раздел выбора правильного идентификатора для кэширования сессии для более подробного объяснения с примерами.
Обратите внимание, что большие или циклические структуры данных могут быть медленными или сложными для сериализации в идентификатор, поэтому будьте внимательны с данными, которые вы указываете.
setup (Функция)
Эта функция вызывается всякий раз, когда сессия для данного id ещё не была кэширована или больше недействительна (см. опцию validate). После того, как setup выполнится, Cypress сохранит все куки, sessionStorage, и localStorage, так что последующие вызовы cy.session() с тем же id пропустят setup и просто восстановят данные кэшированной сессии.
Страница и все активные данные сессии (куки, localStorage и sessionStorage) по всем доменам всегда очищаются перед выполнением setup.
options (Объект)
| Опция | По умолчанию | Описание |
|---|---|---|
validate |
undefined |
Проверяет вновь созданную или восстановленную сессию. Функция validate выполняется сразу после выполнения функции setup и также каждый раз, когда cy.session() восстанавливает кэшированную сессию. Если функция validate возвращает false, вызывает исключение, возвращает обещание, которое разрешается в false или отклоняется, или содержит любые неисправные команды Cypress, сессия считается недействительной, и setup будет повторно выполнена. Если проверка не пройдёт сразу после выполнения setup, тест завершится неудачно.Страница всегда очищается перед выполнением validate. |
Возвращаемое значение
-
cy.session()возвращаетnull. -
cy.session()не может быть далее использовано в цепочке.
Примеры
Обновление существующей команды логина с пользовательским кодом
Вы можете добавить кэширование сессии в свою команду логина с пользовательским кодом. Оберните внутреннюю часть команды вызовом cy.session().
До
Cypress.Commands.add('login', (username, password) => {
cy.request({
method: 'POST',
url: '/login',
body: { username, password },
}).then(({ body }) => {
window.localStorage.setItem('authToken', body.token)
})
})
После
Cypress.Commands.add('login', (username, password) => {
cy.session([username, password], () => {
cy.request({
method: 'POST',
url: '/login',
body: { username, password },
}).then(({ body }) => {
window.localStorage.setItem('authToken', body.token)
})
})
})
С проверкой сессии
Cypress.Commands.add('login', (username, password) => {
cy.session(
[username, password],
() => {
cy.request({
method: 'POST',
url: '/login',
body: { username, password },
}).then(({ body }) => {
window.localStorage.setItem('authToken', body.token)
})
},
{
validate() {
cy.request('/whoami').its('status').should('eq', 200)
},
}
)
})
Обновление существующей функции помощника логина
Вы можете добавить кэширование сессии в функцию помощника логина, обернув внутреннюю часть функции вызовом cy.session().
До
const login = (name, password) => {
cy.visit('/login')
cy.get('[data-test=name]').type(name)
cy.get('[data-test=password]').type(password)
cy.get('#submit').click()
cy.url().should('contain', '/home')
}
После
const login = (name, password) => {
cy.session([name, password], () => {
cy.visit('/login')
cy.get('[data-test=name]').type(name)
cy.get('[data-test=password]').type(password)
cy.get('#submit').click()
cy.url().should('contain', '/home')
})
}
С проверкой сессии
const login = (name, password) => {
cy.session(
[name, password],
() => {
cy.visit('/login')
cy.get('[data-test=name]').type(name)
cy.get('[data-test=password]').type(password)
cy.get('#submit').click()
cy.url().should('contain', '/home')
},
{
validate() {
cy.visit('/account-details')
},
}
)
}
Проверка сессии внутри setup
Поскольку cy.session() кэширует данные сессии сразу после завершения функции setup, рекомендуется проверить, что процесс входа в систему завершился в конце настройки сессии, чтобы убедиться, что setup не вернётся, прежде чем данные сессии станут доступны для кэширования.
Проверка сессий таким образом может помочь упростить вашу пользовательскую команду логина и уменьшить необходимость условного кэширования сессий.
cy.session('user', () => {
cy.visit('/login')
cy.get('[data-test=name]').type(name)
cy.get('[data-test=password]').type('p4ssw0rd123')
cy.get('#login').click()
// Wait for the post-login redirect to ensure that the
// session actually exists to be cached
cy.url().should('contain', '/login-successful')
})
Условная кэшировка сессии
Спецификации обычно содержат два типа тестов, где требуется вход в систему:
- Тестирование функциональности, которая существует только для авторизованных пользователей
- Тестирование процесса входа в систему
Для первого случая кэширование сессий может быть очень полезным для сокращения времени выполнения тестов. Однако для второго может потребоваться не кэшировать сессию, чтобы можно было проверить другие аспекты процесса входа в систему.
В этом случае может быть полезно создать пользовательскую команду входа в систему, которая будет условно кэшировать сессию. Однако, по возможности, лучше проверить сессию внутри setup.
Cypress.Commands.add('login', (name, { cacheSession = true } = {}) => {
const login = () => {
cy.visit('/login')
cy.get('[data-test=name]').type(name)
cy.get('[data-test=password]').type('p4ssw0rd123')
cy.get('#login').click()
}
if (cacheSession) {
cy.session(name, login)
} else {
login()
}
})
// Testing the login flow itself
describe('login', () => {
it('should redirect to the correct page after logging in', () => {
cy.login('user', { cacheSession: false })
cy.url().should('contain', '/login-successful')
})
})
// Testing something that simply requires being logged in
describe('account details', () => {
it('should have the correct document title', () => {
cy.login('user')
cy.visit('/account')
cy.title().should('eq', 'User Account Details')
})
})
Переключение между сессиями внутри тестов
Поскольку cy.session() очищает страницу и все активные данные сессии перед выполнением setup, вы можете использовать её для лёгкого переключения между сессиями, без необходимости сначала выходить из предыдущего пользователя. Это позволяет тестам более точно отражать реальные сценарии и помогает сократить время выполнения тестов.
const login = (name) => {
cy.session(name, () => {
cy.visit('/login')
cy.get('[data-test=name]').type(name)
cy.get('[data-test=password]').type('s3cr3t')
cy.get('#submit').click()
cy.url().should('contain', '/home')
})
}
it('should transfer money between users', () => {
login('user')
cy.visit('/account')
cy.get('#amount').type('100.00')
cy.get('#send-money').click()
login('other-user')
cy.visit('/account')
cy.get('#balance').should('eq', '100.00')
})
Проверка сессии
Если функция validate возвращает false, вызывает исключение, возвращает обещание, которое разрешается в false или отклоняется, или содержит любые неисправные команды Cypress, сессия считается недействительной, и setup будет повторно выполнена.
Вот несколько validate примеров:
// Attempt to visit a page that only a logged-in user can see
function validate() {
cy.visit('/private')
}
// Make an API request that returns a 200 only when logged in
function validate() {
cy.request('/api/user').its('status').should('eq', 200)
}
// Run any Cypress command that fails if the user is not logged in
function validate() {
cy.visit('/account', { failOnStatusCode: false })
cy.url().should('match', /^/account/)
}
// Or just return false if the session is invalid
function validate() {
if (!MyApp.isSessionValid()) {
return false
}
}
Изменение данных сессии перед кэшированием
Если вы хотите изменить данные кэшированной сессии, вы можете изменять куки, localStorage, sessionStorage по мере необходимости в setup.
cy.session('user', () => {
cy.visit('/login')
cy.get('name').type('user')
cy.get('password').type('p4ssw0rd123')
cy.get('#submit').click()
cy.url().should('contain', '/home')
// Remove session data we don't want to cache
cy.clearCookie('authId')
cy.window().then((win) => {
win.localStorage.removeItem('authToken')
})
// Add session data we do want to cache
cy.setCookie('session_id', '189jd09sufh33aaiidhf99d09')
})
Несколько команд логина
Более сложное приложение может потребовать нескольких команд логина, что может потребовать многократного использования cy.session(). Однако, поскольку значение id используется в качестве уникального идентификатора для сохранения и восстановления сессий, крайне важно, чтобы оно было уникальным для каждой сессии.
В следующем примере, если полученные данные сессии, которые создают loginByForm и loginByApi отличаются каким-либо образом, было бы ошибкой указать [name, password] в качестве id для обоих, так как не было бы возможности отличить сессии, созданные loginByForm("user", "p4ssw0rd") и loginByApi("user", "p4ssw0rd") . Вместо этого, вы можете изменить id так, чтобы оно отличалось между обеими функциями входа, чтобы каждая кэшировалась уникально.
const loginByForm = (name, password) => {
cy.session(['loginByForm', name, password], () => {
cy.visit('/login')
cy.get('[data-test=name]').type(name)
cy.get('[data-test=password]').type(password)
cy.get('#submit').click()
cy.url().should('contain', '/home')
})
}
const loginByApi = (name, password) => {
cy.session(['loginByApi', name, password], () => {
cy.request({
method: 'POST',
url: '/api/login',
body: { name, password },
}).then(({ body }) => {
window.localStorage.setItem('authToken', body.token)
})
})
}
Где вызвать cy.visit()
Если вы вызываете cy.visit() сразу после cy.setup() в вашей функции входа в систему или пользовательской команде, она фактически будет работать так же, как функция входа в систему без кэширования сессии.
const login = (name) => {
cy.session(name, () => {
cy.visit('/login')
cy.get('[data-test=name]').type(name)
cy.get('[data-test=password]').type('s3cr3t')
cy.get('#submit').click()
cy.url().should('contain', '/home')
})
cy.visit('/home')
}
beforeEach(() => {
login('user')
})
it('should test something on the /home page', () => {
// assertions
})
it('should test something else on the /home page', () => {
// assertions
})
Но в тот момент, когда вы хотите протестировать что-то на другой странице, ваш тест фактически вызовет cy.visit() дважды подряд, что приведет к несколько более медленным тестам.
// ...continued...
it('should test something on the /other page', () => {
cy.visit('/other')
// assertions
})
Тесты часто будут быстрее, если вы вызываете cy.visit() только при необходимости. Это особенно хорошо работает, когда организуете тесты в наборы и вызываете cy.visit() после входа в систему внутри beforeEach хука.
const login = (name) => {
cy.session(name, () => {
cy.visit('/login')
cy.get('[data-test=name]').type(name)
cy.get('[data-test=password]').type('s3cr3t')
cy.get('#submit').click()
cy.url().should('contain', '/home')
})
// no visit here
}
describe('home page tests', () => {
beforeEach(() => {
login('user')
cy.visit('/home')
})
it('should test something on the /home page', () => {
// assertions
})
it('should test something else on the /home page', () => {
// assertions
})
})
describe('other page tests', () => {
beforeEach(() => {
login('user')
cy.visit('/other')
})
it('should test something on the /other page', () => {
// assertions
})
})
Обновление функции входа в систему, возвращающей значение
Если ваша пользовательская команда входа в систему возвращает значение, которое вы используете для проверки в тесте, обертывание её с помощью cy.session() сломает этот тест. Однако, как правило, это легко исправить, переписав код входа в систему, чтобы проверить его непосредственно внутри setup.
До
Cypress.Commands.add('loginByApi', (username, password) => {
return cy.request('POST', `/api/login`, {
username,
password,
})
})
it('should return the correct value', () => {
cy.loginByApi('user', 's3cr3t').then((response) => {
expect(response.status).to.eq(200)
})
})
После
Cypress.Commands.add('loginByApi', (username, password) => {
cy.session([username, password], () => {
cy.request('POST', `/api/login`, {
username,
password,
}).then((response) => {
expect(response.status).to.eq(200)
})
})
})
it('is a redundant test', () => {
/* which you can now delete! */
})
Сессии между доменами
Возможно переключение доменов во время кэширования сессий, просто убедитесь, что вы явно посетили домен в вашей команде входа перед вызовом cy.session().
const login = (name) => {
if (location.hostname !== 'example.com') {
cy.visit('example.com')
}
cy.session(name, () => {
cy.visit('/login')
// etc
}, {
validate() {
cy.request('/whoami', {
headers: { 'Authorization' : localStorage.token }
method: 'POST'
}).its('status').should('equal', 200)
}
})
}
it('t1', ()=>{
login('bob')
// do things on example.com
})
it('t2', ()=>{
cy.visit('anotherexample.com')
// do things on anotherexample.com
})
it('t3', ()=>{
login('bob')
// do things on example.com
})
Примечания
Когда очищаются данные страницы и активной сессии
Страница очищается, а все данные активной сессии (куки, localStorage, и sessionStorage) по всем доменам автоматически очищаются, когда выполняется cy.session(). Это гарантирует согласованное поведение при создании или восстановлении сессии и позволяет переключаться между сессиями без предварительного явного выхода из системы.
| Текущая страница очищена | Данные активной сессии очищены | |
|---|---|---|
Перед setup
|
||
Перед validate
|
||
После cy.session()
|
Поскольку вызов cy.session() очищает текущую страницу помимо восстановления данных кэшированной сессии, cy.visit() всегда должен вызываться явно для обеспечения посещения страницы.
Кэширование сессий
После создания сессия для данного id кэшируется на время файла спецификации. Вы не можете изменить сохраненную сессию после кэширования, но всегда можете создать новую сессию с другим id.
Для сокращения времени разработки, при запуске Test Runner в режиме "open", сессии будут кэшироваться между запусками файлов спецификации, пока функция setup не изменится.
Явное очищение сессий
При запуске Test Runner в режиме "open" вы можете явно очистить все сессии и перезапустить файл спецификации, нажав кнопку "Clear All Sessions" в панели инструментов.
Для отладки все сессии можно очистить с помощью метода Cypress.session.clearAllSavedSessions().
Где вызывать cy.session()
Хотя можно явно вызвать cy.session() внутри теста или beforeEach, рекомендуется вызывать cy.session() внутри команды входа custom command или многократно используемой функции-обертки. Смотрите примеры обновления существующей команды входа и обновления существующей вспомогательной функции входа для получения более подробной информации.
Выбор правильного идентификатора для кэширования сессии
Чтобы сессии кэшировались уникально, аргумент id должен быть уникальным для каждой новой созданной сессии.
// If your session setup code uses a string variable, pass in the
// string as the id
const login = (name) => {
cy.session(name, () => {
loginWith(name)
})
}
// If your session setup code uses a single object, pass in the
// object as the id and it will be serialized into an identifier
const login = (params = {}) => {
cy.session(params, () => {
loginWith(params)
})
}
// If your session setup code uses multiple variables, pass in an
// array of those variables and it will be serialized into an
// identifier
const login = (name, email, params = {}) => {
cy.session([name, email, params], () => {
loginWith(name, email, params)
})
}
// If your session setup code uses external constants, they don't
// need to be included in the id, since they will never change
const API_KEY = 'I_AM_AN_API_KEY'
const login = (name, email) => {
cy.session([name, email], () => {
loginWith(name, email, API_KEY)
})
}
Неправильное использование
Если у вас есть код пользовательской login функции, который использует несколько параметров (в этом примере имя, токен и пароль), чтобы иметь возможность входить в систему многим разным пользователям, но id включала только один из них (в этом примере name):
const login = (name, token, password) => {
cy.session(name, () => {
cy.visit('/login')
cy.get('[data-test=name]').type(name)
cy.get('[data-test=token]').type(token)
cy.get('[data-test=password]').type(password)
cy.get('#submit').click()
})
}
Если вы запустили это, user1 вошел бы с token1 и p4ssw0rd, и была бы создана и кэширована сессия, используя "user1" в качестве id.
login('user1', 'token1', 'p4ssw0rd')
Теперь предположим, что вы хотели попробовать войти в систему тому же пользователю, но с другим токеном и/или паролем и ожидаете, что будет создана и кэширована другая сессия. Вы запускаете это, но поскольку cy.session() получает только name в качестве id, он не создаст новую сессию, а вместо этого загрузит сохраненную сессию для "user1".
login('user1', 'different-token', 'p4ssw0rd')
В общем случае необходимо убедиться, что id создается из всех параметров, которые могут меняться внутри функции setup, иначе значения id могут конфликтовать и создавать неожиданные результаты.
Правильное использование
В этом примере, установка id в [name, token, password] гарантирует, что вызов login() с разными значениями name, token и password приведет к созданию и кэшированию уникальных сессий.
const login = (name, token, password) => {
cy.session([name, token, password], () => {
cy.visit('/login')
cy.get('[data-test=name]').type(name)
cy.get('[data-test=token]').type(token)
cy.get('[data-test=password]').type(password)
cy.get('#submit').click()
})
}
Частые вопросы
Почему все мои команды Cypress вызывают ошибки после вызова cy.session()?
Убедитесь, что вызывается cy.visit() после вызова cy.session(), иначе ваши тесты будут выполняться на пустой странице.
Почему я вижу ошибки 401 после вызова cy.session()?
Возможная причина — сессия была аннулирована. Убедитесь, что указана функция validate для того, чтобы cy.session() могла валидировать и, при необходимости, воссоздать сессию.
Журнал команд
Панель инструментов
Всякий раз, когда сессия создается или восстанавливается внутри теста, в верхней части теста отображается дополнительная панель инструментов, предоставляющая больше информации о состоянии ваших сессий.
Нажав на любую сессию id на панели, вы отобразите подробную информацию об этой сессии в консоли, а нажав кнопку "Clear All Sessions", вы очистите все сохраненные сессии и перезапустите файл спецификации (подробнее см. Кэширование сессий).
Журнал команд
Всякий раз, когда вызывается cy.session(), в журнале команд будет отображаться одна из следующих строк, содержащая статус вызова сессии вместе со значением сессии id:
-
Сохраненная сессия не найдена, поэтому новая сессия была создана и сохранена:
-
Найденная сохраненная сессия использована:
-
Найденная сохраненная сессия, но функция
validateзавершилась ошибкой, поэтому сессия была пересоздана и сохранена:
Обратите внимание, что в тех случаях, когда функция validate завершается ошибкой сразу после того, как setup создаст сессию, тест завершится с ошибкой.
Развернув группу сессий в журнале команд, вы увидите все команды, которые были выполнены при создании и/или проверке сессии.
На этом изображении сохраненная сессия восстанавливается, но когда /personal посещается в функции validate, приложение перенаправляется на /signin, что аннулирует сессию. Новая сессия создается путем посещения /signin, где пользователь вошел в систему, после чего проверка завершается успешно, и сессия активна до конца теста.
Вывод в консоль
Нажав на сессию id в панели инструментов или на первую строку в развернутой группе сессий в журнале команд, вы выведете подробную информацию о данной сессии в консоль. Эта информация содержит id вместе с любыми кэшированными данными сессии, включая куки, localStorage и sessionStorage.
См. также
© 2017 Cypress.io
Licensed under the MIT License.
https://docs.cypress.io/api/commands/session