From 0904e07f140e5bc133a4875477eb6abb11e05c46 Mon Sep 17 00:00:00 2001 From: Nick LaMuro Date: Tue, 30 Jun 2020 13:41:58 -0500 Subject: [PATCH 1/9] [webpack] Add integration env --- bin/webpack | 4 ++-- config/webpack/integration.js | 11 +++++++++++ config/webpacker.yml | 9 +++++++++ 3 files changed, 22 insertions(+), 2 deletions(-) create mode 100644 config/webpack/integration.js diff --git a/bin/webpack b/bin/webpack index 1395d029d9f..ab070d3b88d 100755 --- a/bin/webpack +++ b/bin/webpack @@ -7,12 +7,12 @@ require "yaml" ENV["RAILS_ENV"] ||= "development" RAILS_ENV = ENV["RAILS_ENV"] -ENV["NODE_ENV"] ||= RAILS_ENV +ENV["NODE_ENV"] ||= RAILS_ENV == "integration" ? "production" : RAILS_ENV NODE_ENV = ENV["NODE_ENV"] APP_PATH = File.expand_path("../", __dir__) NODE_MODULES_PATH = File.join(APP_PATH, "node_modules") -WEBPACK_CONFIG = File.join(APP_PATH, "config/webpack/#{NODE_ENV}.js") +WEBPACK_CONFIG = File.join(APP_PATH, "config/webpack/#{RAILS_ENV}.js") unless File.exist?(WEBPACK_CONFIG) puts "Webpack configuration not found." diff --git a/config/webpack/integration.js b/config/webpack/integration.js new file mode 100644 index 00000000000..a03774642c4 --- /dev/null +++ b/config/webpack/integration.js @@ -0,0 +1,11 @@ +// Note: You must restart bin/webpack-dev-server for changes to take effect + +/* eslint global-require: 0 */ + +const { env } = require('process') + +if (env.CYPRESS_DEV) { + module.exports = require('./development.js') +} else { + module.exports = require('./production.js') +} diff --git a/config/webpacker.yml b/config/webpacker.yml index 57f56bf7674..ecc351bcbef 100644 --- a/config/webpacker.yml +++ b/config/webpacker.yml @@ -26,5 +26,14 @@ development: test: <<: *default +integration: + <<: *default + +# uncomment these to enable webpack:server + dev_server: + host: 0.0.0.0 + port: 8080 + https: false + production: <<: *default From 4d52a4fb31011ac837610607c5b505cc4c4bcd27 Mon Sep 17 00:00:00 2001 From: Nick LaMuro Date: Tue, 30 Jun 2020 14:05:00 -0500 Subject: [PATCH 2/9] [ui_tasks.rake] Use app_prefix trick Allows asset-compile tasks to run properly from manageiq-ui-classic, allowing the full `cypress` test suite tasks to be invoked from there as well. --- lib/tasks/manageiq/ui_tasks.rake | 26 ++++++++++++++++---------- 1 file changed, 16 insertions(+), 10 deletions(-) diff --git a/lib/tasks/manageiq/ui_tasks.rake b/lib/tasks/manageiq/ui_tasks.rake index 2fd22ce21d0..9d93fc4f702 100644 --- a/lib/tasks/manageiq/ui_tasks.rake +++ b/lib/tasks/manageiq/ui_tasks.rake @@ -88,27 +88,33 @@ namespace :webpack do end end +miq_app_prefix = defined?(ENGINE_ROOT) ? "app:" : "" + # compile and clobber when running assets:* tasks -if Rake::Task.task_defined?("assets:precompile") - Rake::Task["assets:precompile"].enhance do - Rake::Task["webpack:compile"].invoke unless ENV["TRAVIS"] +if Rake::Task.task_defined?("#{miq_app_prefix}assets:precompile") + unless ENV["TRAVIS"] + Rake::Task["#{miq_app_prefix}assets:precompile"].enhance do + Rake::Task["webpack:compile"].invoke + end end - Rake::Task["assets:precompile"].actions.each do |action| + Rake::Task["#{miq_app_prefix}assets:precompile"].actions.each do |action| if action.source_location[0].include?(File.join("lib", "tasks", "webpacker")) - Rake::Task["assets:precompile"].actions.delete(action) + Rake::Task["#{miq_app_prefix}assets:precompile"].actions.delete(action) end end end -if Rake::Task.task_defined?("assets:clobber") - Rake::Task["assets:clobber"].enhance do - Rake::Task["webpack:clobber"].invoke unless ENV["TRAVIS"] +if Rake::Task.task_defined?("#{miq_app_prefix}assets:clobber") + unless ENV["TRAVIS"] + Rake::Task["#{miq_app_prefix}assets:clobber"].enhance do + Rake::Task["webpack:clobber"].invoke + end end - Rake::Task["assets:clobber"].actions.each do |action| + Rake::Task["#{miq_app_prefix}assets:clobber"].actions.each do |action| if action.source_location[0].include?(File.join("lib", "tasks", "webpacker")) - Rake::Task["assets:clobber"].actions.delete(action) + Rake::Task["#{miq_app_prefix}assets:clobber"].actions.delete(action) end end end From 1fbcc6069d8e9415290fd6cb5ff9c3008dade798 Mon Sep 17 00:00:00 2001 From: Nick LaMuro Date: Mon, 13 Jul 2020 11:25:44 -0500 Subject: [PATCH 3/9] Add cypress.rake Adds a new set of rake tasks for setting up and running cypress. Makes use of the `integration:*` rake tasks from core to leverage running a server in the background. --- lib/tasks/manageiq/cypress.rake | 16 ++++++++++++++++ package.json | 1 + 2 files changed, 17 insertions(+) create mode 100644 lib/tasks/manageiq/cypress.rake diff --git a/lib/tasks/manageiq/cypress.rake b/lib/tasks/manageiq/cypress.rake new file mode 100644 index 00000000000..c88a4bf8d4c --- /dev/null +++ b/lib/tasks/manageiq/cypress.rake @@ -0,0 +1,16 @@ +# This check ensures that we only load this task under the `app:` prefix when +# in `manageiq-ui-classic`, and loads it normally under `manageiq` +# +# Without it, a few things happen: +# +# - There is an error since there is no manageiq/integration file in the path +# - If the above is addressed, it will load this task in two contexts when +# loading from manageiq-ui-classic: +# - cypress:ui:* +# - app:cypress:ui:* +# +if Rails.root + require "manageiq/integration" + + ManageIQ::Integration::CypressRakeTask.new(:ui) +end diff --git a/package.json b/package.json index a634ff7e2f2..4a1a9ddd7ed 100644 --- a/package.json +++ b/package.json @@ -12,6 +12,7 @@ ], "scripts": { "cypress:open": "cypress open", + "cypress:run:ci": "cypress run --headless --browser chrome --config video=false ", "cypress:run:chrome": "cypress run --headless --browser chrome", "cypress:run:firefox": "cypress run --headless --browser firefox", "test": "jest", From 4ea61066901d4105a5ad170f8c4954f4a68f466a Mon Sep 17 00:00:00 2001 From: Nick LaMuro Date: Tue, 14 Jul 2020 19:05:46 -0500 Subject: [PATCH 4/9] [cypress/support/index.js] No screenshots on CI Disables screenshots when running in travis. cypress.env.json generation --------------------------- Cypress handles environment variables itself, so `process.env` isn't available. As a result, "Option 2" is what is being done from here: https://docs.cypress.io/guides/guides/environment-variables.html#Option-2-cypress-env-json which makes sure a default config file exists as part of the `setup` step for running the specs. A `file` task is used here to only run it if the file doesn't exist, but makes it a dependency for the `:setup` task. By default, screenshots are disabled in CI, but enabled in dev. The cypress.env.json file is also ignored by git, so local edits can happen and can be customized for each developer. On CI, it will just be copied in and the defaults from `.cypress.ci.env.json` will be what is configured in that environment --- .cypress.ci.env.json | 3 +++ .cypress.dev.env.json | 3 +++ .gitignore | 1 + cypress/support/index.js | 4 ++++ 4 files changed, 11 insertions(+) create mode 100644 .cypress.ci.env.json create mode 100644 .cypress.dev.env.json diff --git a/.cypress.ci.env.json b/.cypress.ci.env.json new file mode 100644 index 00000000000..1a79ceb1163 --- /dev/null +++ b/.cypress.ci.env.json @@ -0,0 +1,3 @@ +{ + "disable_screenshots": "true" +} diff --git a/.cypress.dev.env.json b/.cypress.dev.env.json new file mode 100644 index 00000000000..18c5362392c --- /dev/null +++ b/.cypress.dev.env.json @@ -0,0 +1,3 @@ +{ + "disable_screenshots": "false" +} diff --git a/.gitignore b/.gitignore index bf1f7099e4e..6c5c1acb42a 100644 --- a/.gitignore +++ b/.gitignore @@ -21,3 +21,4 @@ config/webpack/paths.json *.sw[po] cypress/screenshots cypress/videos +cypress.env.json diff --git a/cypress/support/index.js b/cypress/support/index.js index d68db96df26..f9d539cd7c8 100644 --- a/cypress/support/index.js +++ b/cypress/support/index.js @@ -18,3 +18,7 @@ import './commands' // Alternatively you can use CommonJS syntax: // require('./commands') + +Cypress.Screenshot.defaults({ + screenshotOnRunFailure: !(Cypress.env('disable_screenshots') == 'true') +}) From 4a37dc34f130e56c8b41d00addd59a98ca281f5c Mon Sep 17 00:00:00 2001 From: Nick LaMuro Date: Tue, 14 Jul 2020 19:06:57 -0500 Subject: [PATCH 5/9] [Rakefile] Add spec:cypress task Just a helper task for executing the cypress:specs (intended for CI) --- Rakefile | 3 +++ 1 file changed, 3 insertions(+) diff --git a/Rakefile b/Rakefile index 7871428aae0..0556dfb09b6 100644 --- a/Rakefile +++ b/Rakefile @@ -54,6 +54,9 @@ namespace :spec do desc "Run all javascript specs" task :javascript => ["app:test:initialize", :environment, "jasmine:ci"] + desc "Run all cypress specs" + task :cypress => ["app:cypress:ui:run"] + desc "Try to compile assets" task :compile => ["app:assets:precompile"] From f990a16bfdd75381a8095c063de883bf200a80a7 Mon Sep 17 00:00:00 2001 From: Nick LaMuro Date: Tue, 14 Jul 2020 19:12:13 -0500 Subject: [PATCH 6/9] [tools/ci] Support spec:cypress - Install node for spec:cypress - Do an app:cypress:ui:seed instead of update:ui The app:cypress:ui:seed handles compiling assets, so that would be duplicate work to run it twice. --- tools/ci/before_install.sh | 2 +- tools/ci/before_script.sh | 7 ++++++- 2 files changed, 7 insertions(+), 2 deletions(-) diff --git a/tools/ci/before_install.sh b/tools/ci/before_install.sh index a5c7c6dca14..8d2f89c8fc4 100644 --- a/tools/ci/before_install.sh +++ b/tools/ci/before_install.sh @@ -1,4 +1,4 @@ # not spec, and not cross repo -if [ "$TEST_SUITE" = "spec:javascript" -o "$TEST_SUITE" = "spec:jest" -o "$TEST_SUITE" = "spec:compile" ]; then +if [ "$TEST_SUITE" = "spec:javascript" -o "$TEST_SUITE" = "spec:jest" -o "$TEST_SUITE" = "spec:compile" -o "$TEST_SUITE" = "spec:cypress" ]; then nvm install 12 fi diff --git a/tools/ci/before_script.sh b/tools/ci/before_script.sh index 7fb1ed2d92f..46625b84bec 100644 --- a/tools/ci/before_script.sh +++ b/tools/ci/before_script.sh @@ -1,8 +1,13 @@ # not spec, and not cross repo -if [ "$TEST_SUITE" = "spec:javascript" -o "$TEST_SUITE" = "spec:jest" -o "$TEST_SUITE" = "spec:compile" ]; then +if [ "$TEST_SUITE" = "spec:javascript" -o "$TEST_SUITE" = "spec:jest" -o "$TEST_SUITE" = "spec:compile" -o "$TEST_SUITE" = "spec:cypress" ]; then # make sure yarn is installed, in the right version bundle exec rake webpacker:check_yarn || npm install -g yarn # install & compile dependencies bundle exec rake update:ui + + if [ "$TEST_SUITE" = "spec:cypress" ]; then + # run evm:compile_assets (and friends) if cypress + bundle exec rake app:cypress:ui:seed + fi fi From 90add9563f1ddf4c4f98393d5814f275f7100df6 Mon Sep 17 00:00:00 2001 From: Nick LaMuro Date: Tue, 14 Jul 2020 19:14:27 -0500 Subject: [PATCH 7/9] [WIP][.travis.yml] Enable spec:cypress --- .travis.yml | 9 +++------ 1 file changed, 3 insertions(+), 6 deletions(-) diff --git a/.travis.yml b/.travis.yml index 1a56fe7785e..a8dc4e09d01 100644 --- a/.travis.yml +++ b/.travis.yml @@ -14,12 +14,7 @@ addons: chrome: beta env: matrix: - - TEST_SUITE=spec - - TEST_SUITE=spec:routes - - TEST_SUITE=spec:javascript - - TEST_SUITE=spec:compile - - TEST_SUITE=spec:jest - - TEST_SUITE=spec:debride + - TEST_SUITE=spec:cypress matrix: exclude: - rvm: 2.5.7 @@ -32,6 +27,8 @@ matrix: env: TEST_SUITE=spec:jest - rvm: 2.5.7 env: TEST_SUITE=spec:debride + - rvm: 2.5.7 + env: TEST_SUITE=spec:cypress bundler_args: "--no-deployment" before_install: source tools/ci/before_install.sh install: bin/setup From d2facc21d7c55a1b45fe4660a3b9a9dde256116a Mon Sep 17 00:00:00 2001 From: Nick LaMuro Date: Wed, 22 Jul 2020 18:34:51 -0500 Subject: [PATCH 8/9] [POC] Setup Rake task Because I am tired of doing `bash -l`... for cross repo tests... Edit: Well... apparently it is required for anything `nvm` related... Edit 2: `nvm` seems to install then use, so just makes sense to have this as one step. Edit 3: `nvm` is a function, so have to source it first... ugh Edit 4: Set PATH for node. Add spec:cypress:ci exec task (sets path) Edit 5: Debugging... Edit 6: Forgot to add `../bin`... derp Edit 7: Call seed prior to spec:cypress to avoid DB loaded errors "DB loaded errors come from the following task: app:test:verify_no_db_access_loading_rails_environment --- Rakefile | 18 ++++++++++++++++++ 1 file changed, 18 insertions(+) diff --git a/Rakefile b/Rakefile index 0556dfb09b6..de1e144ddc2 100644 --- a/Rakefile +++ b/Rakefile @@ -57,6 +57,24 @@ namespace :spec do desc "Run all cypress specs" task :cypress => ["app:cypress:ui:run"] + namespace :cypress do + task :ci => ["ci:set_node_path", "app:cypress:ui:seed", "spec:cypress"] + + task "ci:install_yarn" do + sh "/bin/bash", "-c", "source ~/.nvm/nvm.sh && nvm install 12 && npm install -g yarn" + end + + task "ci:set_node_path" do + ENV["PATH"] = "#{Dir.glob("/home/travis/.nvm/versions/node/v12*").sort.last}/bin:#{ENV["PATH"]}" + # puts;puts;puts + # puts ">>>>>> PATH: #{ENV["PATH"]}" + # puts;puts;puts + end + + desc "Setup for CI" + task "ci:setup" => ["ci:install_yarn", "ci:set_node_path", "update:ui"] + end + desc "Try to compile assets" task :compile => ["app:assets:precompile"] From 0b81ff8484b37e18a71532c83536ef865abac58a Mon Sep 17 00:00:00 2001 From: Nick LaMuro Date: Tue, 11 Aug 2020 16:19:53 -0500 Subject: [PATCH 9/9] [cypress] Breakup and remove commands.js Breaks the file into multiple files of two different categories: - commands - assertions --- cypress/support/assertions/expect_title.js | 7 + cypress/support/commands.js | 198 --------------------- cypress/support/commands/explorer.js | 18 ++ cypress/support/commands/gtl.js | 22 +++ cypress/support/commands/login.js | 10 ++ cypress/support/commands/menu.js | 39 ++++ cypress/support/commands/search.js | 8 + cypress/support/commands/toolbar.js | 61 +++++++ cypress/support/index.js | 39 +++- 9 files changed, 200 insertions(+), 202 deletions(-) create mode 100644 cypress/support/assertions/expect_title.js delete mode 100644 cypress/support/commands.js create mode 100644 cypress/support/commands/explorer.js create mode 100644 cypress/support/commands/gtl.js create mode 100644 cypress/support/commands/login.js create mode 100644 cypress/support/commands/menu.js create mode 100644 cypress/support/commands/search.js create mode 100644 cypress/support/commands/toolbar.js diff --git a/cypress/support/assertions/expect_title.js b/cypress/support/assertions/expect_title.js new file mode 100644 index 00000000000..8b795f7d777 --- /dev/null +++ b/cypress/support/assertions/expect_title.js @@ -0,0 +1,7 @@ +Cypress.Commands.add("expect_explorer_title", (text) => { + return cy.get('#explorer_title_text').contains(text); +}); + +Cypress.Commands.add("expect_show_list_title", (text) => { + return cy.get('#main-content h1').contains(text); +}); diff --git a/cypress/support/commands.js b/cypress/support/commands.js deleted file mode 100644 index f2f15b9fd86..00000000000 --- a/cypress/support/commands.js +++ /dev/null @@ -1,198 +0,0 @@ -// *********************************************** -// This example commands.js shows you how to -// create various custom commands and overwrite -// existing commands. -// -// For more comprehensive examples of custom -// commands please read more here: -// https://on.cypress.io/custom-commands -// *********************************************** -// -// -// -- This is a parent command -- -// Cypress.Commands.add("login", (email, password) => { ... }) -// -// -// -- This is a child command -- -// Cypress.Commands.add("drag", { prevSubject: 'element'}, (subject, options) => { ... }) -// -// -// -- This is a dual command -- -// Cypress.Commands.add("dismiss", { prevSubject: 'optional'}, (subject, options) => { ... }) -// -// -// -- This will overwrite an existing command -- -// Cypress.Commands.overwrite("visit", (originalFn, url, options) => { ... }) - -// cy.login() - log in -// FIXME: use cy.request and inject cookie and localStorage.miqToken -Cypress.Commands.add("login", (user = 'admin', password = 'smartvm') => { - cy.visit('/'); - - cy.get('#user_name').type(user); - cy.get('#user_password').type(password); - return cy.get('#login').click(); -}); - -// cy.menu('Compute', 'Infrastructure', 'VMs') - navigate the main menu -Cypress.Commands.add("menu", (...items) => { - expect(items.length).to.be.within(1, 3); - - const primary = '#main-menu nav.primary'; - const secondary = '#main-menu nav.secondary'; - - let ret = cy.get(`${primary} > ul > li`) - .contains('a > span', items[0]) - .parent().parent() - .click(); - - if (items.length === 2) { - ret = cy.get(`${secondary} > ul > li`) - .contains('a > span', items[1]) - .parent().parent() - .click(); - } - - if (items.length === 3) { - ret = cy.get(`${secondary} > ul > li`) - .contains('button > span', items[1]) - .parent().parent() - .click() - .find(`ul > li`) - .contains('a > span', items[2]) - .parent().parent() - .click(); - } - - return ret; - // TODO support by id: cy.get('li[id=menu_item_provider_foreman]').click({ force: true }); -}); - -// cy.menuItems() - returns an array of top level menu items with {title, href, items (array of children)} -Cypress.Commands.add("menuItems", () => { - cy.get('#main-menu nav.primary'); // Wait for menu to appear - return cy.window().then((window) => window.ManageIQ.menu); -}); - -// cy.accordion('Catalog Items') - click accordion, unless already expanded -Cypress.Commands.add('accordion', (text) => { - cy.get('#main-content'); // ensure screen loads first - - let ret = cy.get('#accordion') - .find('.panel-title a') - .contains(new RegExp(`^${text}$`)) - - ret.then((el) => { - // do not collapse if expanded - if (el.is('.collapsed')) { - el.click(); - } - return el; - }); - - return ret.parents('.panel'); -}); - -// cy.toolbarItem('Configuration', 'Edit this VM') - return a toolbar button state -Cypress.Commands.add('toolbarItem', (...items) => { - expect(items.length).to.be.within(1, 2); - - let ret = cy.get('#toolbar') - .find(items[1] ? '.dropdown-toggle' : 'button') - .contains(items[0]); - - if (items[1]) { - ret = ret.siblings('.dropdown-menu') - .find('a > span') - .contains(items[1]) - .parents('a > span'); - } - - return ret.then((el) => { - return { - id: el[0].id, - label: el.text().trim(), - icon: el.find('i'), - disabled: !items[1] ? !!el.prop('disabled') : !!el.parents('li').hasClass('disabled'), - }; - }); -}); - -// cy.toolbar('Configuration', 'Edit this VM') - click a toolbar button -// TODO {id: 'view_grid'|'view_tile'|'view_list'} for the view toolbar -// TODO {id: 'download_choice'}, .. for the download dropdown -// TODO: some alias for having %i.fa-download, .fa-refresh, .pficon-print, .fa-arrow-left -// TODO custom buttons -Cypress.Commands.add('toolbar', (...items) => { - expect(items.length).to.be.within(1, 2); - - let ret = cy.get('#toolbar') - .contains(items[1] ? '.dropdown-toggle' : 'button', items[0]); - // by-id: #id on button instead of .contains - - ret = ret.then((el) => { - assert.equal(!!el.prop('disabled'), false, "Parent toolbar button disabled"); - return el; - }); - - if (items[1]) { - ret.click(); - - // a doesn't react to click here, have to click the span - ret = ret.siblings('.dropdown-menu') - .find('a > span') - .contains(items[1]) - .parents('a > span'); - // by-id: #id on the same span - - ret = ret.then((el) => { - assert.equal(el.parents('li').hasClass('disabled'), false, "Child toolbar button disabled"); - return el; - }); - } - - return ret.click(); - // TODO .dropdown-toggle#vm_lifecycle_choice -}); - -// assertions - -Cypress.Commands.add("expect_explorer_title", (text) => { - return cy.get('#explorer_title_text').contains(text); -}); - -Cypress.Commands.add("expect_show_list_title", (text) => { - return cy.get('#main-content h1').contains(text); -}); - -// GTL related helpers -Cypress.Commands.add("gtl_error", () => { - return cy.get('#miq-gtl-view > #flash_msg_div').should('be.visible'); -}); - -Cypress.Commands.add("gtl_no_record", () => { - return cy.get('#miq-gtl-view > div.no-record').should('be.visible'); -}); - -Cypress.Commands.add("gtl", () => { - cy.get('#miq-gtl-view').then($gtlTile => { - if ($gtlTile.find("miq-tile-view").length > 0) { - return cy.get("div[pf-card-view] > .card-view-pf > .card"); - } else { - return cy.get('#miq-gtl-view > miq-data-table > div > table'); - }; - }); -}); - -Cypress.Commands.add("gtl_click", (name) => { - cy.gtl().contains(name).click() -}); - -// Searchbox related helpers -Cypress.Commands.add("search_box", () => { - return cy.get('#search_text').should('be.visible'); -}); - -Cypress.Commands.add("no_search_box", () => { - return cy.get('#search_text').should('not.be.visible'); -}); diff --git a/cypress/support/commands/explorer.js b/cypress/support/commands/explorer.js new file mode 100644 index 00000000000..ad89345b238 --- /dev/null +++ b/cypress/support/commands/explorer.js @@ -0,0 +1,18 @@ +// cy.accordion('Catalog Items') - click accordion, unless already expanded +Cypress.Commands.add('accordion', (text) => { + cy.get('#main-content'); // ensure screen loads first + + let ret = cy.get('#accordion') + .find('.panel-title a') + .contains(new RegExp(`^${text}$`)) + + ret.then((el) => { + // do not collapse if expanded + if (el.is('.collapsed')) { + el.click(); + } + return el; + }); + + return ret.parents('.panel'); +}); diff --git a/cypress/support/commands/gtl.js b/cypress/support/commands/gtl.js new file mode 100644 index 00000000000..06d16bd7804 --- /dev/null +++ b/cypress/support/commands/gtl.js @@ -0,0 +1,22 @@ +// GTL related helpers +Cypress.Commands.add("gtl_error", () => { + return cy.get('#miq-gtl-view > #flash_msg_div').should('be.visible'); +}); + +Cypress.Commands.add("gtl_no_record", () => { + return cy.get('#miq-gtl-view > div.no-record').should('be.visible'); +}); + +Cypress.Commands.add("gtl", () => { + cy.get('#miq-gtl-view').then($gtlTile => { + if ($gtlTile.find("miq-tile-view").length > 0) { + return cy.get("div[pf-card-view] > .card-view-pf > .card"); + } else { + return cy.get('#miq-gtl-view > miq-data-table > div > table'); + }; + }); +}); + +Cypress.Commands.add("gtl_click", (name) => { + cy.gtl().contains(name).click() +}); diff --git a/cypress/support/commands/login.js b/cypress/support/commands/login.js new file mode 100644 index 00000000000..d35c2ecb515 --- /dev/null +++ b/cypress/support/commands/login.js @@ -0,0 +1,10 @@ +// cy.login() - log in +// FIXME: use cy.request and inject cookie and localStorage.miqToken +Cypress.Commands.add("login", (user = 'admin', password = 'smartvm') => { + cy.visit('/'); + + cy.get('#user_name').type(user); + cy.get('#user_password').type(password); + return cy.get('#login').click(); +}); + diff --git a/cypress/support/commands/menu.js b/cypress/support/commands/menu.js new file mode 100644 index 00000000000..e880df93a8e --- /dev/null +++ b/cypress/support/commands/menu.js @@ -0,0 +1,39 @@ +// cy.menu('Compute', 'Infrastructure', 'VMs') - navigate the main menu +Cypress.Commands.add("menu", (...items) => { + expect(items.length).to.be.within(1, 3); + + const primary = '#main-menu nav.primary'; + const secondary = '#main-menu nav.secondary'; + + let ret = cy.get(`${primary} > ul > li`) + .contains('a > span', items[0]) + .parent().parent() + .click(); + + if (items.length === 2) { + ret = cy.get(`${secondary} > ul > li`) + .contains('a > span', items[1]) + .parent().parent() + .click(); + } + + if (items.length === 3) { + ret = cy.get(`${secondary} > ul > li`) + .contains('button > span', items[1]) + .parent().parent() + .click() + .find(`ul > li`) + .contains('a > span', items[2]) + .parent().parent() + .click(); + } + + return ret; + // TODO support by id: cy.get('li[id=menu_item_provider_foreman]').click({ force: true }); +}); + +// cy.menuItems() - returns an array of top level menu items with {title, href, items (array of children)} +Cypress.Commands.add("menuItems", () => { + cy.get('#main-menu nav.primary'); // Wait for menu to appear + return cy.window().then((window) => window.ManageIQ.menu); +}); diff --git a/cypress/support/commands/search.js b/cypress/support/commands/search.js new file mode 100644 index 00000000000..906a8f1b41b --- /dev/null +++ b/cypress/support/commands/search.js @@ -0,0 +1,8 @@ +// Searchbox related helpers +Cypress.Commands.add("search_box", () => { + return cy.get('#search_text').should('be.visible'); +}); + +Cypress.Commands.add("no_search_box", () => { + return cy.get('#search_text').should('not.be.visible'); +}); diff --git a/cypress/support/commands/toolbar.js b/cypress/support/commands/toolbar.js new file mode 100644 index 00000000000..2e4a4d05b17 --- /dev/null +++ b/cypress/support/commands/toolbar.js @@ -0,0 +1,61 @@ +// cy.toolbarItem('Configuration', 'Edit this VM') - return a toolbar button state +Cypress.Commands.add('toolbarItem', (...items) => { + expect(items.length).to.be.within(1, 2); + + let ret = cy.get('#toolbar') + .find(items[1] ? '.dropdown-toggle' : 'button') + .contains(items[0]); + + if (items[1]) { + ret = ret.siblings('.dropdown-menu') + .find('a > span') + .contains(items[1]) + .parents('a > span'); + } + + return ret.then((el) => { + return { + id: el[0].id, + label: el.text().trim(), + icon: el.find('i'), + disabled: !items[1] ? !!el.prop('disabled') : !!el.parents('li').hasClass('disabled'), + }; + }); +}); + +// cy.toolbar('Configuration', 'Edit this VM') - click a toolbar button +// TODO {id: 'view_grid'|'view_tile'|'view_list'} for the view toolbar +// TODO {id: 'download_choice'}, .. for the download dropdown +// TODO: some alias for having %i.fa-download, .fa-refresh, .pficon-print, .fa-arrow-left +// TODO custom buttons +Cypress.Commands.add('toolbar', (...items) => { + expect(items.length).to.be.within(1, 2); + + let ret = cy.get('#toolbar') + .contains(items[1] ? '.dropdown-toggle' : 'button', items[0]); + // by-id: #id on button instead of .contains + + ret = ret.then((el) => { + assert.equal(!!el.prop('disabled'), false, "Parent toolbar button disabled"); + return el; + }); + + if (items[1]) { + ret.click(); + + // a doesn't react to click here, have to click the span + ret = ret.siblings('.dropdown-menu') + .find('a > span') + .contains(items[1]) + .parents('a > span'); + // by-id: #id on the same span + + ret = ret.then((el) => { + assert.equal(el.parents('li').hasClass('disabled'), false, "Child toolbar button disabled"); + return el; + }); + } + + return ret.click(); + // TODO .dropdown-toggle#vm_lifecycle_choice +}); diff --git a/cypress/support/index.js b/cypress/support/index.js index f9d539cd7c8..0a79512a6ef 100644 --- a/cypress/support/index.js +++ b/cypress/support/index.js @@ -11,13 +11,44 @@ // // You can read more here: // https://on.cypress.io/configuration + + +// *********************************************** +// Below shows you how to create various custom +// commands and overwrite existing commands. +// +// For more comprehensive examples of custom +// commands please read more here: +// https://on.cypress.io/custom-commands +// *********************************************** +// +// +// -- This is a parent command -- +// Cypress.Commands.add("login", (email, password) => { ... }) +// +// +// -- This is a child command -- +// Cypress.Commands.add("drag", { prevSubject: 'element'}, (subject, options) => { ... }) +// +// +// -- This is a dual command -- +// Cypress.Commands.add("dismiss", { prevSubject: 'optional'}, (subject, options) => { ... }) +// +// +// -- This will overwrite an existing command -- +// Cypress.Commands.overwrite("visit", (originalFn, url, options) => { ... }) // *********************************************************** -// Import commands.js using ES2015 syntax: -import './commands' +// Commands +import './commands/gtl.js' +import './commands/login.js' +import './commands/menu.js' +import './commands/search.js' +import './commands/toolbar.js' -// Alternatively you can use CommonJS syntax: -// require('./commands') +// Assertions +import './assertions/expect_explorer_title.js' +import './assertions/expect_show_list_title.js' Cypress.Screenshot.defaults({ screenshotOnRunFailure: !(Cypress.env('disable_screenshots') == 'true')