├── .gitignore ├── README.md ├── backend ├── .gitignore ├── knexfile.js ├── package-lock.json ├── package.json ├── src │ ├── controllers │ │ ├── IncidentController.js │ │ ├── OngController.js │ │ ├── ProfileController.js │ │ └── SessionController.js │ ├── database │ │ ├── connection.js │ │ ├── db.sqlite │ │ └── migrations │ │ │ ├── 20200327202114_create_ongs.js │ │ │ └── 20200327202618_create_incidents.js │ ├── index.js │ └── routes.js └── yarn.lock ├── cypress.json ├── cypress ├── integration │ └── ongs.spec.js ├── plugins │ └── index.js └── support │ ├── commands.js │ └── index.js ├── frontend ├── .gitignore ├── cypress.json ├── cypress │ ├── fixtures │ │ ├── data.json │ │ ├── example.json │ │ ├── ongId.json │ │ └── ongRegistered.json │ ├── integration │ │ └── ongs.spec.js │ ├── plugins │ │ └── index.js │ ├── screenplay.md │ └── support │ │ ├── commands.js │ │ ├── index.js │ │ ├── pages │ │ ├── Logon.page.js │ │ ├── NewIncident.page.js │ │ ├── Profile.page.js │ │ └── Register.page.js │ │ ├── questions │ │ └── TheOng.js │ │ ├── tasks │ │ ├── DoLogIn.js │ │ ├── DoRegister.js │ │ └── Start.js │ │ └── utils.js ├── package.json ├── public │ ├── favicon.ico │ └── index.html ├── src │ ├── App.js │ ├── Header.js │ ├── assets │ │ ├── heroes.png │ │ └── logo.svg │ ├── global.css │ ├── index.js │ ├── pages │ │ ├── Logon │ │ │ ├── index.js │ │ │ └── styles.css │ │ ├── NewIncident │ │ │ ├── index.js │ │ │ └── styles.css │ │ ├── Profile │ │ │ ├── index.js │ │ │ └── styles.css │ │ └── Register │ │ │ ├── index.js │ │ │ └── styles.css │ ├── routes.js │ └── services │ │ └── api.js └── yarn.lock ├── package-lock.json ├── package.json └── youtube-cypress.png /.gitignore: -------------------------------------------------------------------------------- 1 | node_modules/**/* 2 | npm-debug.* 3 | 4 | # macOS 5 | .DS_Store 6 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 |

Cypress: do zero ao reporte

2 |

Um projeto para aprender sobre o Cypress no Youtube

3 | 4 | ------------ 5 | 6 | [![HitCount](http://hits.dwyl.com/samlucax/youtube-cypress.svg)](http://hits.dwyl.com/samlucax/youtube-cypress) 7 | 8 |

9 | 10 | ## TL;TR; 11 | > **Chegou agora e quer ir direto para o código da primeira aula? [Clique aqui](https://github.com/samlucax/youtube-cypress/tree/video1), e depois faça clone do repositório. Bons estudos.** 12 | 13 | ------------ 14 | 15 | ## Um curso quase gratuito - custa só uma ⭐️ aqui no repositório 16 | 17 | O *Cypress: do zero ao reporte* é uma série de vídeos para quem deseja aprender sobre Cypress na prática. Seguindo uma sequência lógica de conteúdos, o projeto vai desde o mais básico que é a criação do projeto, mostrando problemas comuns e propostas de solução, melhorias de arquitetura com padrões de projeto e finalizando com a geração de relatórios para exibir o resultado dos testes. Toda a teoria e eventuais explicações são dadas a medida em que os problemas são resolvidos, sempre em pequenas porções. Todo o conteúdo é muito prático. 18 | 19 | Se você quer aprender sobre Cypress, colocar mais um projeto no Portfólio e elevar seus conhecimentos de forma gratuita, #bora. 20 | 21 | ## Vídeo aulas 22 | 23 | O conteúdo dos vídeos é organizado por branches, conforme a lista abaixo: 24 | 25 | > ⚡️ **Acesse os vídeos aqui**: [youtube-cypress](https://www.youtube.com/playlist?list=PLnUo-Rbc3jjyx5BVnG8MB7vNd5ecu2yP1 "youtube-cypress") 26 | 27 | 28 | - [Vídeo 1 - Cypress - passo a passo para criar o primeiro projeto e testes](https://github.com/samlucax/youtube-cypress/tree/video1 "Vídeo 1") 29 | - [Vídeo 2 - Cypress - adicionando mais testes, background login e data-cy](https://github.com/samlucax/youtube-cypress/tree/video2 "Vídeo 2") 30 | - [Vídeo 3 - Cypress - utilizando o Page Objects](https://github.com/samlucax/youtube-cypress/tree/video3 "Vídeo 3") 31 | - [Vídeo 4 - Cypress - adicionando relatório de testes com o Allure Reports Plugin](https://github.com/samlucax/youtube-cypress/tree/video4 "Vídeo 4") 32 | 33 | 34 | ## Resumo dos conteúdos abordados: 35 | 36 | - como criar um projeto node 37 | - como adicionar o Cypress ao projeto 38 | - como criar a estrutura de pastas padrão do Cypress 39 | - como executar os testes utilizando o Cypress Runner 40 | - como acessar uma página 41 | - como mapear elementos utilizando Cypress 42 | - como utilizar comandos comuns de digitar, clicar, etc. 43 | - como realizar asserções nos nossos testes 44 | - como criar comandos customizados para auxiliar no desenvolvimento 45 | - como fazer requisições para a api 46 | - como executar testes de forma independente 47 | - como configurar a aplicação testada para executar localmente 48 | - como criar um script para subir o Cypress de forma mais organizada 49 | - como manipular recursos do browser, como o localStorage 50 | - reforçar o padrão de testes independentes 51 | - como criar seletores dedicados para testes 52 | - qual problema o page objects se propõe a resolver 53 | - como criar uma estrutura simples para usar este padrão 54 | - como separar testes, ações e elementos da página 55 | 56 | ------------ 57 | 58 | # Passo a passo para configurar o projeto 59 | 60 | #### Baixe o projeto no Github, de preferência na branch do Vídeo 1: 61 | - Branch do vídeo 1: `https://github.com/samlucax/youtube-cypress/tree/video1` 62 | 63 | #### Instale e suba nossa *cobaia* localmente 64 | 1.Acessar o diretório backend, instalar as dependências e iniciar a api: 65 | - `cd backend` 66 | - depois `npm install` 67 | - depois `npm start` 68 | 69 | 2.Acessar o diretório frontend, instalar as dependências e iniciar o site: 70 | - `cd frontend` 71 | - depois `npm install` 72 | - depois `npm start` 73 | 74 | #### Instale e abra o Cypress 75 | 3.Acessar o diretório root do projeto (que tem a pasta Cypress), instalar as dependências e abrir o Cypress Runner: 76 | - `npm install` 77 | - `./node_modules/.bin/cypress open` 78 | 79 | ## Deixe uma ⭐️ e espalhe esse conhecimento para o mundo 80 | 81 | Esse treinamento foi feito com muito empenho, carinho e principalmente, pensando em ajudar quem deseja iniciar em automação de testes. 82 | Se este conteúdo ajudou você ou você acredita que pode ajudar alguém #compartilhe esse projeto 🖤 83 | 84 | Ah, e deixe uma ⭐️ no repositório para nos apoiar ⚡️#boraagilizar 85 | 86 | ------------ 87 | 88 | Ps.: 89 | Se quiser, apague o projeto do Cypress e tente fazer do zero acompanhando o vídeo. Bons estudos! 90 | -------------------------------------------------------------------------------- /backend/.gitignore: -------------------------------------------------------------------------------- 1 | node_modules/**/* 2 | npm-debug.* 3 | 4 | # macOS 5 | .DS_Store 6 | -------------------------------------------------------------------------------- /backend/knexfile.js: -------------------------------------------------------------------------------- 1 | // Update with your config settings. 2 | 3 | module.exports = { 4 | 5 | development: { 6 | client: 'sqlite3', 7 | connection: { 8 | filename: './src/database/db.sqlite' 9 | }, 10 | migrations: { 11 | directory: './src/database/migrations' 12 | }, 13 | useNullAsDefault: true, 14 | }, 15 | 16 | staging: { 17 | client: 'postgresql', 18 | connection: { 19 | database: 'my_db', 20 | user: 'username', 21 | password: 'password' 22 | }, 23 | pool: { 24 | min: 2, 25 | max: 10 26 | }, 27 | migrations: { 28 | tableName: 'knex_migrations' 29 | } 30 | }, 31 | 32 | production: { 33 | client: 'postgresql', 34 | connection: { 35 | database: 'my_db', 36 | user: 'username', 37 | password: 'password' 38 | }, 39 | pool: { 40 | min: 2, 41 | max: 10 42 | }, 43 | migrations: { 44 | tableName: 'knex_migrations' 45 | } 46 | } 47 | 48 | }; 49 | -------------------------------------------------------------------------------- /backend/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "backend", 3 | "version": "1.0.0", 4 | "description": "", 5 | "main": "index.js", 6 | "scripts": { 7 | "start": "nodemon src/index.js" 8 | }, 9 | "keywords": [], 10 | "author": "", 11 | "license": "ISC", 12 | "dependencies": { 13 | "cors": "^2.8.5", 14 | "express": "^4.17.1", 15 | "knex": "^0.20.13", 16 | "sqlite3": "^4.1.1" 17 | }, 18 | "devDependencies": { 19 | "nodemon": "^2.0.2" 20 | } 21 | } 22 | -------------------------------------------------------------------------------- /backend/src/controllers/IncidentController.js: -------------------------------------------------------------------------------- 1 | const connection = require('../database/connection'); 2 | 3 | module.exports = { 4 | async index(request, response) { 5 | const { page = 1 } = request.query; 6 | 7 | const [count] = await connection('incidents').count(); 8 | 9 | console.log(count); 10 | 11 | const incidents = await connection('incidents') 12 | .join('ongs', 'ongs.id', '=', 'incidents.ong_id') 13 | .limit(5) 14 | .offset((page - 1) * 5) 15 | .select([ 16 | 'incidents.*', 17 | 'ongs.name', 18 | 'ongs.email', 19 | 'ongs.whatsapp', 20 | 'ongs.city', 21 | 'ongs.uf']); 22 | 23 | response.header('X-Total-Count', count['count(*)']); 24 | 25 | return response.json(incidents); 26 | }, 27 | 28 | async create(request, response) { 29 | const { title, description, value } = request.body; 30 | 31 | const ong_id = request.headers.authorization; 32 | 33 | const [id] = await connection('incidents').insert({ 34 | title, 35 | description, 36 | value, 37 | ong_id, 38 | }); 39 | 40 | return response.json({ id }); 41 | }, 42 | 43 | async delete(request, response) { 44 | const { id } = request.params; 45 | const ong_id = request.headers.authorization; 46 | 47 | const incident = await connection('incidents') 48 | .where('id', id) 49 | .select('ong_id') 50 | .first(); 51 | 52 | if(incident.ong_id != ong_id) { 53 | return response.status(401).json({ error: 'Operation not permited' }); 54 | } 55 | 56 | await connection('incidents').where('id', id).delete(); 57 | 58 | return response.status(204).send(); 59 | } 60 | 61 | }; -------------------------------------------------------------------------------- /backend/src/controllers/OngController.js: -------------------------------------------------------------------------------- 1 | const crypto = require('crypto'); 2 | const connection = require('../database/connection'); 3 | 4 | module.exports = { 5 | async index(request, response) { 6 | const ongs = await connection('ongs').select('*'); 7 | 8 | return response.json(ongs); 9 | }, 10 | 11 | async create(request, response) { 12 | const { name, email, whatsapp, city, uf } = request.body; 13 | 14 | const id = crypto.randomBytes(4).toString('HEX'); 15 | 16 | //conexao com o banco de dados 17 | await connection('ongs').insert({ 18 | id, 19 | name, 20 | email, 21 | whatsapp, 22 | city, 23 | uf, 24 | }) 25 | 26 | return response.json({ id }); 27 | } 28 | }; -------------------------------------------------------------------------------- /backend/src/controllers/ProfileController.js: -------------------------------------------------------------------------------- 1 | const connection = require('../database/connection'); 2 | 3 | module.exports = { 4 | async index(request, response) { 5 | const ong_id = request.headers.authorization; 6 | 7 | const incidents = await connection('incidents') 8 | .where('ong_id', ong_id) 9 | .select('*'); 10 | 11 | return response.json(incidents); 12 | } 13 | } -------------------------------------------------------------------------------- /backend/src/controllers/SessionController.js: -------------------------------------------------------------------------------- 1 | const connection = require('../database/connection'); 2 | 3 | module.exports = { 4 | async create(request, response) { 5 | const { id } = request.body; 6 | 7 | const ong = await connection('ongs') 8 | .where('id', id) 9 | .select('name') 10 | .first(); 11 | 12 | if(!ong) { 13 | return response.status(400).json({ error: 'No ONG found with this ID' }); 14 | } 15 | 16 | return response.json(ong); 17 | } 18 | } -------------------------------------------------------------------------------- /backend/src/database/connection.js: -------------------------------------------------------------------------------- 1 | const knex = require('knex'); 2 | const configuration = require('../../knexfile'); 3 | 4 | const connection = knex(configuration.development); 5 | 6 | module.exports = connection; -------------------------------------------------------------------------------- /backend/src/database/db.sqlite: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/samlucax/youtube-cypress/12ae5601592c4c60797b328387c6fe161f298059/backend/src/database/db.sqlite -------------------------------------------------------------------------------- /backend/src/database/migrations/20200327202114_create_ongs.js: -------------------------------------------------------------------------------- 1 | 2 | exports.up = function(knex) { 3 | return knex.schema.createTable('ongs', function (table) { 4 | table.string('id').primary(); 5 | table.string('name').notNullable(); 6 | table.string('email').notNullable(); 7 | table.string('whatsapp').notNullable(); 8 | table.string('city').notNullable(); 9 | table.string('uf', 2).notNullable(); 10 | }) 11 | }; 12 | 13 | exports.down = function(knex) { 14 | return knex.schema.dropTable('ongs'); 15 | }; 16 | -------------------------------------------------------------------------------- /backend/src/database/migrations/20200327202618_create_incidents.js: -------------------------------------------------------------------------------- 1 | 2 | exports.up = function(knex) { 3 | return knex.schema.createTable('incidents', function(table) { 4 | // incrementa 5 | table.increments(); 6 | 7 | //campos do banco 8 | table.string('title').notNullable(); 9 | table.string('description').notNullable(); 10 | table.decimal('value').notNullable(); 11 | 12 | //foreign key 13 | table.string('ong_id').notNullable(); 14 | 15 | //relacionamento 16 | table.foreign('ong_id').references('id').inTable('ongs'); 17 | }); 18 | }; 19 | 20 | exports.down = function(knex) { 21 | return knex.schema.dropTable('incidents'); 22 | }; 23 | -------------------------------------------------------------------------------- /backend/src/index.js: -------------------------------------------------------------------------------- 1 | const express = require('express'); 2 | const cors = require('cors'); 3 | const routes = require('./routes'); 4 | const app = express(); 5 | 6 | // cors é o modulo de seguranca 7 | app.use(cors()); 8 | 9 | //importante ser antes de todas as requisicoes, por isso esta no topo 10 | app.use(express.json()); 11 | //importante ser depois da linha acima 12 | app.use(routes); 13 | 14 | app.listen(3333); 15 | 16 | 17 | /** 18 | * BTW: estou no min 40 - Migrations knex 19 | */ 20 | -------------------------------------------------------------------------------- /backend/src/routes.js: -------------------------------------------------------------------------------- 1 | const express = require('express'); 2 | const routes = express.Router(); 3 | 4 | const OngController = require('./controllers/OngController'); 5 | const IncidentController = require('./controllers/IncidentController'); 6 | const ProfileController = require('./controllers/ProfileController'); 7 | const SessionController = require('./controllers/SessionController'); 8 | 9 | 10 | /** 11 | * Rota é o conjunto completo que será acessado 12 | * Recurso é a parte contextual, normalmente relacionado a banco de dados, objeto, etc 13 | */ 14 | 15 | /** 16 | * Métodos HTTP (principais): 17 | * 18 | * GET: Buscar/Listar uma informação no backend 19 | * POST: Criar uma informação no backend 20 | * PUT: Alterar uma informação no backend 21 | * DELETE: Deletar uma informação no backend 22 | */ 23 | 24 | /** 25 | * Tipos de parâmetros: 26 | * 27 | * Query Params: Parâmetros nomeados enviados na rota após "?" (Filtros, paginação) 28 | * Route Params: Parâmetros utilizados para identificar recursos 29 | * Request Body: Corpo da requisição, utilizado para criar ou alterar recursos 30 | */ 31 | 32 | /** 33 | * SQL: MySQL, SQLite, PostgreSQL, Oracle, Microsoft SQL Server 34 | * NoSQL: MongoDB, CouchDB, etc. 35 | */ 36 | 37 | /** 38 | * Driver: SELECT * FROM users 39 | * Query Builder: table('users').select('*').where() 40 | */ 41 | 42 | routes.post('/sessions', SessionController.create); 43 | 44 | routes.get('/ongs', OngController.index); 45 | routes.post('/ongs', OngController.create); 46 | 47 | routes.get('/profile', ProfileController.index); 48 | 49 | routes.get('/incidents', IncidentController.index); 50 | routes.post('/incidents',IncidentController.create); 51 | routes.delete('/incidents/:id', IncidentController.delete); 52 | 53 | 54 | module.exports = routes; -------------------------------------------------------------------------------- /cypress.json: -------------------------------------------------------------------------------- 1 | {} 2 | -------------------------------------------------------------------------------- /cypress/integration/ongs.spec.js: -------------------------------------------------------------------------------- 1 | /// 2 | 3 | describe('Ongs', () => { 4 | it('devem poder realizar um cadastro', () => { 5 | cy.visit('http://localhost:3000/register'); 6 | // cy.get - busca um elemento 7 | // .type - insere um texto 8 | cy.get('[data-cy=name]').type('Dogs queridos'); 9 | cy.get('[data-cy=email]').type('dogs@mail.com'); 10 | cy.get('[data-cy=whatsapp]').type('51999999999'); 11 | cy.get('[data-cy=city]').type('Porto Alegre'); 12 | cy.get('[data-cy=uf]').type('RS'); 13 | 14 | // routing 15 | // start server com cy.server() 16 | // criar uma rota com cy.route() 17 | // atribuir rota a um alias 18 | // esperar com cy.wait e fazer uma validação 19 | 20 | cy.route('POST', '**/ongs').as('postOng'); 21 | 22 | cy.get('[data-cy=submit]').click(); 23 | 24 | cy.wait('@postOng').then((xhr) => { 25 | expect(xhr.status).be.eq(200); 26 | expect(xhr.response.body).has.property('id'); 27 | expect(xhr.response.body.id).is.not.null; 28 | }); 29 | 30 | }); 31 | 32 | it('deve poder realizar um login no sistema', () => { 33 | cy.visit('http://localhost:3000/'); 34 | cy.get('input').type(Cypress.env('createdOngId')); 35 | cy.get('.button').click(); 36 | }); 37 | }); -------------------------------------------------------------------------------- /cypress/plugins/index.js: -------------------------------------------------------------------------------- 1 | /// 2 | // *********************************************************** 3 | // This example plugins/index.js can be used to load plugins 4 | // 5 | // You can change the location of this file or turn off loading 6 | // the plugins file with the 'pluginsFile' configuration option. 7 | // 8 | // You can read more here: 9 | // https://on.cypress.io/plugins-guide 10 | // *********************************************************** 11 | 12 | // This function is called when a project is opened or re-opened (e.g. due to 13 | // the project's config changing) 14 | 15 | /** 16 | * @type {Cypress.PluginConfig} 17 | */ 18 | module.exports = (on, config) => { 19 | // `on` is used to hook into various events Cypress emits 20 | // `config` is the resolved Cypress config 21 | } 22 | -------------------------------------------------------------------------------- /cypress/support/commands.js: -------------------------------------------------------------------------------- 1 | // *********************************************** 2 | // This example commands.js shows you how to 3 | // create various custom commands and overwrite 4 | // existing commands. 5 | // 6 | // For more comprehensive examples of custom 7 | // commands please read more here: 8 | // https://on.cypress.io/custom-commands 9 | // *********************************************** 10 | // 11 | // 12 | // -- This is a parent command -- 13 | // Cypress.Commands.add("login", (email, password) => { ... }) 14 | // 15 | // 16 | // -- This is a child command -- 17 | // Cypress.Commands.add("drag", { prevSubject: 'element'}, (subject, options) => { ... }) 18 | // 19 | // 20 | // -- This is a dual command -- 21 | // Cypress.Commands.add("dismiss", { prevSubject: 'optional'}, (subject, options) => { ... }) 22 | // 23 | // 24 | // -- This will overwrite an existing command -- 25 | // Cypress.Commands.overwrite("visit", (originalFn, url, options) => { ... }) 26 | 27 | Cypress.Commands.add("createOng", () => { 28 | cy.request({ 29 | method: 'POST', 30 | url: 'http://localhost:3333/ongs', 31 | body: { 32 | name: "Gatos queridos", 33 | email: "gatos@mail.com", 34 | whatsapp: "519999999999", 35 | city: "Porto Alegre", 36 | uf: "RS" 37 | } 38 | }).then(response => { 39 | expect(response.body.id).is.not.null; 40 | cy.log(response.body.id); 41 | 42 | Cypress.env('createdOngId', response.body.id); 43 | }); 44 | }) -------------------------------------------------------------------------------- /cypress/support/index.js: -------------------------------------------------------------------------------- 1 | // *********************************************************** 2 | // This example support/index.js is processed and 3 | // loaded automatically before your test files. 4 | // 5 | // This is a great place to put global configuration and 6 | // behavior that modifies Cypress. 7 | // 8 | // You can change the location of this file or turn off 9 | // automatically serving support files with the 10 | // 'supportFile' configuration option. 11 | // 12 | // You can read more here: 13 | // https://on.cypress.io/configuration 14 | // *********************************************************** 15 | 16 | // Import commands.js using ES2015 syntax: 17 | import './commands' 18 | 19 | // Alternatively you can use CommonJS syntax: 20 | // require('./commands') 21 | 22 | before(() => { 23 | // cy.server - para route e request 24 | cy.server(); 25 | cy.createOng(); 26 | }); -------------------------------------------------------------------------------- /frontend/.gitignore: -------------------------------------------------------------------------------- 1 | # See https://help.github.com/articles/ignoring-files/ for more about ignoring files. 2 | 3 | # dependencies 4 | /node_modules 5 | /.pnp 6 | .pnp.js 7 | 8 | # testing 9 | /coverage 10 | 11 | # production 12 | /build 13 | 14 | # misc 15 | .DS_Store 16 | .env.local 17 | .env.development.local 18 | .env.test.local 19 | .env.production.local 20 | 21 | npm-debug.log* 22 | yarn-debug.log* 23 | yarn-error.log* 24 | -------------------------------------------------------------------------------- /frontend/cypress.json: -------------------------------------------------------------------------------- 1 | {} 2 | -------------------------------------------------------------------------------- /frontend/cypress/fixtures/data.json: -------------------------------------------------------------------------------- 1 | { 2 | "id": "b30e8e1c" 3 | } -------------------------------------------------------------------------------- /frontend/cypress/fixtures/example.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "Using fixtures to represent data", 3 | "email": "hello@cypress.io", 4 | "body": "Fixtures are a great way to mock data for responses to routes" 5 | } -------------------------------------------------------------------------------- /frontend/cypress/fixtures/ongId.json: -------------------------------------------------------------------------------- 1 | { 2 | "id": "62c41360" 3 | } -------------------------------------------------------------------------------- /frontend/cypress/fixtures/ongRegistered.json: -------------------------------------------------------------------------------- 1 | { 2 | "id": "c3f0b6ef" 3 | } -------------------------------------------------------------------------------- /frontend/cypress/integration/ongs.spec.js: -------------------------------------------------------------------------------- 1 | /// 2 | 3 | import * as Start from '../support/tasks/Start' 4 | import * as DoRegister from '../support/tasks/DoRegister' 5 | import * as DoLogIn from '../support/tasks/DoLogIn' 6 | import * as TheOng from '../support/questions/TheOng' 7 | 8 | describe('Ongs', () => { 9 | it('Devem poder realizar um cadastro', () => { 10 | Start.atRegisterPage(); 11 | DoRegister.asOngNamed("Dogs Queridos"); 12 | TheOng.receivedAnAccessIdWhenDone(); 13 | }); 14 | 15 | it('Deve poder realizar login no sistema', () => { 16 | Start.withARegisteredOng(); 17 | Start.atLogonPage(); 18 | DoLogIn.withTheRegisteredOng(); 19 | }); 20 | 21 | it.only('Devem poder visualizar casos cadastrados', () => { 22 | Start.atProfilePageLogged(); 23 | }); 24 | 25 | it('Devem poder cadastrar novos casos', () => { 26 | 27 | }); 28 | }); -------------------------------------------------------------------------------- /frontend/cypress/plugins/index.js: -------------------------------------------------------------------------------- 1 | /// 2 | // *********************************************************** 3 | // This example plugins/index.js can be used to load plugins 4 | // 5 | // You can change the location of this file or turn off loading 6 | // the plugins file with the 'pluginsFile' configuration option. 7 | // 8 | // You can read more here: 9 | // https://on.cypress.io/plugins-guide 10 | // *********************************************************** 11 | 12 | // This function is called when a project is opened or re-opened (e.g. due to 13 | // the project's config changing) 14 | 15 | /** 16 | * @type {Cypress.PluginConfig} 17 | */ 18 | module.exports = (on, config) => { 19 | // `on` is used to hook into various events Cypress emits 20 | // `config` is the resolved Cypress config 21 | } 22 | -------------------------------------------------------------------------------- /frontend/cypress/screenplay.md: -------------------------------------------------------------------------------- 1 | ScreenPlay - Análise pré-implementação 2 | 3 | Atores 4 | - Ong 5 | 6 | Habilidades / Ações 7 | - preencher o formulário de autenticação 8 | - clicar no botão de entrar 9 | - clicar no botão de realizar novo cadastro 10 | - preencher o formulário de registro 11 | - clicar no botão de registrar-se 12 | - clicar no botão de cadastrar novo caso 13 | - preencher o formulário de cadastro de casos 14 | - clicar no botão para apagar um caso 15 | - clicar no botão de logout 16 | 17 | Tarefas 18 | - Autenticar com sua id 19 | - Registrar-se 20 | - Cadastrar novo caso 21 | - Apagar um caso 22 | - Visualizar os casos na tela 23 | 24 | Questões 25 | - recebi um id para autenticação? 26 | - estou na homepage? 27 | - o caso cadastrado foi listado? 28 | - o caso apagado aparece na listagem? 29 | 30 | 31 | --- 32 | 33 | 34 | https://docs.cypress.io/api/commands/visit.html#Options 35 | 36 | cy.visit('http://localhost:3000/#dashboard', { 37 | onBeforeLoad: (contentWindow) => { 38 | // contentWindow is the remote page's window object 39 | } 40 | }) 41 | 42 | usar fixture como request body 43 | https://github.com/cypress-io/cypress/issues/3387 44 | 45 | cypress para component unit testing - react 46 | https://github.com/bahmutov/cypress-react-unit-test -------------------------------------------------------------------------------- /frontend/cypress/support/commands.js: -------------------------------------------------------------------------------- 1 | // *********************************************** 2 | // This example commands.js shows you how to 3 | // create various custom commands and overwrite 4 | // existing commands. 5 | // 6 | // For more comprehensive examples of custom 7 | // commands please read more here: 8 | // https://on.cypress.io/custom-commands 9 | // *********************************************** 10 | // 11 | // 12 | // -- This is a parent command -- 13 | // Cypress.Commands.add("login", (email, password) => { ... }) 14 | // 15 | // 16 | // -- This is a child command -- 17 | // Cypress.Commands.add("drag", { prevSubject: 'element'}, (subject, options) => { ... }) 18 | // 19 | // 20 | // -- This is a dual command -- 21 | // Cypress.Commands.add("dismiss", { prevSubject: 'optional'}, (subject, options) => { ... }) 22 | // 23 | // 24 | // -- This will overwrite an existing command -- 25 | // Cypress.Commands.overwrite("visit", (originalFn, url, options) => { ... }) 26 | 27 | const BACKEND_HOST = "http://localhost:3333"; 28 | 29 | // comando para criar uma ong antes dos testes 30 | Cypress.Commands.add("createOng", () => { 31 | // let ongId = 'piça'; 32 | 33 | cy.request({ 34 | method: 'POST', 35 | url: `${BACKEND_HOST}/ongs`, 36 | headers: { Accept: 'application/json' }, 37 | body: { 38 | name: 'Dogs Queridos', 39 | email: 'dogs@mail.com', 40 | whatsapp: '51999999999', 41 | city: 'Porto Alegre', 42 | uf: 'RS' 43 | } 44 | }).then(response => { 45 | // return "c2a5929d"; 46 | Cypress.env('createdOngId', response.body.id); 47 | // ongId = response.body.id; 48 | }); 49 | 50 | // return ongId; 51 | }) 52 | -------------------------------------------------------------------------------- /frontend/cypress/support/index.js: -------------------------------------------------------------------------------- 1 | // *********************************************************** 2 | // This example support/index.js is processed and 3 | // loaded automatically before your test files. 4 | // 5 | // This is a great place to put global configuration and 6 | // behavior that modifies Cypress. 7 | // 8 | // You can change the location of this file or turn off 9 | // automatically serving support files with the 10 | // 'supportFile' configuration option. 11 | // 12 | // You can read more here: 13 | // https://on.cypress.io/configuration 14 | // *********************************************************** 15 | 16 | // Import commands.js using ES2015 syntax: 17 | import './commands' 18 | 19 | // Alternatively you can use CommonJS syntax: 20 | // require('./commands') 21 | 22 | 23 | // Init do cy.server utilizado para routing 24 | before(() => { 25 | cy.server(); 26 | cy.createOng(); 27 | }); -------------------------------------------------------------------------------- /frontend/cypress/support/pages/Logon.page.js: -------------------------------------------------------------------------------- 1 | module.exports = { 2 | INPUT_USER: 'input', 3 | BUTTON_LOGIN: '.button' 4 | } -------------------------------------------------------------------------------- /frontend/cypress/support/pages/NewIncident.page.js: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/samlucax/youtube-cypress/12ae5601592c4c60797b328387c6fe161f298059/frontend/cypress/support/pages/NewIncident.page.js -------------------------------------------------------------------------------- /frontend/cypress/support/pages/Profile.page.js: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/samlucax/youtube-cypress/12ae5601592c4c60797b328387c6fe161f298059/frontend/cypress/support/pages/Profile.page.js -------------------------------------------------------------------------------- /frontend/cypress/support/pages/Register.page.js: -------------------------------------------------------------------------------- 1 | module.exports = { 2 | INPUT_NAME: "[data-cy=name]", 3 | INPUT_EMAIL: "[data-cy=email]", 4 | INPUT_WHATSAPP: "[data-cy=whatsapp]", 5 | INPUT_CITY: "[data-cy=city]", 6 | INPUT_UF: "[data-cy=uf]", 7 | BUTTON_SUBMIT: "[data-cy=submit]", 8 | } -------------------------------------------------------------------------------- /frontend/cypress/support/questions/TheOng.js: -------------------------------------------------------------------------------- 1 | /// 2 | 3 | export function receivedAnAccessIdWhenDone() { 4 | cy.wait('@postOng').then((xhr) => { 5 | expect(xhr.status).be.eq(200); 6 | expect(xhr.response.body).has.property('id'); 7 | expect(xhr.response.body.id).is.not.null; 8 | }); 9 | // cy.readFile('cypress/fixtures/data.json').as('data'); 10 | // cy.get('@data').then((data) => { 11 | // cy.log(`Data: ${data.id}`); 12 | // }) 13 | } -------------------------------------------------------------------------------- /frontend/cypress/support/tasks/DoLogIn.js: -------------------------------------------------------------------------------- 1 | import * as logonEl from '../pages/Logon.page' 2 | 3 | export function withTheRegisteredOng() { 4 | cy.readFile('cypress/fixtures/ongId.json').as('data'); 5 | cy.get('@data').then((data) => { 6 | cy.get(logonEl.INPUT_USER).type(data.id); 7 | }) 8 | 9 | cy.get(logonEl.BUTTON_LOGIN).click(); 10 | } -------------------------------------------------------------------------------- /frontend/cypress/support/tasks/DoRegister.js: -------------------------------------------------------------------------------- 1 | /// 2 | 3 | import * as registerPage from '../pages/Register.page' 4 | 5 | export function asOngNamed(name){ 6 | cy.get(registerPage.INPUT_NAME).type(name) 7 | cy.get(registerPage.INPUT_EMAIL).type("dogs@mail.com") 8 | cy.get(registerPage.INPUT_WHATSAPP).type("5551999999999") 9 | cy.get(registerPage.INPUT_CITY).type("Porto Alegre") 10 | cy.get(registerPage.INPUT_UF).type("RS") 11 | 12 | cy.route('POST', '**/ongs').as('postOng'); 13 | 14 | cy.get(registerPage.BUTTON_SUBMIT).click(); 15 | 16 | } -------------------------------------------------------------------------------- /frontend/cypress/support/tasks/Start.js: -------------------------------------------------------------------------------- 1 | /// 2 | 3 | const HOST = "http://localhost:3000"; 4 | const BACKEND_HOST = "http://localhost:3333"; 5 | // const fs = require('fs') 6 | 7 | import * as utils from '../utils'; 8 | 9 | export function atRegisterPage(){ 10 | cy.visit(`${HOST}/register`); 11 | } 12 | 13 | export function atLogonPage(){ 14 | cy.visit(`${HOST}/`); 15 | } 16 | 17 | export function atProfilePageLogged() { 18 | cy.clearLocalStorage(); 19 | // const ongId = utils.registerOngAndSaveDataAsFixture('ongRegistered'); 20 | const createdOngId = Cypress.env('createdOngId'); 21 | cy.log(createdOngId); 22 | // const ongInfo = require('../../fixtures/ongRegistered') 23 | 24 | cy.pause(); 25 | 26 | cy.visit(`${HOST}/profile`, { 27 | onBeforeLoad: (win) => { 28 | win.localStorage.setItem('ongId', createdOngId); 29 | win.localStorage.setItem('ongName', createdOngId); 30 | } 31 | }); 32 | } 33 | 34 | export function withARegisteredOng(){ 35 | 36 | cy.request({ 37 | method: 'POST', 38 | url: `${BACKEND_HOST}/ongs`, 39 | headers: { Accept: 'application/json' }, 40 | body: { 41 | name: 'Dogs Queridos', 42 | email: 'dogs@mail.com', 43 | whatsapp: '51999999999', 44 | city: 'Porto Alegre', 45 | uf: 'RS' 46 | } 47 | }).then((res) => { 48 | cy.log(res.body.id); 49 | cy.writeFile('cypress/fixtures/ongId.json', 50 | { 51 | id: res.body.id, 52 | name: res.body.name 53 | }); 54 | }); 55 | } -------------------------------------------------------------------------------- /frontend/cypress/support/utils.js: -------------------------------------------------------------------------------- 1 | // const pay = require('../fixtures/requests/post-ong.json'); 2 | 3 | // export function payload(fixture) { 4 | // let payload = getPayloadFixture(fixture); 5 | // return JSON.stringify(payload); 6 | // } 7 | 8 | // function getPayloadFixture(fixture){ 9 | // switch (fixture) { 10 | // case 'post-ong': 11 | // return pay; 12 | // } 13 | // } 14 | const BACKEND_HOST = "http://localhost:3333"; 15 | 16 | export function registerOngAndSaveDataAsFixture(fixture){ 17 | let ongId; 18 | 19 | cy.request({ 20 | method: 'POST', 21 | url: `${BACKEND_HOST}/ongs`, 22 | headers: { Accept: 'application/json' }, 23 | body: { 24 | name: 'Dogs Queridos', 25 | email: 'dogs@mail.com', 26 | whatsapp: '51999999999', 27 | city: 'Porto Alegre', 28 | uf: 'RS' 29 | } 30 | }).then((res) => { 31 | cy.log(res.body.id); 32 | // cy.writeFile(`cypress/fixtures/${fixture}.json`, 33 | // { 34 | // id: res.body.id 35 | // }); 36 | ongId = res.body.id 37 | }); 38 | 39 | // cy.log('passou aqui' + ongId); 40 | return ongId; 41 | } -------------------------------------------------------------------------------- /frontend/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "frontend", 3 | "version": "0.1.0", 4 | "private": true, 5 | "dependencies": { 6 | "@testing-library/jest-dom": "^4.2.4", 7 | "@testing-library/react": "^9.3.2", 8 | "@testing-library/user-event": "^7.1.2", 9 | "axios": "^0.19.2", 10 | "react": "^16.13.1", 11 | "react-dom": "^16.13.1", 12 | "react-icons": "^3.9.0", 13 | "react-router-dom": "^5.1.2", 14 | "react-scripts": "3.4.1" 15 | }, 16 | "scripts": { 17 | "start": "react-scripts start", 18 | "build": "react-scripts build", 19 | "test": "react-scripts test", 20 | "eject": "react-scripts eject" 21 | }, 22 | "eslintConfig": { 23 | "extends": "react-app" 24 | }, 25 | "browserslist": { 26 | "production": [ 27 | ">0.2%", 28 | "not dead", 29 | "not op_mini all" 30 | ], 31 | "development": [ 32 | "last 1 chrome version", 33 | "last 1 firefox version", 34 | "last 1 safari version" 35 | ] 36 | }, 37 | "devDependencies": { 38 | "cypress": "4.1" 39 | } 40 | } 41 | -------------------------------------------------------------------------------- /frontend/public/favicon.ico: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/samlucax/youtube-cypress/12ae5601592c4c60797b328387c6fe161f298059/frontend/public/favicon.ico -------------------------------------------------------------------------------- /frontend/public/index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | Be The Hero 10 | 11 | 12 | 13 |
14 | 15 | 16 | -------------------------------------------------------------------------------- /frontend/src/App.js: -------------------------------------------------------------------------------- 1 | import React from 'react'; 2 | 3 | import './global.css' 4 | // JSX - javascript and xml 5 | // import Logon from './pages/Logon'; 6 | import Routes from './routes'; 7 | 8 | function App() { 9 | return ( 10 | 11 | ); 12 | } 13 | 14 | export default App; 15 | -------------------------------------------------------------------------------- /frontend/src/Header.js: -------------------------------------------------------------------------------- 1 | import React from 'react'; 2 | 3 | function Header({ children }) { 4 | return ( 5 |
6 |

{ children }

7 |
8 | ); 9 | } 10 | 11 | export default Header; -------------------------------------------------------------------------------- /frontend/src/assets/heroes.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/samlucax/youtube-cypress/12ae5601592c4c60797b328387c6fe161f298059/frontend/src/assets/heroes.png -------------------------------------------------------------------------------- /frontend/src/assets/logo.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | -------------------------------------------------------------------------------- /frontend/src/global.css: -------------------------------------------------------------------------------- 1 | @import url('https://fonts.googleapis.com/css?family=Roboto:400,500,700&display=swap'); 2 | 3 | * { 4 | margin: 0; 5 | padding: 0; 6 | outline: 0; 7 | box-sizing: border-box; 8 | } 9 | 10 | body { 11 | font: 400 14px Roboto, sans-serif; 12 | background: #f0f0f5; 13 | -webkit-font-smoothing: antialiased; 14 | } 15 | 16 | input, button, textarea { 17 | font: 400 18px Roboto, sans-serif; 18 | } 19 | 20 | button { 21 | cursor: pointer; 22 | } 23 | 24 | form input { 25 | width: 100%; 26 | height: 60px; 27 | color: #333; 28 | border: 1px solid #dcdce6; 29 | border-radius: 8px; 30 | padding: 0 24px; 31 | } 32 | 33 | form textarea { 34 | width: 100%; 35 | resize: vertical; 36 | min-height: 140px; 37 | color: #333; 38 | border: 1px solid #dcdce6; 39 | border-radius: 8px; 40 | padding: 16px 24px; 41 | line-height: 24px; 42 | } 43 | 44 | .button { 45 | width: 100%; 46 | height: 60px; 47 | background: #e02041; 48 | border: 0; 49 | border-radius: 8px; 50 | color: #FFF; 51 | font-weight: 700; 52 | margin-top: 16px; 53 | display: inline-block; 54 | text-align: center; 55 | text-decoration: none; 56 | font-size: 18px; 57 | line-height: 60px; 58 | transition: filter 0.2s; 59 | } 60 | 61 | .button:hover { 62 | filter: brightness(90%); 63 | } 64 | 65 | .back-link { 66 | display: flex; 67 | align-items: center; 68 | margin-top: 40px; 69 | color: #41414d; 70 | font-size: 18px; 71 | text-decoration: none; 72 | font-weight: 500; 73 | transition: opacity 0.2s; 74 | } 75 | 76 | .back-link svg { 77 | margin-right: 8px; 78 | } 79 | 80 | .back-link:hover { 81 | opacity: 0.8; 82 | } 83 | -------------------------------------------------------------------------------- /frontend/src/index.js: -------------------------------------------------------------------------------- 1 | import React from 'react'; 2 | import ReactDOM from 'react-dom'; 3 | import App from './App'; 4 | 5 | ReactDOM.render(, document.getElementById('root')); 6 | -------------------------------------------------------------------------------- /frontend/src/pages/Logon/index.js: -------------------------------------------------------------------------------- 1 | import React, { useState } from 'react'; 2 | import { Link, useHistory } from 'react-router-dom'; 3 | import { FiLogIn } from 'react-icons/fi'; 4 | 5 | import api from '../../services/api'; 6 | 7 | import './styles.css' 8 | 9 | import heroesImg from '../../assets/heroes.png'; 10 | import logoImg from '../../assets/logo.svg'; 11 | 12 | export default function Logon() { 13 | const [id, setId] = useState(''); 14 | 15 | const history = useHistory() 16 | 17 | async function handleLogin(e){ 18 | e.preventDefault(); 19 | 20 | try { 21 | const response = await api.post('sessions', { id }); 22 | 23 | localStorage.setItem('ongId', id); 24 | localStorage.setItem('ongName', response.data.name); 25 | 26 | history.push('profile'); 27 | 28 | } catch (err) { 29 | alert('Falha no login, tente novamente. '); 30 | } 31 | 32 | } 33 | 34 | return ( 35 |
36 |
37 | Be The Hero 38 | 39 |
40 |

Faça seu logon

41 | 42 | setId(e.target.value)} 46 | /> 47 | 48 | 49 | 50 | 51 | 52 | Não tenho cadastro 53 | 54 | 55 | 56 | 57 |
58 | 59 | Heroes 60 |
61 | ); 62 | } -------------------------------------------------------------------------------- /frontend/src/pages/Logon/styles.css: -------------------------------------------------------------------------------- 1 | .logon-container { 2 | width: 100%; 3 | max-width: 1120px; 4 | height: 100vh; 5 | margin: 0 auto; 6 | 7 | display: flex; 8 | align-items: center; 9 | justify-content: space-between; 10 | } 11 | 12 | .logon-container section.form { 13 | width: 100%; 14 | max-width: 350px; 15 | margin-right: 30px; 16 | } 17 | 18 | .logon-container section.form form { 19 | margin-top: 100px; 20 | } 21 | 22 | .logon-container section.form form h1 { 23 | font-size: 32px; 24 | margin-bottom: 32px; 25 | } 26 | 27 | -------------------------------------------------------------------------------- /frontend/src/pages/NewIncident/index.js: -------------------------------------------------------------------------------- 1 | import React, { useState } from 'react'; 2 | import { Link, useHistory } from 'react-router-dom'; 3 | import { FiArrowLeft } from 'react-icons/fi' 4 | 5 | import api from '../../services/api'; 6 | 7 | import './styles.css' 8 | 9 | import logoImg from '../../assets/logo.svg'; 10 | 11 | export default function NewIncident() { 12 | const[title, setTitle] = useState(''); 13 | const[description, setDescription] = useState(''); 14 | const[value, setValue] = useState(''); 15 | 16 | const ongId = localStorage.getItem('ongId'); 17 | 18 | const history = useHistory(); 19 | 20 | 21 | 22 | async function handleNewIncident(e){ 23 | e.preventDefault(); 24 | 25 | const data = { 26 | title, 27 | description, 28 | value 29 | }; 30 | 31 | try { 32 | await api.post('incidents', data, { 33 | headers: { 34 | Authorization: ongId 35 | } 36 | }) 37 | 38 | history.push('/profile'); 39 | 40 | } catch (err) { 41 | alert('Erro ao cadastrar caso, tente novamente'); 42 | } 43 | } 44 | 45 | return ( 46 |
47 |
48 |
49 | Be The Hero 50 | 51 |

Cadastrar novo caso

52 |

Descreva o caso detalhadamente para encontrar um herói para resolver isso.

53 | 54 | 55 | 56 | Voltar para home 57 | 58 | 59 |
60 | 61 |
62 | setTitle(e.target.value)} 66 | /> 67 |