Мы используем Cypress для автоматизации модульных, функциональных и презентационных тестов. Наша методология разработки основывалась на смешивании атомарного дизайна в среде React. Мы балансируем между безжалостной модульностью реагирования и глобальным / региональным / локальным охватом CSS. Таким образом, наша тестовая организация работает по тем же организационным принципам.
Например, у меня есть .../support/button_all.js, который проверяет представление пользовательского интерфейса во всех 5 точках останова нашего сайта (да, 5 - не спрашивайте).
Тесты атомарного паттерна - все кнопки
describe('Global Button Patterns', () => {
context('mobile', () => {
beforeEach(function() {
cy.viewport(320, 740)
})
it('GLOBAL & XS bp: Buttons display as designed', () => {
// $pageURL needs be set/redefined
// by local test variable
// cy.visit('/')
// All Buttons
//cy.document()
cy.get('[data-cy=button]')
.should('be.visible')
.should('have.css', 'background-color', 'rgba(0, 0, 0, 0)')
.should('have.css', 'border-radius', '4px')
.should('have.css', 'font-size', '11px')
.should('have.css', 'font-weight', '800')
.should('have.css', 'letter-spacing', '0.96px')
.should('have.css', 'text-transform', 'uppercase')
cy.get('[data-cy=button')
.should('have.css', 'min-height')
.should('be.gte', '30px')
cy.get('[data-cy=button]')
.should('have.css', 'min-width')
.should('be.gte', '117px')
cy.get('[data-cy=button]')
.should('have.css', 'border-width')
.and('be.gte', '1px')
.and('be.lte', '2px')
cy.get('[data-cy=button]')
.should('have.css', 'line-height')
.and('match', /^15/)
cy.get('[data-cy=button]')
.should('have.css', 'padding-right')
.and('match', /^17/)
// Button - Outline Light
cy.get('.btn-outline-light')
.should('have.css', 'border-color', 'rgb(255, 255, 255)')
.should('have.css', 'color', 'rgb(255, 255, 255)')
// Button - Outline Dark
cy.get('.btn-outline-dark')
.should('have.css', 'border-color', 'rgb(55, 71, 79)')
.should('have.css', 'color', 'rgb(55, 71, 79)')
// Button Light (used for Login buttons)
// Button Light states - :focus, :hover, :active
// Test manually for the moment.
})
})
context('tablet', () => {
beforeEach(function() {
cy.viewport(640, 1136)
})
it('SM bp: Default button displays as designed ', () => {
// cy.visit('/')
// All Buttons
cy.get('[data-cy=button]')
.should('be.visible')
.should('have.css', 'font-size', '11.5px')
.should('have.css', 'letter-spacing', '1.23px')
cy.get('[data-cy=button]')
.should('have.css', 'min-width')
.should('be.gte', '131px')
cy.get('[data-cy=button]')
.should('have.css', 'line-height')
.and('match', /^17/)
cy.get('[data-cy=button]')
.should('have.css', 'padding-right')
.and('match', /^21/)
})
})
context('laptop', () => {
beforeEach(function() {
cy.viewport(960, 600)
})
it('MD bp: Default button displays as designed', () => {
// cy.visit('/')
// All Buttons
cy.get('[data-cy=button]')
.should('be.visible')
.should('have.css', 'font-size', '12.5px')
.should('have.css', 'letter-spacing', '1.34px')
cy.get('[data-cy=button]')
.should('have.css', 'min-width')
.should('be.gte', '139px')
cy.get('[data-cy=button]')
.should('have.css', 'line-height')
.and('match', /^18/)
cy.get('[data-cy=button]')
.should('have.css', 'padding-right')
.and('match', /^21/)
})
})
})
Я хочу включить этот тест в файл integration/components/hero.js. Hero.js содержит 5 контекстов точек останова. Я хочу, чтобы файл button_all.js запускался после запуска контекстов точки останова.
Компонентные тесты - hero.js
import Buttons from '../../support/buttons_all.js'
describe('Hero Component', () => {
context('mobile', () => {
beforeEach(function() {
cy.viewport(320, 740)
})
// global pattern test(s)
after(function() {
// runs once after all tests in the block
})
it('displays Hero Component in mobile bp as designed', () => {
cy.visit('/')
cy.get('[data-cy=hero]')
.should('have.css', 'padding', '14px 0px 40px')
.should('have.css', 'width')
.should('be.gt', '292px')
cy.get('[data-cy=hero]')
.should('have.css', 'background')
cy.get('[data-cy=hero] h1')
.should('have.css', 'color', 'rgb(255, 255, 255)')
.should('have.css', 'font-size', '38px')
.should('have.css', 'line-height').and('match', /^43/)
cy.get('[data-cy=hero] p')
.should('have.css', 'color', 'rgb(255, 255, 255)')
.should('have.css', 'margin-top', '8px')
.should('have.css', 'font-size', '15px')
.should('have.css', 'line-height').and('match', /^18/)
cy.get('[data-cy=hero] .btn-outline-light')
.should('have.css', 'margin-top', '12px')
.should('have.css', 'margin-bottom').and('match', /0/)
})
})
context('tablet', () => {
beforeEach(function() {
cy.viewport(640, 1136)
})
it('displays Hero Component in tablet bp as designed', () => {
cy.visit('/')
cy.get('[data-cy=hero]')
.should('have.css', 'padding', '28px 0px 30px')
.should('have.css', 'width')
.should('be.gt', '580px')
cy.get('[data-cy=hero] h1')
.should('have.css', 'font-size', '48px')
.should('have.css', 'width')
.should('be.gt', '300px')
cy.get('[data-cy=hero] h1')
.should('have.css', 'line-height').and('match', /^54/)
cy.get('[data-cy=hero] p')
.should('have.css', 'margin-top', '8px')
.should('have.css', 'font-size', '15.5px')
cy.get('[data-cy=hero] .btn-outline-light')
.should('have.css', 'margin-top', '12px')
})
})
context('laptop', () => {
beforeEach(function() {
cy.viewport(960, 600)
})
it('displays Hero Component in laptop bp as designed', () => {
cy.visit('/')
cy.get('[data-cy=hero]')
.should('have.css', 'padding', '42px 0px 87.9936px')
.should('have.css', 'width')
.should('be.gt', '860px')
cy.get('[data-cy=hero] h1')
.should('have.css', 'font-size', '52px')
.should('have.css', 'line-height').and('match', /^60/)
cy.get('[data-cy=hero] p')
.should('have.css', 'font-size', '16px')
.should('have.css', 'margin-top', '10px')
.should('have.css', 'line-height').and('match', /^20/)
cy.get('[data-cy=hero] .btn-outline-light')
.should('have.css', 'margin-top', '20px')
})
})
context('desktop', () => {
beforeEach(function() {
cy.viewport(1280, 850)
})
it('displays Hero Component in desktop bp as designed', () => {
cy.visit('/')
cy.get('[data-cy=hero]')
.should('have.css', 'padding', '56px 0px 129.997px')
.should('have.css', 'width')
.should('be.gt', '1168px')
cy.get('[data-cy=hero] h1')
.should('have.css', 'font-size', '60px')
.should('have.css', 'line-height').and('match', /^70/)
cy.get('[data-cy=hero] p')
.should('have.css', 'font-size', '16px')
.should('have.css', 'margin-top', '10px')
.should('have.css', 'line-height').and('match', /^20/)
})
})
context('oversized', () => {
beforeEach(function() {
cy.viewport(1600, 2048)
})
it('displays Hero Component in oversized bp as designed', () => {
cy.visit('/')
cy.get('[data-cy=hero]')
.should('have.css', 'padding', '70px 0px 188px')
})
})
context('buttons_all.js', function(){
})
})
После этого я хочу перейти к своему integration/pages/homepage/hero_spec.js, который объявляет, куда идти, и тестирует фактическое содержимое в этой области страницы. Я хочу, чтобы тест hero.js запускался после контекстов, содержащихся в hero_spec.js.
Тесты на домашней странице - hero_spec.js
import HeroComponent from '../components/hero.js'
describe('Homepage - Hero Component', () => {
context('mobile', () => {
beforeEach(function() {
cy.viewport(320, 740)
})
// global pattern test(s)
after(function() {
// runs once after all tests in the block
})
it('displays Homepage Hero Component as designed in mobile bp', () => {
cy.visit('/')
cy.get('[data-cy=hero]')
.should('have.css', 'background-image')
.should('contain', 'live-life-confidence-1x1', 'contain', '.jpg')
cy.get('[data-cy=hero] h1')
.contains('Lorem Ipsum')
cy.get('[data-cy=hero] p')
.contains('Lorem ipsum sum amit dolor...')
cy.get('[data-cy=hero] .btn-outline-light')
.contains('About us')
.should('have.attr', 'href')
.and('match', /about-us/)
.then((href) => {
cy.visit(href)
})
})
})
context('tablet', () => {
beforeEach(function() {
cy.viewport(640, 1136)
})
after(function() {
// runs once after all tests in the block
})
it('displays Homepage Hero Component as designed in tablet bp', () => {
cy.visit('/')
cy.get('[data-cy=hero]')
.should('have.css', 'background-image')
.should('contain', 'live-life-confidence-2x1', 'contain', '.jpg')
})
})
})
Моя проблема в том, чтобы понять, как проводить тесты по модульному принципу. В этом примере я вижу, что могу импортировать и вызывать чистый файл JS: https://github.com/cypress-io/cypress-example-recipes/blob/master/examples/unit-testing__application-code/cypress/integration/unit_test_application_code_spec.js
Хотя документация Cypress превосходна, кажется, что она ничего не говорит о том, что это можно сделать. Буду признателен за направление для решения этой проблемы.





В приведенном вами примере рецепта все операции импорта выполняются SUT, а не другими тестами.
Чтобы запустить button_all.js внутри другого теста, оберните его код в функцию. Вы также можете передавать параметры для изменения каждого прогона.
export function runButtonAllTests(testParams) {
describe('Global Button Patterns', () => {
context('mobile', () => {
if (testParams.allBreakpoints) {
...
}
Запустите его, вызвав функцию. Обратите внимание: я храню его в папке поддержки, а не в папке интеграции, чтобы Cypress не включил его в «запустить все».
import { runButtonAllTests } from '../../support/run_button_all_tests.js'
describe('Homepage - Hero Component', () => {
context('mobile', () => {
before(function() {
cy.viewport(320, 740)
cy.visit('/')
})
context('buttons_all.js', function() {
runButtonAllTests()
})
Что я упустил в вашем решении?
Красивый! Спасибо, что переработали пример рецепта (github.com/cypress-io/cypress-example-recipes/blob/master/…) до самых простых частей. Работал как шарм!