Cypress 및 페이지 개체 패턴 - EndToEnd 테스트를 위한 모범 사례
속성 이름과 일관성 유지
This is pretty much a no-brainer, but becomes very important the bigger the project is. If you utilize your naming properly, you will be able to abstract your tests in a very neat way. For instance, you can build selectors based on your component names and actions. What I often do is:
<button class="registration-button"
data-test="registration-page__button-start">Register</button>
or
<button class="open-settings"
data-test="settings-page__button-open">Settings</button>
so I follow these conventions all the time, depending on the project:
// it is just important that you are consistent with it and have a system in place
<page-name>__<element-type>-<semantic-action>
or even
<page-name>-<component-name>__<element-type>-<semantic-action>
or just
<component-name>__<element-type>-<semantic-action>
This helps me to unify selectors and I do not even need to look 👀 at my markup anymore. The first example button is starting the registration process, hence start
, the second button is opening a widget/components, hence open
. I would recommend that you take the time and define your semantic action and properly communicate them with your team. Good testing starts with a solid naming convention.
If you do this you can save so much time, have a look at the next section where I show how you can utilize this even more.
테스트를 재사용 가능한 구조로 분할
따라서 코딩의 모든 것에서 일단 코드를 복사하기 시작하면 함수를 작성하는 것이 더 나을 수 있습니다. 그.) 코드를 의미론적 논리적 청크로 분할하는 것은 유지 관리를 위해 많은 의미가 있습니다. 그리고 종단 간 테스트를 작성하는 데 시간을 할애하려는 경우 논리를 공유하지 않는 대규모 프로젝트가 있는 경우 10배 더 많은 시간을 소비하게 됩니다.
내가 본 두 가지 주요 패턴이 있으며 특정 앱에 대해 어느 정도 의미가 있습니다. 하나는 페이지 개체 패턴이고 다른 하나는 함수 개체 패턴입니다(적어도 저는 이렇게 부릅니다 😅). 그러나 이 기사에서는 페이지 개체 패턴만 보여줍니다. 더 많은 기능 기반 접근 방식에 관심이 있는 경우 알려주십시오. 😎
페이지 개체 패턴
페이지 개체 패턴은 의미론적으로 페이지 요소를 클래스로 그룹화하는 방법입니다. 소규모 프로젝트에 복잡성 계층을 추가할 수 있기 때문에 눈살을 찌푸리는 경우가 많습니다. 그러나 클래스는 특히 프로젝트가 커지면 공간이 있습니다. 많은 시간을 절약하고 가독성을 향상시키는 방법으로 수업을 활용하는 방법을 보여 드리겠습니다.
양식을 나타내는 두 개의 구성 요소가 있고 둘 다 열 수 있고 제출된 단추와 오류 메시지가 있다고 가정해 보겠습니다. 따라서 구성 요소를 보지 않고도 테스트가 수행해야 할 작업을 이미 상상할 수 있습니다.
사실 거의 모든 폼 구성 요소가 이 작업을 수행합니다! 따라서 다음과 같이 모든 것을 기본 클래스로 추상화할 수 있습니다.
/**
* The Widget Test Interaction Page Object - abstract class!!
*/
class FormPageObject {
defaultOpenSelector = '';
defaultSubmitUrl = '';
defaultSubmitButtonSelector = '';
defaultInterceptName = '';
defaultSelectorForStringInputChange = '';
defaultCloseSelector = '';
defaultMessageSelector = '';
/**
* Opens the widget
*/
open(
selector = this.defaultOpenSelector,
options = {},
inputSelector = ''
) {
return cy
.$(selector, options, inputSelector)
.click({
force: true,
})
.scrollIntoView({ offset: { top: -100, left: 0 } });
}
/**
* Close the widget
*/
close(
selector = this.defaultCloseSelector,
options = {},
inputSelector = ''
) {
return cy.$(selector, options, inputSelector).click({
force: true,
});
}
/**
* The intercept will register with the default name and url and will look for the next request
* hence you can wait for it. By default it is expecting that the response will work.
* You can also force a success true
*/
interceptSubmit(
forceSuccess = false,
forceFailure = false,
shouldSucceed = false,
inputUrl = '',
interceptName = this.defaultInterceptName,
) {
const url = inputUrl ?? this.defaultSubmitUrl;
cy.intercept('POST', url, (req) => {
req.continue((res) => {
if (forceSuccess) {
res.body.success = true;
} else if (forceFailure) {
res.body.success = false;
} else if (!res.body.success && shouldSucceed) {
expect(
res.body.success,
'It was not possible to change the the widget and it was expected that it would be possible'
).to.be.true;
}
return res;
});
}).as(interceptName);
}
updateStringInput(
input = '',
selector = this.defaultSelectorForStringInputChange
) {
let newString = '';
if (input === '') {
return cy.stringInput(selector, input);
}
submit(
selector = this.defaultSubmitButtonSelector,
options = {},
inputSelector = ''
) {
cy.$(selector, options, inputSelector).click({
force: true,
});
}
submitValidator(req, messageSelector = this.defaultMessageSelector) {
cy.isRequestValid(req);
const correctClass = req.response.body.success
? 'alert-success'
: 'alert-danger';
cy.checkForClass(
correctClass,
messageSelector,
`The submit request returned ${req.response.body.success}, but the message had not this class ${correctClass}`
);
}
}
코드를 추상화하고
cy.$
또는 cy.stringInput
와 같은 나만의 도우미 메서드를 사용했습니다. (만약 내가 헬퍼 메소드를 어떻게 구축하는지 관심이 있다면 댓글로 알려주십시오.) 이 추상 클래스의 좋은 점은 이제 다음과 같이 사용할 수 있다는 것입니다. */
class FormComponentA extends FormPageObject {
defaultOpenSelector = 'form-componentA__button--open';
defaultSubmitUrl = /regexForSubmitUrl/;
defaultSubmitButtonSelector = 'form-componentA__button--submit';
defaultInterceptName = 'formComponentASubmit';
defaultSelectorForStringInputChange = 'form-componentA__input-text--type';
defaultMessageSelector = 'form-componentA__message'
}
class FormComponentB extends FormPageObject {
defaultOpenSelector = 'form-componentB__button--open';
defaultSubmitUrl = /regexForSubmitUrl/;
defaultSubmitButtonSelector = 'form-componentB__button--submit';
defaultInterceptName = 'formComponentBSubmit';
defaultSelectorForStringInputChange = 'form-componentB__input-text--type';
defaultMessageSelector = 'form-componentB__message'
// this component needs some custom interaction, e.g. a slider
useSlider(percentage = 0, selector = 'default-selector-for-slider') {
cy.moveSlider(selector, percentage);
}
}
JumpToNamingConvention
이제 :
describe('test-for-component-A', () => {
it('should fail and display error message', () => {
const component = new FormComponentA();
component.open();
// e.g. update name input
component.updateStringInput('Max');
// set up the interceptor and force it to fail
component.interceptSubmit(false, true);
// hit the submit button
component.submit();
// wait for the request and evaluate the response
cy.wait(component.defaultInterceptName).then((req) => {
component.submitValidator(req);
});
});
it('should succeed and display success message', () => {
const component = new FormComponentA();
component.open();
// set up the interceptor and force it to succeed
component.interceptSubmit(true);
component.updateStringInput('Max');
component.submit();
cy.wait(component.defaultInterceptName).then((req) => {
component.submitValidator(req);
});
});
});
Our tests are now very good readable 😇. I think anyone can now understand what the test is actually doing. Readable code is the first step of creating maintainable code! The cool thing is, if we want to create a test for FormComponentB
we can very quickly do so!
Coming back to my earlier statement
You should use a solid naming convention for your tests attributes can really help you utilize the power of this approach.
Because you can also do stuff like this in your base class(es):
constructor(componentName, interceptRegex) {
this.defaultOpenSelector = `.${componentName}__button-open`;
this.defaultSubmitUrl = interceptRegex;
this.defaultSubmitButtonSelector = `.${componentName}__button--submit`;
this.defaultInterceptName = `${componentName}Intercept`;
this.defaultSelectorForStringInputChange = `.${componentName}__input-text--type`;
this.defaultMessageSelector = `.${componentName}-message`;
}
So now you do not even need to write a new class and can use the abstract FormClass
directly. 🔥🔥🔥
const component = new FormPageObject('form-componentB');
알려주세요 🚀🚀🚀
위에 쓰여진 것과 관련하여 도움이 필요하십니까?
첫 번째 이미지는 무엇입니까? 😄
기사가 마음에 드셨나요? 🔥
Reference
이 문제에 관하여(Cypress 및 페이지 개체 패턴 - EndToEnd 테스트를 위한 모범 사례), 우리는 이곳에서 더 많은 자료를 발견하고 링크를 클릭하여 보았다 https://dev.to/snakepy/cypress-and-page-object-pattern-good-practice-for-endtoend-testing-16cm텍스트를 자유롭게 공유하거나 복사할 수 있습니다.하지만 이 문서의 URL은 참조 URL로 남겨 두십시오.
우수한 개발자 콘텐츠 발견에 전념 (Collection and Share based on the CC Protocol.)