Salve, pessoa! Como estás?
Hoje a ideia do post é simples: Configurar o Cypress pra gerar coverage num projeto NextJS que já possui Jest.
Este post não inclui:
Vamos começar detalhando as tecnologias do projeto e depois a instalação.
node v16.20.0
npm v8.19.4
"babel-plugin-module-resolver": "^4.1.0",
"next": "^12.3.4",
"next-compose-plugins": "^2.2.0",
"next-optimized-images": "^2.6.1",
"react": "^18.2.0",
"react-dom": "^18.2.0",
"@testing-library/jest-dom": "^6.1.3",
"@testing-library/react": "^14.0.0",
"@testing-library/react-hooks": "^8.0.1",
"@testing-library/user-event": "^14.5.1",
"babel-eslint": "^10.1.0",
"babel-loader": "^8.1.0",
"babel-plugin-inline-react-svg": "^2.0.1",
"babel-plugin-styled-components": "^1.10.7",
"jest": "^26.0.1",
"jest-css-modules": "^2.1.0",
"jest-junit": "^16.0.0",
npm install @cypress/code-coverage babel-plugin-istanbul cypress@12.17.2 istanbul-lib-coverage nyc cypress-wait-until --save-dev
Confira as libs:
getByDataTestId
.Lembre-se: Além da flag --save-dev
para instalar como dev dependencies, se você instala as dependências do seu projeto com a flag --legacy-peer-deps
, não esqueça de inseri-la no final do comando.
"scripts": {
"dev": "CYPRESS_ENV=true PROJECT_ENV=development PORT=3000 next dev",
"pretest": "rm -rf .nyc_output || true",
"test:e2e": "npm run pretest && nyc cypress open",
"test:e2e-headless": "npm run pretest && nyc cypress run --headed",
},
Acima temos os comandos:
dev
: Foi adicionada a env CYPRESS_ENV
que nos auxiliará a fazer uma condicional do plugin istanbul
no babel.config.js
pois o Jest estava reclamando que o mesmo plugin foi inserido duas vezes;pretest
: Remove a pasta .nyc_output
antes de rodar os testes para que não seja cacheado;test:e2e
: Pra execução local, permite que você controle os testes onde será aberta a janela do Cypress;test:e2e-headless
: Comando para ser rodado na pipeline de CI. Repare que foi inserido uma flag --headed
no final pois sem ela o coverage não estava sendo gerado. Ao rodar localmente são abertas e fechadas janelas a cada teste;Para efeitos de documentação, o erro acusado pelo Jest antes da condicional com CYPRESS_ENV
:
Duplicate plugin/preset detected.
If you'd like to use two separate instances of a plugin,
they need separate names, e.g.
"nyc": {
"report-dir": "cypress-coverage",
"extension": [
".js"
],
"all": true,
"include": [
"src/containers/**/*.js"
],
"exclude": [
"cypress/",
"src/containers/**/*.test.js"
],
"check-coverage": true,
"excludeAfterRemap": false,
"reporter": [
"lcov",
"text-summary"
]
},
Novamente, vamos percorrer comando por comando:
.js
dentro de src/containers/
e seus subdiretórios serão incluídos..test.js
dentro de src/containers/
serão excluídos.Atenção na chave include
! No meu caso vou testar somente os arquivos de containers
, ou seja, aqueles que estão na minha pasta: src/containers
e faremos uma hierarquia de diretórios semelhante dentro da pasta cypress
alguns passos a frente.
Neste projeto estou utilizando o arquivo babel.config.js
na pasta raíz, pode ser que no seu projeto haja um arquivo .babelrc
, é válido também.
Pessoalmente não tive uma boa experiência criando os dois, não consegui rodar os testes e a aplicação corretamente.
module.exports = function babelConfig({ cache, env }) {
cache.invalidate(() => process.env.NODE_ENV);
const isCypress = process.env.CYPRESS_ENV === "true";
const presets = [
[
"next/babel",
{
"preset-env": {
modules: env("test") ? "commonjs" : "auto",
},
},
],
];
const plugins = [
[
"module-resolver",
{
alias: {
styles: "./styles",
containers: "./src/containers",
public: "./public",
},
},
],
[
"styled-components",
{
ssr: true,
displayName: true,
preprocess: false,
},
],
["inline-react-svg"],
];
if (isCypress) {
plugins.push("istanbul");
}
return { presets, plugins };
};
A seguir você verá o arquivo cypress.config.js
que deverá ficar na pasta raíz do seu projeto.
const { defineConfig } = require("cypress");
module.exports = defineConfig({
e2e: {
baseUrl: "http://localhost:3000",
supportFile: "cypress/support/e2e.js",
specPattern: "cypress/integration/containers/**/*",
chromeWebSecurity: false,
pageLoadTimeout: 70000,
setupNodeEvents(on, config) {
require("@cypress/code-coverage/task")(on, config);
return config;
},
video: false,
},
env: {
cypress: true,
},
});
cypress
na raíz do seu projeto;integration
, plugins
, support
;Dentro desta pasta criei um diretório chamado containers
e coloquei meus testes dentro: admin.spec.js
, sales.spec.js
e, assim por diante.
Exemplo de teste:
describe("Admin page", () => {
it("Should check if promotional banner is visible", () => {
cy.visit("/");
cy.contains("Promoção relâmpago");
cy.getByDataTestId("banner-promotional").should("exist");
});
});
É necessário criar um arquivo index.js
com o seguinte código:
module.exports = (on, config) => {
require("@cypress/code-coverage/task")(on, config);
on(
"file:preprocessor",
require("@cypress/code-coverage/use-browserify-istanbul"),
);
// important - return config because code coverage plugin
// modifies environment variables there
return config;
};
Vamos criar três arquivos.
e2e.js
import "@cypress/code-coverage/support";
import "./commands";
index.js
import "@cypress/code-coverage/support";
after(() => {
cy.task("coverageReport");
});
commands.js
import "cypress-wait-until";
Cypress.Commands.add("getByDataTestId", (selector, ...args) => {
cy.get(`[data-testid=${selector}]`, ...args);
});
Cypress.on("uncaught:exception", () => false);
Por último mas não menos importante, pode ser que você tenha que adicionar nos seus arquivos .gitignore
, .eslintignore
(se você usa eslint
) e jest.config.js
(no parâmetro coveragePathIgnorePatterns
) as pastas geradas nesse processo para serem ignoradas.
Espero que seu processo seja mais simples do que foi o meu :)