Cypress предоставляет мощный механизм создания кастомных команд, позволяющий расширять стандартный API тестирования и создавать собственные, повторно используемые функции. В TypeScript важно корректно описывать типы этих команд, чтобы обеспечить безопасность кода, автодополнение и ясность при разработке тестов.
Для начала необходимо убедиться, что проект настроен на работу с TypeScript. Основные шаги включают:
npm install cypress typescript --save-dev
tsconfig.json с необходимыми
настройками:{
"compilerOptions": {
"target": "ES6",
"lib": ["ES6", "DOM"],
"types": ["cypress"]
},
"include": ["cypress/**/*.ts"]
}
cypress/support/index.d.ts.Cypress позволяет расширять объект cy через метод
Cypress.Commands.add. Простейший пример:
Cypress.Commands.add('login', (username: string, password: string) => {
cy.get('#username').type(username);
cy.get('#password').type(password);
cy.get('button[type="submit"]').click();
});
Чтобы TypeScript понимал, что cy.login существует,
необходимо описать соответствующий тип.
Cypress использует интерфейс Chainable, который
описывает все команды, возвращающие объект для дальнейшей цепочки
(cy.get().click() и т.д.). Для кастомной команды создаётся
расширение:
declare namespace Cypress {
interface Chainable<Subject = any> {
login(username: string, password: string): Chainable<null>;
}
}
Subject – тип предыдущего объекта в
цепочке. По умолчанию any.Chainable<null> – команда не
возвращает новый объект для цепочки, поэтому указываем
null. Если команда возвращает элемент DOM, можно указать
Chainable<JQuery<HTMLElement>>.После этого TypeScript будет корректно проверять вызовы
cy.login('user', 'pass').
Некоторые команды могут возвращать данные для использования в цепочке:
Cypress.Commands.add('getUserData', (userId: number) => {
return cy.request(`/api/users/${userId}`).then((response) => response.body);
});
Соответствующий тип:
declare namespace Cypress {
interface Chainable<Subject = any> {
getUserData(userId: number): Chainable<{ id: number; name: string; email: string }>;
}
}
Теперь cy.getUserData(1).then(user => ...) будет
типизировано с полями id, name и
email.
Cypress поддерживает опции для кастомных команд, например
{ prevSubject: true | 'element' }. Это важно для команд,
которые должны работать с предыдущим элементом цепочки:
Cypress.Commands.add(
'highlight',
{ prevSubject: 'element' },
(subject) => {
subject.css('border', '2px solid red');
return subject;
}
);
Тип для такой команды:
declare namespace Cypress {
interface Chainable<Subject = any> {
highlight(): Chainable<JQuery<HTMLElement>>;
}
}
prevSubject: 'element' означает, что команда
вызывается на элементе, полученном из предыдущей команды
(cy.get('button').highlight()).Chainable<JQuery<HTMLElement>>
позволяет продолжить работу с элементом.prevSubjectprevSubject: false – команда
независима от предыдущего элемента.prevSubject: true – команда получает
предыдущий объект и возвращает что-то.prevSubject: ['element', 'optional'] –
команда может быть вызвана с элементом или без него.Пример с необязательным объектом:
Cypress.Commands.add(
'customAction',
{ prevSubject: ['optional', 'element'] },
(subject, option?: string) => {
if (subject) {
subject.css('background', option || 'yellow');
return subject;
}
cy.log('No element found');
return null;
}
);
declare namespace Cypress {
interface Chainable<Subject = any> {
customAction(option?: string): Chainable<JQuery<HTMLElement> | null>;
}
}
Для крупных проектов рекомендуется хранить типы кастомных команд отдельно:
cypress/
support/
commands.ts
index.d.ts
commands.ts – реализация командindex.d.ts – расширение интерфейса
Chainable/// <reference types="cypress" /> в
index.d.ts гарантирует автодополнение.JQuery<HTMLElement>.any без необходимости – это снижает пользу
TypeScript.prevSubject для цепочек с DOM-элементами,
чтобы команды были более гибкими и безопасными.Правильная типизация кастомных команд позволяет создавать надёжные, читаемые и легко поддерживаемые тесты, а также использовать все преимущества TypeScript: автодополнение, проверку типов и предотвращение ошибок на этапе компиляции.