Skip to content

Commit 9802b1e

Browse files
authored
Merge pull request #9633 from jrafanie/cypress-on-rails
Cypress on rails
2 parents b0cf6fb + 1191a31 commit 9802b1e

File tree

16 files changed

+392
-149
lines changed

16 files changed

+392
-149
lines changed
Lines changed: 57 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,57 @@
1+
# This is identical to the generated file[1] with the exceptions below:
2+
# [1] https://github.com/shakacode/cypress-playwright-on-rails/blob/ac5d69f9ea951c7545e5141db2abb2cb1350e740/lib/generators/cypress_on_rails/templates/config/initializers/cypress_on_rails.rb.erb
3+
# * install_folder (uses our rails engine path)
4+
# * require ENV['CYPRESS'] to be set
5+
# * requires and instantiates the seeded_deletion strategy
6+
if ENV['CYPRESS'].present?
7+
CypressOnRails.configure do |c|
8+
c.api_prefix = ""
9+
10+
# Currently, the only change from the template:
11+
c.install_folder = ManageIQ::UI::Classic::Engine.root.join("cypress/e2e")
12+
# WARNING!! CypressOnRails can execute arbitrary ruby code
13+
# please use with extra caution if enabling on hosted servers or starting your local server on 0.0.0.0
14+
c.use_middleware = !Rails.env.production?
15+
# c.use_vcr_middleware = !Rails.env.production?
16+
# # Use this if you want to use use_cassette wrapper instead of manual insert/eject
17+
# # c.use_vcr_use_cassette_middleware = !Rails.env.production?
18+
# # Pass custom VCR options
19+
# c.vcr_options = {
20+
# hook_into: :webmock,
21+
# default_cassette_options: { record: :once },
22+
# cassette_library_dir: File.expand_path("#{__dir__}/../../e2e/cypress/fixtures/vcr_cassettes")
23+
# }
24+
c.logger = Rails.logger
25+
26+
# Server configuration for rake tasks (cypress:open, cypress:run, playwright:open, playwright:run)
27+
# c.server_host = 'localhost' # or use ENV['CYPRESS_RAILS_HOST']
28+
# c.server_port = 3001 # or use ENV['CYPRESS_RAILS_PORT']
29+
# c.transactional_server = true # Enable automatic transaction rollback between tests
30+
31+
# Server lifecycle hooks for rake tasks
32+
# c.before_server_start = -> { DatabaseCleaner.clean_with(:truncation) }
33+
# c.after_server_start = -> { puts "Test server started on port #{CypressOnRails.configuration.server_port}" }
34+
# c.after_transaction_start = -> { Rails.application.load_seed }
35+
# c.after_state_reset = -> { Rails.cache.clear }
36+
# c.before_server_stop = -> { puts "Stopping test server..." }
37+
38+
# If you want to enable a before_request logic, such as authentication, logging, sending metrics, etc.
39+
# Refer to https://www.rubydoc.info/gems/rack/Rack/Request for the `request` argument.
40+
# Return nil to continue through the Cypress command. Return a response [status, header, body] to halt.
41+
# c.before_request = lambda { |request|
42+
# unless request.env['warden'].authenticate(:secret_key)
43+
# return [403, {}, ["forbidden"]]
44+
# end
45+
# }
46+
end
47+
48+
# # if you compile your assets on CI
49+
# if ENV['CYPRESS'].present? && ENV['CI'].present?
50+
# Rails.application.configure do
51+
# config.assets.compile = false
52+
# config.assets.unknown_asset_fallback = false
53+
# end
54+
# end
55+
require 'extensions/database_cleaner-activerecord-seeded_deletion'
56+
DatabaseCleaner[:active_record].strategy = DatabaseCleaner::ActiveRecord::SeededDeletion.new(:pre_count => true)
57+
end
Lines changed: 23 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,23 @@
1+
logger.debug "[Cypress] Running db_state #{command_options}"
2+
option = command_options.to_s.downcase
3+
case option
4+
when "capture"
5+
logger.info "[Cypress] DatabaseCleaner capturing"
6+
DatabaseCleaner.start
7+
when "restore"
8+
logger.info "[Cypress] DatabaseCleaner restoring"
9+
DatabaseCleaner.clean
10+
CypressOnRails::SmartFactoryWrapper.reload
11+
12+
if defined?(VCR)
13+
VCR.eject_cassette # make sure we no cassette inserted before the next test starts
14+
VCR.turn_off!
15+
WebMock.disable! if defined?(WebMock)
16+
end
17+
else
18+
message = "Unknown db_state #{option}!"
19+
logger.error "[Cypress] #{message}"
20+
raise message
21+
end
22+
# Explicitly return nil from the script so the cypress on rails middleware doesn't try to parse a response
23+
nil

cypress/e2e/app_commands/eval.rb

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1 @@
1+
Kernel.eval(command_options) unless command_options.nil?
Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,12 @@
1+
Array.wrap(command_options).map do |factory_options|
2+
factory_method = factory_options.shift
3+
begin
4+
logger.debug "running #{factory_method}, #{factory_options}"
5+
CypressOnRails::SmartFactoryWrapper.public_send(factory_method, *factory_options)
6+
rescue => e
7+
logger.error "#{e.class}: #{e.message}"
8+
logger.error e.backtrace.join("\n")
9+
logger.error "#{e.record.inspect}" if e.is_a?(ActiveRecord::RecordInvalid)
10+
raise e
11+
end
12+
end

cypress/e2e/e2e_helper.rb

Lines changed: 42 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,42 @@
1+
# This is loaded once before the first command is executed
2+
3+
begin
4+
require 'database_cleaner-active_record'
5+
rescue LoadError => e
6+
puts e.message
7+
begin
8+
require 'database_cleaner'
9+
rescue LoadError => e
10+
puts e.message
11+
end
12+
end
13+
14+
begin
15+
require 'factory_bot_rails'
16+
rescue LoadError => e
17+
puts e.message
18+
begin
19+
require 'factory_girl_rails'
20+
rescue LoadError => e
21+
puts e.message
22+
end
23+
end
24+
25+
require 'cypress_on_rails/smart_factory_wrapper'
26+
27+
factory = CypressOnRails::SimpleRailsFactory
28+
factory = FactoryBot if defined?(FactoryBot)
29+
factory = FactoryGirl if defined?(FactoryGirl)
30+
31+
# TODO: By default, Factory bot sets definition_file_paths to directories to search for factories:
32+
# https://github.com/thoughtbot/factory_bot/blob/8446cb6c5b39ea046d8ba180197aabc66adf62ed/lib/factory_bot/find_definitions.rb#L10
33+
# Cypress on rails SmartFactoryWrapper is expecting files to be a pattern or a file that when evaluated with Dir[xxx],
34+
# will return files, not a directory:
35+
# https://github.com/shakacode/cypress-playwright-on-rails/blob/abb505c0691c29d5f2a57c2ba28aedbfd43d079e/lib/cypress_on_rails/smart_factory_wrapper.rb#L88
36+
# Therefore, to reuse the existing definition_file_paths, we must convert the directories to glob pattern matches.
37+
require Rails.root.join('spec/support/factory_bot_helper')
38+
CypressOnRails::SmartFactoryWrapper.configure(
39+
always_reload: false,
40+
factory: factory,
41+
files: FactoryBot.definition_file_paths.flat_map { |p| p.directory? ? p.join("**/*.rb") : p }
42+
)

cypress/e2e/ui/Automation/Embedded-Automate/Explorer/class.cy.js

Lines changed: 11 additions & 42 deletions
Original file line numberDiff line numberDiff line change
@@ -1,43 +1,23 @@
11
/* eslint-disable no-undef */
22

33
describe('Automation > Embedded Automate > Explorer', () => {
4-
before(() => {
5-
// Create a Domain and Namespace before all the tests
4+
beforeEach(() => {
5+
cy.appFactories([
6+
['create', 'miq_ae_domain', {name: 'TestDomain'}],
7+
]).then((results) => {
8+
cy.appFactories([
9+
['create', 'miq_ae_namespace', {name: 'TestNameSpace', domain_id: results[0].id}]
10+
])
11+
});
12+
613
cy.login();
714
cy.intercept('POST', '/ops/accordion_select?id=rbac_accord').as('accordion');
815
cy.menu('Automation', 'Embedded Automate', 'Explorer');
916
cy.get('#explorer_title_text');
10-
11-
// Creates a Domain
12-
cy.get('[title="Datastore"]').click();
13-
cy.get('[title="Configuration"]').click();
14-
cy.get('[title="Add a New Domain"]').click();
15-
cy.get('[name="name"]').type('TestDomain', {force: true});
16-
cy.get('[name="description"]').type('This is a test domain');
17-
cy.get('#enabled').check();
18-
cy.get('[class="bx--btn bx--btn--primary"]').contains('Add').click();
19-
20-
// Check for the success message
21-
cy.get('div.alert.alert-success.alert-dismissable').should('exist').and('contain', 'Automate Domain "TestDomain" was added').find('button.close').should('exist');
22-
23-
// Creates a Namespace
24-
cy.get('[title="Datastore"]').click();
25-
cy.get('[title="Automate Domain: TestDomain"]').click(); // Click on Domain
26-
cy.get('[title="Configuration"]').click();
27-
cy.get('[title="Add a New Namespace"]').click();
28-
cy.get('[name="name"]').type('TestNameSpace', {force: true});
29-
cy.get('[name="description"]').type('This is a test NameSpace');
30-
cy.get('.bx--btn--primary').contains('Add').click();
31-
32-
// Wait for namespace to be visible
33-
cy.get('[title="Automate Namespace: TestNameSpace"]', {timeout: 1000}).should('be.visible')
3417
});
3518

36-
beforeEach(() => {
37-
cy.login();
38-
cy.intercept('POST', '/ops/accordion_select?id=rbac_accord').as('accordion');
39-
cy.menu('Automation', 'Embedded Automate', 'Explorer');
40-
cy.get('#explorer_title_text');
19+
afterEach(() => {
20+
cy.appDbState('restore');
4121
});
4222

4323
describe('Class Form', () => {
@@ -268,15 +248,4 @@ describe('Automation > Embedded Automate > Explorer', () => {
268248
cy.get('#ns_details_div > .alert').contains('The selected Namespace is empty');
269249
});
270250
});
271-
272-
after(() => {
273-
// Remove the Domain after all the tests
274-
cy.menu('Automation', 'Embedded Automate', 'Explorer');
275-
cy.get('[title="Datastore"]').click({force: true});
276-
cy.get('[title="Automate Domain: TestDomain"]').click({force: true});
277-
cy.get('[title="Configuration"]').click({force: true});
278-
cy.get('[title="Remove this Domain"]').click({force: true});
279-
280-
cy.get('.bx--data-table-content tbody tr').should('not.contain', 'Automate Domain: TestDomain');
281-
});
282251
});

cypress/e2e/ui/Automation/Embedded-Automate/Explorer/namespace.cy.js

Lines changed: 8 additions & 37 deletions
Original file line numberDiff line numberDiff line change
@@ -122,22 +122,23 @@ function createNamespaceAndOpenEditForm() {
122122

123123
describe('Automate operations on Namespaces: Automation -> Embedded Automate -> Explorer -> {Any-created-domain} -> Namespace form', () => {
124124
beforeEach(() => {
125+
cy.appFactories([
126+
['create', 'miq_ae_domain', {name: DOMAIN_NAME}],
127+
]);
128+
125129
cy.login();
126130
cy.menu(
127131
AUTOMATION_MENU_OPTION,
128132
EMBEDDED_AUTOMATION_MENU_OPTION,
129133
EXPLORER_MENU_OPTION
130134
);
131-
cy.accordion(DATA_STORE_ACCORDION_LABEL);
132-
/* TODO: DATA_SETUP - Refactor to use API for domain data setup */
133-
cy.toolbar(TOOLBAR_CONFIGURATION, TOOLBAR_ADD_NEW_DOMAIN);
134-
addDomainOrNamespace({
135-
nameFieldValue: DOMAIN_NAME,
136-
});
137-
cy.expect_flash(flashClassMap.success, FLASH_MESSAGE_ADD_SUCCESS);
138135
cy.selectAccordionItem([DATA_STORE_ACCORDION_LABEL, DOMAIN_NAME]);
139136
});
140137

138+
afterEach(() => {
139+
cy.appDbState('restore');
140+
});
141+
141142
it('Validate Add Namespace form fields', () => {
142143
cy.toolbar(TOOLBAR_CONFIGURATION, TOOLBAR_ADD_NEW_NAMESPACE);
143144
validateNamespaceFormFields();
@@ -245,34 +246,4 @@ describe('Automate operations on Namespaces: Automation -> Embedded Automate ->
245246
}).click();
246247
cy.expect_flash(flashClassMap.warning, FLASH_MESSAGE_CANCELLED);
247248
});
248-
249-
afterEach(() => {
250-
cy.url()
251-
.then((url) => {
252-
// Ensures navigation to Automation -> Embedded Automate -> Explorer in the UI
253-
if (!url.endsWith(COMPONENT_ROUTE_URL)) {
254-
cy.visit(COMPONENT_ROUTE_URL);
255-
}
256-
cy.accordion(DATA_STORE_ACCORDION_LABEL);
257-
})
258-
.then(() => {
259-
cy.get('div.panel-collapse.collapse.in li.list-group-item').each(
260-
(item) => {
261-
const text = item.text().trim();
262-
if (text === DOMAIN_NAME) {
263-
if (!item.hasClass('node-selected')) {
264-
cy.wrap(item).click();
265-
}
266-
cy.expect_browser_confirm_with_text({
267-
confirmTriggerFn: () =>
268-
cy.toolbar(TOOLBAR_CONFIGURATION, TOOLBAR_REMOVE_DOMAIN),
269-
containsText: BROWSER_CONFIRM_REMOVE_MESSAGE,
270-
});
271-
return false; // exit iteration
272-
}
273-
return null; // has no impact, just to get rid of eslint warning
274-
}
275-
);
276-
});
277-
});
278249
});

cypress/e2e/ui/Settings/Application-Settings/schedule.cy.js

Lines changed: 1 addition & 31 deletions
Original file line numberDiff line numberDiff line change
@@ -534,36 +534,6 @@ describe('Automate Schedule form operations: Settings > Application Settings > S
534534
});
535535

536536
afterEach(() => {
537-
cy.url()
538-
.then((url) => {
539-
// Ensures navigation to Settings -> Application-Settings in the UI
540-
if (!url.endsWith(COMPONENT_ROUTE_URL)) {
541-
cy.visit(COMPONENT_ROUTE_URL);
542-
}
543-
cy.accordion(SETTINGS_OPTION);
544-
})
545-
.then(() => {
546-
// Iterate and clean up any leftover schedules created during the test
547-
cy.get('div.panel-collapse.collapse.in li.list-group-item').each(
548-
(item) => {
549-
const text = item.text().trim();
550-
if (
551-
text === INITIAL_SCHEDULE_NAME ||
552-
text === EDITED_SCHEDULE_NAME
553-
) {
554-
if (!item.hasClass('node-selected')) {
555-
cy.wrap(item).click();
556-
}
557-
cy.expect_browser_confirm_with_text({
558-
confirmTriggerFn: () =>
559-
selectConfigMenu(DELETE_SCHEDULE_CONFIG_OPTION),
560-
containsText: BROWSER_ALERT_DELETE_CONFIRM_TEXT,
561-
});
562-
return false; // exit iteration
563-
}
564-
return null; // has no impact, just to get rid of eslint warning
565-
}
566-
);
567-
});
537+
cy.appDbState('restore');
568538
});
569539
});

0 commit comments

Comments
 (0)