include ApplicationHelper # add a hover_intent method to element, so that you can do find(selector).hover_intent module Capybara module Node class Element def attempt_hover begin hover rescue => e end end def hover_intent hover sleep 0.3 attempt_hover sleep 0.3 attempt_hover end def help_bubble hover end end end end # holds a single test's session name's, mapped to pooled session names $capybara_session_mapper = {} # called in before (or after) test, to make sure each test run has it's own map of session names def reset_session_mapper $capybara_session_mapper.clear Capybara.session_name = :default end # manages the mapped session name def mapped_session_name(session_name) return :default if session_name == :default # special treatment for the built-in session $capybara_session_mapper[session_name] ||= 'session_' + $capybara_session_mapper.length.to_s end # in place of ever using Capybara.session_name directly, # this utility is used to handle the mapping of session names in a way across all tests runs def in_client(name) session_name = name.class == JamRuby::User ? name.id : name Capybara.session_name = mapped_session_name(session_name) yield end def cookie_jar Capybara.current_session.driver.browser.current_session.instance_variable_get(:@rack_mock_session).cookie_jar end #see also ruby/spec/support/utilities.rb JAMKAZAM_TESTING_BUCKET = 'jamkazam-testing' #at least, this is the name given in jam-ruby def wipe_s3_test_bucket s3 = AWS::S3.new(:access_key_id => Rails.application.config.aws_access_key_id, :secret_access_key => Rails.application.config.aws_secret_access_key) test_bucket = s3.buckets[JAMKAZAM_TESTING_BUCKET] if test_bucket.name == JAMKAZAM_TESTING_BUCKET test_bucket.objects.each do |obj| obj.delete end end end def authorize_google_user(youtube_client, user, google_password) youtube_client.wait_for_callback(2112) do |access_token| #puts("Authorizing with token #{access_token}") user_auth_hash = { :provider => "google_login", :uid => user.email, :token => access_token, :token_expiration => nil, :secret => "" } authorization = user.user_authorizations.build(user_auth_hash) authorization.save end url = youtube_client.get_login_url(user.email) #puts("Login URL: #{url}") visit url sleep(1) # save_screenshot("initial.png") # Fill in password (the username is filled in with a hint in URL): # fill_in "Usernm", with: user.email fill_in "Passwd", with: google_password #save_screenshot("password.png") find('#signIn').trigger(:click) # Wait for submit to enable and then click it: sleep(5) #save_screenshot("signin.png") #save_screenshot("submit.png") find('#submit_approve_access').trigger(:click) #save_screenshot("log2.png") sleep(5) #save_screenshot("submitted.png") youtube_client end def sign_in(user) visit signin_path within('#landing-inner form.signin-form') do fill_in "Email", with: user.email fill_in "Password", with: user.password click_button "SIGN IN" end # Sign in when not using Capybara as well. cookie_jar[:remember_token] = user.remember_token end def set_cookie(k, v) case Capybara.current_session.driver when Capybara::Poltergeist::Driver page.driver.set_cookie(k,v) when Capybara::RackTest::Driver headers = {} Rack::Utils.set_cookie_header!(headers,k,v) cookie_string = headers['Set-Cookie'] Capybara.current_session.driver.browser.set_cookie(cookie_string) when Capybara::Selenium::Driver page.driver.browser.manage.add_cookie(:name=>k, :value=>v) else raise "no cookie-setter implemented for driver #{Capybara.current_session.driver.class.name}" end end def sign_in_poltergeist(user, options = {}) validate = options[:validate] validate = true if validate.nil? uri = URI.parse(current_url) # in tests, we often have an issue where an old signin screen is unloading # as this one is loading. # so one way to fix this is to go to a different page in this case, and then come back to /signin if uri.path == signin_path visit '/' should_be_at_root end visit signin_path page.should have_selector('#landing-inner form.signin-form') within('#landing-inner form.signin-form') do fill_in "Email Address:", with: user.email fill_in "Password:", with: user.password click_button "SIGN IN" end page.should have_no_selector('h1', text: 'sign in or register') wait_until_curtain_gone # presence of this means websocket gateway is not working page.should have_no_selector('.no-websocket-connection') if validate end # skip the typical login form, which redirects to /client (slow due to extra login step). # So this just sets the cookie, and puts you where you want to be def fast_signin(user, url) set_login_cookie(user) visit url end #skip the 'hunt' for Sign Out, and redirect after. Just empty cookie, and go to '/' def fast_signout page.driver.set_cookie(:remember_token, '') visit '/' end def set_login_cookie(user) page.driver.set_cookie(:remember_token, user.remember_token) end def sign_out() if Capybara.javascript_driver == :poltergeist page.driver.remove_cookie(:remember_token) else page.driver.browser.manage.remove_cookie :name => :remember_token end end def sign_out_poltergeist(options = {}) open_user_dropdown click_link 'Sign Out' should_be_at_signin if options[:validate] end def open_user_dropdown find('.userinfo').hover() end def go_to_root visit '/' should_be_at_root end def should_be_at_root find('h1', text: 'Play music together over the Internet as if in the same room') end def should_be_at_signin find('h1', text: 'sign in or register') end def leave_music_session_sleep_delay # add a buffer to ensure WSG has enough time to expire sleep_dur = (Rails.application.config.websocket_gateway_connect_time_stale_browser + Rails.application.config.websocket_gateway_connect_time_expire_browser) * 1.4 sleep sleep_dur end def wait_for_ajax(wait=Capybara.default_wait_time) wait = wait * 10 #(because we sleep .1) counter = 0 while page.execute_script("$.active").to_i > 0 counter += 1 sleep(0.1) raise "AJAX request took longer than #{wait} seconds." if counter >= wait end end # waits until the user object has been requested, which comes after the 'curtain' is lifted # and after a call to /api/user/:id for the current user is called initially def wait_until_user(wait=Capybara.default_wait_time) wait = wait * 10 #(because we sleep .1) counter = 0 # while page.execute_script("$('.curtain').is(:visible)") == "true" # counter += 1 # sleep(0.1) # raise "Waiting for user to populate took longer than #{wait} seconds." if counter >= wait # end end def wait_until_curtain_gone page.should have_no_selector('.curtain') end def wait_to_see_my_track within('div.session-mytracks') {first('div.session-track.track')} end def repeat_for(duration=Capybara.default_wait_time) finish_time = Time.now + duration.seconds loop do yield sleep 1 # by default this will execute the block every 1 second break if (Time.now > finish_time) end end def determine_test_name(metadata, test_name_buffer = '') description = metadata[:description_args] if description.kind_of?(Array) description = description[0] end if metadata.has_key? :example_group return determine_test_name(metadata[:example_group], "#{description} #{test_name_buffer}") else return "#{description} #{test_name_buffer}" end end def get_description description = example.metadata[:description_args] if description.kind_of?(Array) description = description[0] end return description end # will select the value from a easydropdown'ed select element def jk_select(text, select) # the approach here is to find the hidden select element, and work way back up to the elements that need to be interacted with find(select, :visible => false).find(:xpath, 'ancestor::div[contains(@class, "dropdown easydropdown")]').trigger(:click) find(select, :visible => false).find(:xpath, 'ancestor::div[contains(@class, "dropdown-wrapper") and contains(@class, "easydropdown-wrapper") and contains(@class, "open")]').find('li', text: text).trigger(:click) # works, but is 'cheating' because of visible = false #select(genre, :from => 'genres', :visible => false) end # takes, or creates, a unique session description which is returned for subsequent calls to join_session to use # in finding this session) def create_session(options={}) creator = options[:creator] || FactoryGirl.create(:user) unique_session_name = options[:name] || "create_join_session #{SecureRandom.urlsafe_base64}" unique_session_desc = options[:description] || "create_join_session #{SecureRandom.urlsafe_base64}" genre = options[:genre] || 'Alternative Rock' musician_access = options[:musician_access].nil? ? true : options[:musician_access] approval_required = options[:approval_required].nil? ? true : options[:approval_required] fan_access = options[:fan_access].nil? ? true : options[:fan_access] fan_chat = options[:fan_chat].nil? ? false : options[:fan_chat] # create session in one client in_client(creator) do page.driver.resize(1500, 800) # makes sure all the elements are visible emulate_client fast_signin(creator, "/client#/createSession") expect(page).to have_selector('h1', text: 'create session') within('#create-session-form') do # step 1 find('li[create-type="immediately"] ins').trigger(:click) find('.btn-next').trigger(:click) # step 2 jk_select(genre, '#create-session-form select[name="genres"]') fill_in('session-name', :with => unique_session_name) fill_in('session-description', :with => unique_session_desc) find('.btn-next').trigger(:click) # step 3 find('.btn-next').trigger(:click) # step 4 musician_access_value = "Musicians may join by approval" if !musician_access && !approval_required musician_access_value = "Only RSVP musicians may join" elsif musician_access && approval_required musician_access_value = "Musicians may join at will" end jk_select(musician_access_value, '#session-musician-access') fan_access_value = "Fans may listen, chat with each other" if !fan_access && !fan_chat fan_access_value = 'Fans may not listen to session' elsif fan_access && fan_chat fan_access_value = 'Fans may listen, chat with the band' #XXX this option is currently disabled/not available end jk_select(fan_access_value, '#session-fans-access') find('#divSessionPolicy ins').trigger(:click) find('.btn-next').trigger(:click) # step 5 find('.btn-next').trigger(:click) end # verify that the in-session page is showing expect(page).to have_selector('h2', text: 'my tracks') find('#session-screen .session-mytracks .session-track') end return creator, unique_session_desc, genre end def schedule_session(options = {}) creator = options[:creator] || FactoryGirl.create(:user) unique_session_name = options[:name] || "schedule_session #{SecureRandom.urlsafe_base64}" unique_session_desc = options[:description] || "schedule_session #{SecureRandom.urlsafe_base64}" genre = options[:genre] || 'Alternative Rock' musician_access = options[:musician_access].nil? ? true : options[:musician_access] approval_required = options[:approval_required].nil? ? true : options[:approval_required] fan_access = options[:fan_access].nil? ? true : options[:fan_access] fan_chat = options[:fan_chat].nil? ? false : options[:fan_chat] musician_access_value = 'Musicians may join by approval' fan_permission_value = 'Fans may listen, chat with each other' rsvp = options[:rsvp] immediate = options[:immediate] quickstart = options[:quickstart] if musician_access && !approval_required musician_access_value = 'Musicians may join at will' elsif !musician_access && !approval_required musician_access_value = 'Only RSVP musicians may join' end if fan_access && fan_chat fan_permission_value = 'Fans may listen, chat with the band' elsif !fan_access && !fan_chat fan_permission_value = 'Fans may not listen to session' end # schedule a session in one client in_client(creator) do page.driver.resize(1500, 800) # makes sure all the elements are visible emulate_client fast_signin(creator, "/client#/createSession") expect(page).to have_selector('h1', text: 'create session') within('#create-session-form') do if rsvp find('li[create-type="rsvp"] ins').trigger(:click) elsif immediate find('li[create-type="immediately"] ins').trigger(:click) elsif quickstart find('li[create-type="quick-start"] ins').trigger(:click) else find('li[create-type="schedule-future"] ins').trigger(:click) end find('.btn-next').trigger(:click) unless quickstart jk_select(genre, '#create-session-form select[name="genres"]') fill_in('session-name', :with => unique_session_name) fill_in('session-description', :with => unique_session_desc) find('.btn-next').trigger(:click) find('.btn-next').trigger(:click) find('div#divSessionPolicy ins').trigger(:click) jk_select(musician_access_value, '#session-musician-access') jk_select(fan_permission_value, '#session-fans-access') find('.btn-next').trigger(:click) end unless quickstart || immediate find('.btn-next', text: 'PUBLISH SESSION').trigger(:click) else find('.btn-next', text: 'START SESSION').trigger(:click) end end # find('h2', text: 'create session') unless quickstart || immediate sleep 1 # to get rid of this, we need to verify that the URL is /client#/home.. otherwise intermittent fails end return creator, unique_session_name, genre end # this code assumes that there are no music sessions in the database. it should fail on the # find('.join-link') call if > 1 session exists because capybara will complain of multiple matches def join_session(joiner, options) description = options[:description] in_client(joiner) do page.driver.resize(1500, 800) # makes sure all the elements are visible emulate_client fast_signin(joiner, "/client#/findSession") # verify the session description is seen by second client expect(page).to have_text(description) find('.join-link').trigger(:click) unless options[:no_verify] find('#btn-accept-terms').trigger(:click) expect(page).to have_selector('h2', text: 'my tracks') find('#session-screen .session-mytracks .session-track') end end end def request_to_join_session(joiner, options) join_session(joiner, options.merge(no_verify: true)) find('#btn-alert-ok').trigger(:click) # page.should have_no_selector('h1', text: 'Alert') end def emulate_client page.driver.headers = { 'User-Agent' => ' JamKazam ' } end def create_join_session(creator, joiners=[], options={}) options[:creator] = creator creator, unique_session_desc = create_session(options) # find session in second client [*joiners].each do |joiner| join_session(joiner, description: unique_session_desc) end return creator, unique_session_desc end def formal_leave_by user in_client(user) do find('#session-leave').trigger(:click) #find('#btn-accept-leave-session').trigger(:click) expect(page).to have_selector('h2', text: 'feed') end end def verify_feed_shows_users *users users = [*users] visit "/client#/feed" find('.feed-details a.details').trigger(:click) within 'div.music-session-history-entry' do users.each do |user| # confirm user avatar exists find("a.avatar-tiny[user-id=\"#{user.id}\"][hoveraction=\"musician\"] img") # confirm user name exists find("a.musician-name[user-id=\"#{user.id}\"][hoveraction=\"musician\"]", text: user.name) end end end def start_recording_with(creator, joiners=[], genre=nil) create_join_session(creator, joiners, {genre: genre}) in_client(creator) do find('#recording-start-stop').trigger(:click) find('#recording-status').should have_content 'Stop Recording' end joiners.each do |joiner| in_client(joiner) do find('#notification').should have_content 'started a recording' find('#recording-status').should have_content 'Stop Recording' end end end def stop_recording find('#recording-start-stop').trigger(:click) end def assert_recording_finished find('#recording-status').should have_content 'Make a Recording' should have_selector('h1', text: 'recording finished') end def check_recording_finished_for(users=[]) users.each do |user| in_client(user) do assert_recording_finished end end end def claim_recording(name, description) find('#recording-finished-dialog h1') fill_in "claim-recording-name", with: name fill_in "claim-recording-description", with: description find('#keep-session-recording').trigger(:click) page.should have_no_selector('h1', text: 'recording finished') end def set_session_access access_type case access_type when :only_rsvp, :private access_level = "Only RSVP musicians may join" when :by_approval access_level = "Musicians may join by approval" when :at_will, :public, :open access_level = "Musicians may join at will" else access_level = "Musicians may join at will" end find('#session-settings-button').trigger(:click) within('#session-settings-dialog') do jk_select(access_level, '#session-settings-dialog #session-settings-musician-access') find('#session-settings-dialog-submit').trigger(:click) end # verify it's dismissed page.should have_no_selector('h1', text: 'update session settings') end def get_options(selector) find(selector, :visible => false).all('option', :visible => false).collect(&:text).uniq end def selected_genres(selector='#session-settings-genre') page.evaluate_script("JK.GenreSelectorHelper.getSelectedGenres('#{selector}')") end def random_genre ['African', 'Ambient', 'Asian', 'Blues', 'Classical', 'Country', 'Electronic', 'Folk', 'Hip Hop', 'Jazz', 'Latin', 'Metal', 'Pop', 'R&B', 'Reggae', 'Religious', 'Alternative Rock', 'Ska', 'Other'].sample end def change_session_genre #randomly just change it here = 'select.genre-list' #wait_for_ajax find('#session-settings-button').trigger(:click) find('#session-settings-dialog') # ensure the dialog is visible within('#session-settings-dialog') do wait_for_ajax @new_genre = get_options(here).-(["Select Genre", "Unspecified"]).-(selected_genres).sample.to_s jk_select(@new_genre, '#session-settings-dialog select[name="genres"]') wait_for_ajax find('#session-settings-dialog-submit').trigger(:click) end return @new_genre end def get_session_genre here = 'select.genre-list' find('#session-settings-button').trigger(:click) wait_for_ajax @current_genres = selected_genres find('#session-settings-dialog-submit').trigger(:click) return @current_genres.join(" ") end def find_session_contains?(text) visit "/client#/findSession" wait_for_ajax within('#find-session-form') do expect(page).to have_text(text) end end def assert_all_tracks_seen(users=[]) users.each do |user| in_client(user) do users.reject {|u| u==user}.each do |other| find('div.track-label > span', text: other.name) #puts user.name + " is able to see " + other.name + "\'s track" end end end end def view_profile_of user id = user.kind_of?(JamRuby::User) ? user.id : user # assume some user signed in already visit "/client#/profile/#{id}" wait_until_curtain_gone end def view_band_profile_of band id = band.kind_of?(JamRuby::Band) ? band.id : band.kind_of?(JamRuby::BandMusician) ? band.bands.first.id : band visit "/client#/bandProfile/#{id}" wait_until_curtain_gone end def sidebar_search_for string, category visit "/client#/home" find('#search-input') fill_in "search", with: string sleep 1 page.execute_script("JK.Sidebar.searchForInput()") wait_for_ajax jk_select(category, "search_text_type") wait_for_ajax end def expand_sidebar header_name #search, friends, chat, notifications panel_id = "panel#{header_name.to_s.capitalize}" within("div[layout-id='#{panel_id}']") { find('div.panel-header').trigger(:click) } end def show_user_menu page.execute_script("$('ul.shortcuts').show()") #page.execute_script("JK.UserDropdown.menuHoverIn()") end # wait for the easydropdown version of the specified select element to become visible def wait_for_easydropdown(select) find(select, :visible => false).find(:xpath, 'ancestor::div[contains(@class, "dropdown easydropdown")]') end # defaults to enter key (13) def send_key(selector, keycode = 13) keypress_script = "var e = $.Event('keyup', { keyCode: #{keycode} }); jQuery('#{selector}').trigger(e);" page.driver.execute_script(keypress_script) end def special_characters ["?", "[", "]", "/", "\\", "=", "<", ">", ":", ";", ",", "'", "\"", "&", "$", "#", "*", "(", ")", "|", "~", "`", "!", "{", "}"] end def garbage length output = '' length.times { output << special_characters.sample } output.slice(0, length) end def nav_profile_history(user) visit Nav.profile(user) find('#profile-history-link').trigger(:click) end