diff --git a/.env b/.env new file mode 100644 index 00000000..9a4ab536 --- /dev/null +++ b/.env @@ -0,0 +1 @@ +CYPRESS_RAILS_PORT=5678 diff --git a/.github/workflows/build-and-test.yml b/.github/workflows/build-and-test.yml index d8da085b..5087e77f 100644 --- a/.github/workflows/build-and-test.yml +++ b/.github/workflows/build-and-test.yml @@ -48,3 +48,5 @@ jobs: run: yarn install - name: Run javascript tests run: yarn test + - name: Run Cypress tests + run: bundle exec rake cypress:run diff --git a/Gemfile b/Gemfile index 4617d813..1be44d75 100644 --- a/Gemfile +++ b/Gemfile @@ -40,8 +40,6 @@ gem "devise" gem "omniauth" -gem "twilio-ruby", "~> 4.11.1" - gem "activerecord-import" gem "jsbundling-rails" @@ -54,7 +52,6 @@ gem "stimulus-rails" gem "watir" -gem "selenium-webdriver", "~> 4.4" gem "net-sftp" gem "ed25519" gem "bcrypt_pbkdf" @@ -64,6 +61,8 @@ gem "standard_deviation" group :development, :test do # Call 'byebug' anywhere in the code to stop execution and get a debugger console gem "byebug", platform: :mri + gem "cypress-rails" + gem "dotenv-rails" gem "factory_bot_rails" gem "parallel_tests" gem "rack-mini-profiler" @@ -87,11 +86,10 @@ group :development do gem "seed_dump" gem "solargraph-reek" gem "spring" - gem "web-console" end group "test" do - gem "apparition", github: "twalpole/apparition", ref: "ca86be4d54af835d531dbcd2b86e7b2c77f85f34" + gem "cuprite" gem "capybara" gem "database_cleaner" gem "launchy" diff --git a/Gemfile.lock b/Gemfile.lock index 5fd10a41..1e6af0a6 100644 --- a/Gemfile.lock +++ b/Gemfile.lock @@ -1,12 +1,3 @@ -GIT - remote: https://github.com/twalpole/apparition.git - revision: ca86be4d54af835d531dbcd2b86e7b2c77f85f34 - ref: ca86be4d54af835d531dbcd2b86e7b2c77f85f34 - specs: - apparition (0.6.0) - capybara (~> 3.13, < 4) - websocket-driver (>= 0.6.5) - GEM remote: https://rubygems.org/ specs: @@ -91,7 +82,6 @@ GEM erubi (~> 1.4) parser (>= 2.4) smart_properties - bindex (0.8.1) bootsnap (1.16.0) msgpack (~> 1.2) brakeman (5.4.1) @@ -100,7 +90,7 @@ GEM activesupport (>= 3.0.0) uniform_notifier (~> 1.11) byebug (11.1.3) - capybara (3.39.0) + capybara (3.39.2) addressable matrix mini_mime (>= 0.1.3) @@ -114,6 +104,12 @@ GEM crass (1.0.6) cssbundling-rails (1.1.2) railties (>= 6.0.0) + cuprite (0.14.3) + capybara (~> 3.0) + ferrum (~> 0.13.0) + cypress-rails (0.6.0) + puma (>= 3.8.0) + railties (>= 5.2.0) database_cleaner (2.0.2) database_cleaner-active_record (>= 2, < 3) database_cleaner-active_record (2.1.0) @@ -129,6 +125,10 @@ GEM warden (~> 1.2.3) diff-lcs (1.5.0) docile (1.4.0) + dotenv (2.8.1) + dotenv-rails (2.8.1) + dotenv (= 2.8.1) + railties (>= 3.2) e2mmap (0.1.0) ed25519 (1.3.0) em-websocket (0.5.3) @@ -150,6 +150,11 @@ GEM factory_bot_rails (6.2.0) factory_bot (~> 6.2.0) railties (>= 5.0.0) + ferrum (0.13) + addressable (~> 2.5) + concurrent-ruby (~> 1.1) + webrick (~> 1.7) + websocket-driver (>= 0.6, < 0.8) ffi (1.15.5) formatador (1.1.0) friendly_id (5.1.0) @@ -195,7 +200,6 @@ GEM jsbundling-rails (1.1.1) railties (>= 6.0.0) json (2.6.3) - jwt (1.5.6) kramdown (2.4.0) rexml kramdown-parser-gfm (1.1.0) @@ -415,10 +419,6 @@ GEM actionpack (>= 6.0.0) activejob (>= 6.0.0) railties (>= 6.0.0) - twilio-ruby (4.11.1) - builder (>= 2.1.2) - jwt (~> 1.0) - multi_json (>= 1.3.0) tzinfo (2.0.6) concurrent-ruby (~> 1.0) uglifier (4.2.0) @@ -430,11 +430,7 @@ GEM watir (7.2.2) regexp_parser (>= 1.2, < 3) selenium-webdriver (~> 4.2) - web-console (4.2.0) - actionview (>= 6.0.0) - activemodel (>= 6.0.0) - bindex (>= 0.4.0) - railties (>= 6.0.0) + webrick (1.8.1) websocket (1.2.9) websocket-driver (0.7.5) websocket-extensions (>= 0.1.0) @@ -451,7 +447,6 @@ PLATFORMS DEPENDENCIES activerecord-import - apparition! bcrypt_pbkdf bootsnap brakeman @@ -459,8 +454,11 @@ DEPENDENCIES byebug capybara cssbundling-rails + cuprite + cypress-rails database_cleaner devise + dotenv-rails ed25519 erb_lint erblint-github @@ -492,7 +490,6 @@ DEPENDENCIES rspec-rails (~> 5.1.0) rubocop seed_dump - selenium-webdriver (~> 4.4) simplecov solargraph-reek spring @@ -501,11 +498,9 @@ DEPENDENCIES stimulus-rails timecop turbo-rails - twilio-ruby (~> 4.11.1) tzinfo-data uglifier (>= 1.3.0) watir - web-console RUBY VERSION ruby 3.2.1p31 diff --git a/app/lib/seeders/journey.rb b/app/lib/seeders/journey.rb new file mode 100644 index 00000000..3a3ffd6e --- /dev/null +++ b/app/lib/seeders/journey.rb @@ -0,0 +1,20 @@ +module Seeders + class Journey + def seed + school = School.first + academic_year = AcademicYear.last + SurveyItem.all.each do |survey_item| + 20.times do |i| + SurveyItemResponse.create(response_id: "#{i}#{survey_item.survey_item_id}", school:, academic_year:, + likert_score: 5, survey_item:, grade: 1) + end + end + + School.all.each do |school| + AcademicYear.all.each do |academic_year| + Respondent.create(school:, academic_year:, one: 20) + end + end + end + end +end diff --git a/config/environments/test.rb b/config/environments/test.rb index 54e083ad..668d20cb 100644 --- a/config/environments/test.rb +++ b/config/environments/test.rb @@ -13,7 +13,7 @@ Rails.application.configure do # Settings specified here will take precedence over those in config/application.rb. config.cache_classes = false - config.action_view.cache_template_loading = true + config.action_view.cache_template_loading = false # Do not eager load code on boot. This avoids loading your whole application # just for the purpose of running a single test. If you are using a tool that @@ -23,11 +23,11 @@ Rails.application.configure do # Configure public file server for tests with Cache-Control for performance. config.public_file_server.enabled = true config.public_file_server.headers = { - 'Cache-Control' => "public, max-age=#{1.hour.to_i}" + "Cache-Control" => "public, max-age=#{1.hour.to_i}" } # Show full error reports and disable caching. - config.consider_all_requests_local = true + config.consider_all_requests_local = true config.action_controller.perform_caching = false config.cache_store = :null_store @@ -55,7 +55,7 @@ Rails.application.configure do config.action_controller.include_all_helpers = false - config.active_record.encryption.primary_key = 'test' - config.active_record.encryption.deterministic_key = 'test' - config.active_record.encryption.key_derivation_salt = 'test' + config.active_record.encryption.primary_key = "test" + config.active_record.encryption.deterministic_key = "test" + config.active_record.encryption.key_derivation_salt = "test" end diff --git a/config/initializers/cypress_rails.rb b/config/initializers/cypress_rails.rb new file mode 100644 index 00000000..015ca8e4 --- /dev/null +++ b/config/initializers/cypress_rails.rb @@ -0,0 +1,14 @@ +return unless Rails.env.test? + +Rails.application.load_tasks unless defined?(Rake::Task) +CypressRails.hooks.before_server_start do + Rake::Task["db:seed"].invoke + Seeders::Journey.new.seed +end +CypressRails.hooks.after_transaction_start do +end +CypressRails.hooks.after_state_reset do +end +CypressRails.hooks.before_server_stop do + Rake::Task["db:test:prepare"].invoke +end diff --git a/cypress.config.js b/cypress.config.js new file mode 100644 index 00000000..b872f3d7 --- /dev/null +++ b/cypress.config.js @@ -0,0 +1,17 @@ +const { defineConfig } = require('cypress') + +module.exports = defineConfig({ + // setupNodeEvents can be defined in either + // the e2e or component configuration + e2e: { + setupNodeEvents(on, config) { + on('before:browser:launch', (browser = {}, launchOptions) => { + /* ... */ + }) + }, + supportFile: false, + }, + screenshotsFolder: "tmp/cypress_screenshots", + videosFolder: "tmp/cypress_videos", + trashAssetsBeforeRuns: false +}) diff --git a/cypress/e2e/journey.cy.js b/cypress/e2e/journey.cy.js new file mode 100644 index 00000000..bb7a29f1 --- /dev/null +++ b/cypress/e2e/journey.cy.js @@ -0,0 +1,86 @@ +/// + +// Welcome to Cypress! +// +// This spec file contains a variety of sample tests +// for a todo list app that are designed to demonstrate +// the power of writing tests in Cypress. +// +// To learn more about how Cypress works and +// what makes it such an awesome testing tool, +// please read our getting started guide: +// https://on.cypress.io/introduction-to-cypress + +describe('navigates the site', () => { + it('displays the div containing the framework wheel', () => { + cy.visit('/') + cy.get('.framework-wheel-container').should('have.length', 1) + }) + + it('displays the title of the teachers and leadership accordion', () => { + cy.visit('/') + cy.get('[data-bs-target="#teachers-and-leadership-item"]').should('include.text', "Teachers & Leadership") + }) + + it('shows schools when a district is selected', () => { + cy.visit('/') + cy.get("#district-dropdown").select('Lee Public Schools') + cy.get("#school-dropdown").select('Lee Elementary School') + cy.get("#school-dropdown").children("option[selected='selected']").should('have.text', 'Lee Elementary School') + + cy.contains('Go').click() + + cy.url().should('include', '/districts/lee-public-schools/schools/lee-elementary-school/overview?year=2022-23') + }) + + + it('displays overview content', () => { + login("districts/lee-public-schools/schools/lee-elementary-school/overview?year=2022-23", "bGVlOmxlZSE=") + cy.url().should('include', '/districts/lee-public-schools/schools/lee-elementary-school/overview?year=2022-23') + const icons = ['.fa-apple-alt', '.fa-school', '.fa-users-cog', '.fa-graduation-cap', '.fa-heart'] + for (const icon of icons) { + cy.get(icon).should('exist') + } + }) +}) + +// # district_admin_sees_overview_content + +// # click_on "Teachers & Leadership" +// # district_admin_sees_browse_content + +// # click_on "Overview" +// # district_admin_sees_overview_content + +// # click_on "Analyze" +// # district_admin_sees_analyze_content + +// # go_to_different_category(different_category) +// # district_admin_sees_category_change + +// # go_to_different_subcategory(different_subcategory) +// # district_admin_sees_subcategory_change + +// # click_on "Browse" +// # district_admin_sees_browse_content + +// # click_on "School Culture" +// # expect(page).to have_text("Measures the degree to which the school environment is safe, caring, and academically-oriented. It considers factors like bullying, student-teacher relationships, and student valuing of learning.") + +// # go_to_different_school_in_same_district(school_in_same_district) +// # district_admin_sees_schools_change + +// # go_to_different_district(different_district) +// # district_admin_sees_district_change + +// # go_to_different_year(ay_2019_20) +// # district_admin_sees_year_change + +function login(path, credentials) { + cy.visit(path, { + headers: { + authorization: `Basic ${credentials}` + }, + failOnStatusCode: false + }) +} diff --git a/cypress/example_tests/1-getting-started/todo.cy.js b/cypress/example_tests/1-getting-started/todo.cy.js new file mode 100644 index 00000000..7d013aac --- /dev/null +++ b/cypress/example_tests/1-getting-started/todo.cy.js @@ -0,0 +1,144 @@ +/// + +// Welcome to Cypress! +// +// This spec file contains a variety of sample tests +// for a todo list app that are designed to demonstrate +// the power of writing tests in Cypress. +// +// To learn more about how Cypress works and +// what makes it such an awesome testing tool, +// please read our getting started guide: +// https://on.cypress.io/introduction-to-cypress + +describe('example to-do app', () => { + beforeEach(() => { + // Cypress starts out with a blank slate for each test + // so we must tell it to visit our website with the `cy.visit()` command. + // Since we want to visit the same URL at the start of all our tests, + // we include it in our beforeEach function so that it runs before each test + // cy.visit('https://example.cypress.io/todo') + cy.visit(`${Cypress.env('host')}`) + }) + + it('displays two todo items by default', () => { + // We use the `cy.get()` command to get all elements that match the selector. + // Then, we use `should` to assert that there are two matched items, + // which are the two default items. + cy.get('.todo-list li').should('have.length', 2) + + // We can go even further and check that the default todos each contain + // the correct text. We use the `first` and `last` functions + // to get just the first and last matched elements individually, + // and then perform an assertion with `should`. + cy.get('.todo-list li').first().should('have.text', 'Pay electric bill') + cy.get('.todo-list li').last().should('have.text', 'Walk the dog') + }) + + it('can add new todo items', () => { + // We'll store our item text in a variable so we can reuse it + const newItem = 'Feed the cat' + + // Let's get the input element and use the `type` command to + // input our new list item. After typing the content of our item, + // we need to type the enter key as well in order to submit the input. + // This input has a data-test attribute so we'll use that to select the + // element in accordance with best practices: + // https://on.cypress.io/selecting-elements + cy.get('[data-test=new-todo]').type(`${newItem}{enter}`) + + // Now that we've typed our new item, let's check that it actually was added to the list. + // Since it's the newest item, it should exist as the last element in the list. + // In addition, with the two default items, we should have a total of 3 elements in the list. + // Since assertions yield the element that was asserted on, + // we can chain both of these assertions together into a single statement. + cy.get('.todo-list li') + .should('have.length', 3) + .last() + .should('have.text', newItem) + }) + + it('can check off an item as completed', () => { + // In addition to using the `get` command to get an element by selector, + // we can also use the `contains` command to get an element by its contents. + // However, this will yield the