From b858cba8ca3d25ca5cbad3a65b81e557b935728a Mon Sep 17 00:00:00 2001 From: kinezu Date: Thu, 29 Oct 2015 15:16:47 -0600 Subject: [PATCH] Taking appium mobile specs out of the canvas-lms project They now live in their own private repository. Change-Id: Id4e9e3f745c1311fc75f99bb62333af44b90f145 Reviewed-on: https://gerrit.instructure.com/66125 Tested-by: Jenkins Reviewed-by: Taylor Wilson Product-Review: Derek Hansen QA-Review: Derek Hansen --- Gemfile.d/test.rb | 1 - config/appium.yml.example | 16 -- .../candroid/bookmarks/bookmarks_common.rb | 67 ----- .../candroid/bookmarks/bookmarks_spec.rb | 106 ------- .../conversations/conversations_common.rb | 125 -------- .../candroid/conversations/inbox_spec.rb | 79 ----- .../general/landing_page/landing_page_spec.rb | 10 - .../candroid/general/login/login_spec.rb | 11 - .../appium/android/helpers/android_common.rb | 144 ---------- .../android/helpers/landing_page_common.rb | 174 ----------- .../appium/android/helpers/login_common.rb | 86 ------ .../general/landing_page/landing_page_spec.rb | 10 - .../speedgrader/general/login/login_spec.rb | 11 - spec/selenium/appium/environment_setup.rb | 271 ------------------ .../selenium/appium/ios/helpers/ios_common.rb | 140 --------- .../appium/ios/helpers/landing_page_common.rb | 106 ------- .../appium/ios/helpers/login_common.rb | 147 ---------- .../general/landing_page/landing_page_spec.rb | 8 - .../ios/icanvas/general/login/login_spec.rb | 12 - .../general/landing_page/landing_page_spec.rb | 8 - .../speedgrader/general/login/login_spec.rb | 12 - spec/selenium/appium/mobile_common.rb | 233 --------------- spec/spec_helper.rb | 3 - 23 files changed, 1780 deletions(-) delete mode 100644 config/appium.yml.example delete mode 100644 spec/selenium/appium/android/candroid/bookmarks/bookmarks_common.rb delete mode 100644 spec/selenium/appium/android/candroid/bookmarks/bookmarks_spec.rb delete mode 100644 spec/selenium/appium/android/candroid/conversations/conversations_common.rb delete mode 100644 spec/selenium/appium/android/candroid/conversations/inbox_spec.rb delete mode 100644 spec/selenium/appium/android/candroid/general/landing_page/landing_page_spec.rb delete mode 100644 spec/selenium/appium/android/candroid/general/login/login_spec.rb delete mode 100644 spec/selenium/appium/android/helpers/android_common.rb delete mode 100644 spec/selenium/appium/android/helpers/landing_page_common.rb delete mode 100644 spec/selenium/appium/android/helpers/login_common.rb delete mode 100644 spec/selenium/appium/android/speedgrader/general/landing_page/landing_page_spec.rb delete mode 100644 spec/selenium/appium/android/speedgrader/general/login/login_spec.rb delete mode 100644 spec/selenium/appium/environment_setup.rb delete mode 100644 spec/selenium/appium/ios/helpers/ios_common.rb delete mode 100644 spec/selenium/appium/ios/helpers/landing_page_common.rb delete mode 100644 spec/selenium/appium/ios/helpers/login_common.rb delete mode 100644 spec/selenium/appium/ios/icanvas/general/landing_page/landing_page_spec.rb delete mode 100644 spec/selenium/appium/ios/icanvas/general/login/login_spec.rb delete mode 100644 spec/selenium/appium/ios/speedgrader/general/landing_page/landing_page_spec.rb delete mode 100644 spec/selenium/appium/ios/speedgrader/general/login/login_spec.rb delete mode 100644 spec/selenium/appium/mobile_common.rb diff --git a/Gemfile.d/test.rb b/Gemfile.d/test.rb index b5d907ce45d..369f3e6050f 100644 --- a/Gemfile.d/test.rb +++ b/Gemfile.d/test.rb @@ -29,7 +29,6 @@ group :test do gem 'childprocess', '0.5.0', require: false gem 'websocket', '1.0.7', require: false gem 'selinimum', '0.0.1', require: false, path: 'gems/selinimum' - gem 'appium_lib', '7.0.0', require: false gem 'test_after_commit', '0.4.0' gem 'test-unit', '~> 3.0', require: false, platform: :ruby_22 gem 'webmock', '1.16.1', require: false diff --git a/config/appium.yml.example b/config/appium.yml.example deleted file mode 100644 index f38a1fab08c..00000000000 --- a/config/appium.yml.example +++ /dev/null @@ -1,16 +0,0 @@ -test: - host: "localhost" - port: 4444 - browser: "firefox" - school_domain: - # URL to Canvas environment - # "twilson" - appium_host_url: - # setting path for test server - # "10.0.15.242" - # iOS device settings - ios_version: "8.4" - ios_device_name: "iPad" - ios_udid: "Put the unique device id here" - ios_type: "iPad" - ios_app_path: "Put the setting path for icanvas here" diff --git a/spec/selenium/appium/android/candroid/bookmarks/bookmarks_common.rb b/spec/selenium/appium/android/candroid/bookmarks/bookmarks_common.rb deleted file mode 100644 index 3e80b541ce7..00000000000 --- a/spec/selenium/appium/android/candroid/bookmarks/bookmarks_common.rb +++ /dev/null @@ -1,67 +0,0 @@ -require_relative '../../helpers/android_common' - -def bookmark_original_label - 'goto_Course_Grades' -end - -def bookmark_edited_label - 'my_Grades' -end - -def bookmark_routing_target - 'Grades' -end - -def click_add_bookmark - find_ele_by_attr('tag', 'android.widget.ImageView', 'name', /(More options)/).click - text_exact('Add Bookmark').click -end - -# Multiple elements may exist with the same resource id. -# This chooses the element which is vertically aligned with the bookmark text. -def get_more_options(bookmark) - ids('overflowRipple').each do |more_options| - return more_options if more_options.location.y <= bookmark.location.y + bookmark.size.height / 4 - end - raise('Unable to find more options button for bookmark.') -end - -# Selects either 'Edit' or 'Delete' option on a given bookmark object -def click_bookmark_option(bookmark, option) - navigate_to('Bookmarks') unless exists{ text_exact('Bookmarks') } - get_more_options(bookmark).click - if option == 'Edit' || option == 'Delete' - text_exact(option).click - else - raise('Unsupported bookmark feature') - end -end - -# When deleting a bookmark, the user may decide to cancel the deletion. -# This provides two ways to cancel, press 'back' or tap 'No' -def cancel_delete(bookmark, option) - click_bookmark_option(bookmark, 'Delete') - if option == 'No' - text_exact('No').click - elsif option == 'Back' - back - else - raise('Unsupported bookmark feature') - end - expect(exists{ text_exact('Remove Bookmark?') }).to be false -end - -def verify_bookmark(bookmark_title) - navigate_to('Bookmarks') - wait_true(timeout: 10, interval: 0.100){ text_exact('Bookmarks') } - - # Check routing - find_ele_by_attr('id', 'title', 'text', /(#{bookmark_title})/).click - expect(exists(1){ text_exact(bookmark_routing_target) }).to be true - expect(exists{ text_exact('Bookmarks') }).to be false - - # Check back-stack, returns to 'Bookmarks' - back - expect(exists(1){ text_exact(bookmark_routing_target) }).to be false - expect(exists{ text_exact('Bookmarks') }).to be true -end \ No newline at end of file diff --git a/spec/selenium/appium/android/candroid/bookmarks/bookmarks_spec.rb b/spec/selenium/appium/android/candroid/bookmarks/bookmarks_spec.rb deleted file mode 100644 index 760b419a51c..00000000000 --- a/spec/selenium/appium/android/candroid/bookmarks/bookmarks_spec.rb +++ /dev/null @@ -1,106 +0,0 @@ -require_relative 'bookmarks_common' - -describe 'bookmarks and internal routing' do - include_context 'in-process server appium tests' - include_context 'appium mobile specs', 'candroid' - include_context 'course with a single user', 'student', 'candroid' - - # uses *bookmark_original_label* for creating a new bookmark - # uses *bookmark_edited_label* for editing and deleting bookmarks - context 'navigated to a page that can be bookmarked' do - before(:each) do - navigate_to('Course_Grades') - click_add_bookmark - end - - it 'displays new bookmark view', priority: "1", test_id: 369240 do - expect(text_exact('Add Bookmark')).to be_truthy - expect(textfield_exact('Label')).to be_truthy - expect(text_exact('Cancel')).to be_truthy - expect(text_exact('Done')).to be_truthy - text_exact('Cancel').click - end - - it 'does not create invalid bookmarks', priority: "1", test_id: 369241 do - # A label was not entered; bookmarks without a label are invalid - find_element(:id, 'buttonDefaultPositive').click - - # Bookmarks page displays 'Create a bookmark' if user has no bookmarks - navigate_to('Bookmarks') - expect(text('Create a bookmark')).to be_truthy - back # TODO: remove *back* when new navigation framework is complete - end - - it 'creates a new bookmark to course grades', priority: "1", test_id: 208761 do - find_element(:id, 'bookmarkEditText').send_keys(bookmark_original_label) - find_element(:id, 'buttonDefaultPositive').click - verify_bookmark(bookmark_original_label) - end - end - - context 'navigated to bookmarks page' do - before(:each) do - navigate_to('Bookmarks') - end - - it 'displays bookmark options', priority: "1", test_id: 369242 do - # Tap the vertical ellipsis to display 'Edit' and 'Delete' options - get_more_options(text_exact(bookmark_original_label)).click - expect(text_exact('Edit')).to be_truthy - expect(text_exact('Delete')).to be_truthy - - # Close the Bookmark Options by tapping 'Back Button' - back - end - - it 'displays a bookmark edit view', priority: "1", test_id: 369243 do - # Tap the vertical ellipsis to display 'Edit' and 'Delete' options - click_bookmark_option(text_exact(bookmark_original_label), 'Edit') - expect(find_element(:id, 'title').text).to eq('Edit Bookmark') - expect(find_element(:id, 'bookmarkEditText').text).to eq(bookmark_original_label) - expect(text_exact('Done')).to be_truthy - - # Close the Bookmark Options by tapping 'Back Button' - back - end - - it 'displays a bookmark delete view', priority: "1", test_id: 369244 do - # Tap the vertical ellipsis to display 'Edit' and 'Delete' options - click_bookmark_option(text_exact(bookmark_original_label), 'Delete') - expect(find_element(:id, 'title').text).to eq('Remove Bookmark?') - expect(find_element(:id, 'content').text).to eq(bookmark_original_label) - expect(text_exact('No')).to be_truthy - expect(text_exact('Yes')).to be_truthy - - # Close the Bookmark Options by tapping 'Back Button' - back - end - - it 'edits the bookmark label', priority: "1", test_id: 209406 do - click_bookmark_option(text_exact(bookmark_original_label), 'Edit') - - # Make the edit - find_element(:id, 'bookmarkEditText').send_keys(bookmark_edited_label) - expect(find_element(:id, 'bookmarkEditText').text).to eq(bookmark_edited_label) - text_exact('Done').click - expect(exists{ text_exact(bookmark_original_label) }).to be false - verify_bookmark(bookmark_edited_label) - end - - it 'closes the \'Remove Bookmark?\' dialogue', priority: "1", test_id: 369245 do - cancel_delete(text_exact(bookmark_edited_label), 'No') - verify_bookmark(bookmark_edited_label) - end - - it 'closes the \'Remove Bookmark?\' dialogue when using back button', priority: "1", test_id: 369246 do - cancel_delete(text_exact(bookmark_edited_label), 'Back') - verify_bookmark(bookmark_edited_label) - end - - it 'deletes the bookmark', priority: "1", test_id: 209422 do - click_bookmark_option(text_exact(bookmark_edited_label), 'Delete') - text_exact('Yes').click - expect(exists{ text_exact(bookmark_edited_label) }).to be false - end - end -end diff --git a/spec/selenium/appium/android/candroid/conversations/conversations_common.rb b/spec/selenium/appium/android/candroid/conversations/conversations_common.rb deleted file mode 100644 index 48a72022b31..00000000000 --- a/spec/selenium/appium/android/candroid/conversations/conversations_common.rb +++ /dev/null @@ -1,125 +0,0 @@ -require_relative '../../helpers/android_common' - -# types a single character until auto-fill generates target recipient -# then selects auto-populated recipient -def auto_populate_recipient(name) - email_field = find_element(:id, 'recipient') - substring = '' - name.each_char do |char| - substring += char - email_field.send_keys(substring) - - # auto-populated recipients will appear immediately below the email textfield object - # tap the location where recipient will appear - action = Appium::TouchAction.new.tap(x: 0.5 * window_size.width, - y: (email_field.location.y + email_field.size.height) + (0.5 * email_field.size.height)) - action.perform - - # if recipient was auto-populated, the tap should have selected them - # check if email textfield reflects a successful auto-population; keep trying if unsuccessful - if email_field.text =~ recipient_list_matcher - # auto-population on last character of input string does not count... fail - return true unless char == name[-1, 1] - break - end - end - return false -end - -# clicking compose button, for unknown reason, FAILS -# this method attempts to open the form several times before giving up -# this usually works on the second attempt -def click_object(obj) - if obj == 'compose' - click_compose_button - elsif obj == 'sent' - click_sent_tab - else - raise('Unsupported option for click_object. Expecting \'compose\' or \'sent\'') - end -end - -def click_compose_button - attempts = 0 - loop do - if find_ele_by_attr('tag', 'android.widget.ImageButton', 'name', /(Navigate up)/) != nil - break - elsif (attempts += 1) > 5 - raise('Unable to open compose message form.') - else - find_element(:id, 'compose').click - end - end -end - -def click_sent_tab - attempts = 0 - loop do - if find_ele_by_attr('tag', 'android.widget.TextView', 'text', /(Sent)/).selected? - break - elsif (attempts += 1) > 5 - raise('Unable to open sent tab.') - else - text_exact('Sent').click - end - end -end - -def enter_subject(subject) - subject_field = find_element(:id, 'subject') - subject_field.send_keys(subject) -end - -def enter_message(message) - message_field = find_element(:id, 'message') - message_field.send_keys(message) -end - -# only matches for a single recipient; does not match multiple recipients -def recipient_list_matcher - return /(^<#{recipient}>)(,.)$/ -end - -def send_message(recipient, recipient_role, subject, message) - select_recipient_course - select_recipient_from_menu(recipient, recipient_role) - enter_subject(subject) - enter_message(message) - find_element(:id, 'menu_send').click -end - -def select_recipient_course - find_element(:id, 'course_spinner').click - text_exact(@course.name).click -end - -def select_recipient_from_menu(recipient, recipient_role) - # possible recipients are organized by user role - find_element(:id, 'menu_choose_recipients').click - select_user_group(recipient_role) - text_exact(recipient).click - find_element(:id, 'menu_done').click -end - -def select_user_group(group) - case group - when 'teacher' - text_exact('Teachers').click - when 'ta' - text_exact('Teaching Assistants').click - when 'student' - text_exact('Students').click - when 'observer' - text_exact('Observers').click - else - raise('Invalid user group selected.') - end -end - -def student_subject - 'Final Grades' -end - -def student_message - 'Will I pass this class?' -end diff --git a/spec/selenium/appium/android/candroid/conversations/inbox_spec.rb b/spec/selenium/appium/android/candroid/conversations/inbox_spec.rb deleted file mode 100644 index 43805fda661..00000000000 --- a/spec/selenium/appium/android/candroid/conversations/inbox_spec.rb +++ /dev/null @@ -1,79 +0,0 @@ -require_relative 'conversations_common' - -describe 'conversations inbox' do - include_context 'in-process server appium tests' - include_context 'appium mobile specs', 'candroid' - include_context 'teacher and student users', 'candroid' - - context 'student as the sender' do - let(:sender){ @student.primary_pseudonym.unique_id } - let(:recipient){ @teacher.primary_pseudonym.unique_id } - let(:recipient_role){ 'teacher' } - - before(:all) do - android_app_init(@student.primary_pseudonym.unique_id, user_password(@student), @course.name) - end - - before(:each) do - navigate_to('Inbox') - click_object('compose') - end - - after(:each) do - # use this over *back*; this will clear out previously entered recipients, *back* will not - find_ele_by_attr('tag', 'android.widget.ImageButton', 'name', /(Navigate up)/).click - end - - after(:all) do - logout(false) - end - - # taps the compose button (buttom right) and verifies a new message form is displayed - it 'has a working compose button', priority: "1", test_id: 18399 do - expect(find_ele_by_attr('tag', 'android.widget.ImageButton', 'name', /(Navigate up)/)).to be_displayed - expect(text_exact('Compose Message')).to be_displayed - expect(find_element(:id, 'menu_send')).to be_displayed - expect(text_exact('Select a course')).to be_displayed - expect(find_element(:id, 'subject')).to be_displayed - expect(text_exact('Compose Message')).to be_displayed - end - - # uses course menu dropdown rather than manually typing the course - it 'selects a course', priority: "1", test_id: 220022 do - expect(exists{ find_element(:id, 'menu_choose_recipients') }).to be false - select_recipient_course - expect(exists{ find_element(:id, 'menu_choose_recipients') }).to be true - end - - it 'adds recipient using recipient menu', priority: "1", test_id: 220024 do - select_recipient_course - select_recipient_from_menu(recipient, recipient_role) - - # actual text of recipient field encapsulates entries with "< >" - expect(find_element(:id, 'recipient').text).to match(recipient_list_matcher) - end - - it 'adds recipient with auto-populated response', priority: "1", test_id: 18400 do - select_recipient_course - expect(auto_populate_recipient(recipient)).to be true - end - - it 'enters a subject line', priority: "1", test_id: 18401 do - expect(find_element(:id, 'subject').text).to eq('Subject') - enter_subject(student_subject) - expect(find_element(:id, 'subject').text).to eq(student_subject) - end - - it 'enters a message body', priority: "1", test_id: 369247 do - expect(find_element(:id, 'message').text).to eq('Compose Message') - enter_message(student_message) - expect(find_element(:id, 'message').text).to eq(student_message) - end - - it 'sends a message', priority: "1", test_id: 18403 do - # sending the email closes the form and displays the inbox view - send_message(recipient, recipient_role, student_subject, student_message) - expect(find_element(:id, 'compose')).to be_displayed - end - end -end diff --git a/spec/selenium/appium/android/candroid/general/landing_page/landing_page_spec.rb b/spec/selenium/appium/android/candroid/general/landing_page/landing_page_spec.rb deleted file mode 100644 index 43d265d41ae..00000000000 --- a/spec/selenium/appium/android/candroid/general/landing_page/landing_page_spec.rb +++ /dev/null @@ -1,10 +0,0 @@ -require_relative '../../../helpers/landing_page_common' - -describe 'candroid landing page' do - include_context 'in-process server appium tests' - include_context 'appium mobile specs', 'candroid' - let(:default_url){ 'Find your school or district' } - - # examples located in: spec/selenium/appium/android/helpers/landing_page_common.rb - it_behaves_like 'candroid and speedgrader landing page', 'candroid' -end diff --git a/spec/selenium/appium/android/candroid/general/login/login_spec.rb b/spec/selenium/appium/android/candroid/general/login/login_spec.rb deleted file mode 100644 index 05445dc4f9c..00000000000 --- a/spec/selenium/appium/android/candroid/general/login/login_spec.rb +++ /dev/null @@ -1,11 +0,0 @@ -require_relative '../../../helpers/login_common' - -describe 'candroid login credentials' do - include_context 'in-process server appium tests' - include_context 'appium mobile specs', 'candroid' - let(:app_login_message){ /(Canvas for Android)/ } - let(:app_access_message){ /Canvas for Android is requesting access.*/ } - - # examples located in: spec/selenium/appium/android/helpers/login_common.rb - it_behaves_like 'login credentials for candroid and speedgrader', 'candroid' -end diff --git a/spec/selenium/appium/android/helpers/android_common.rb b/spec/selenium/appium/android/helpers/android_common.rb deleted file mode 100644 index 70455ea9c19..00000000000 --- a/spec/selenium/appium/android/helpers/android_common.rb +++ /dev/null @@ -1,144 +0,0 @@ -require_relative '../../mobile_common' - -# ====================================================================================================================== -# Log In / Out of Mobile App -# ====================================================================================================================== - -def android_app_init(username, password, course_name) - # check for multi-user access - if (userlink = find_ele_by_attr('id', 'name', 'text', /#{username}/)).nil? - enter_school - login_mobile(username, password) - navigate_to_course(course_name) - else - userlink.click - end -end - -def enter_school - find_element(:id, 'enterURL').send_keys(@school) - find_element(:id, 'connect').click - - # blocks until 1st login view loads - wait_true(timeout: 10, interval: 0.250){ button_exact('Log In') } -end - -def provide_credentials(username, password) - first_textfield.send_keys(username) - last_textfield.send_keys(password) - button('Log in').click -end - -def login_mobile(username, password) - # 1st login view - provide_credentials(username, password) - - # blocks until 2nd login view loads - wait_true(timeout: 10, interval: 0.250){ find_ele_by_attr('tags', 'android.view.View', 'name', /Cancel/) } - button('Log In').click - skip_tutorial -end - -def logout(add_account) - candroid_app ? logout_android(add_account) : logout_speedgrader -end - -def logout_android(add_account) - open_hamburger - find_element(:id, 'userNameContainer').click unless exists{ find_element(:id, 'logout') } - if add_account - find_element(:id, 'addAccount').click - else - find_element(:id, 'logout').click - find_element(:id, 'dialog_custom_confirm').click - end - wait_true(timeout: 10, interval: 0.100){ find_element(:id, 'enterURL') } -end - -def logout_speedgrader - find_ele_by_attr('tag', 'android.widget.ImageButton', 'name', /(Open Drawer)/).click - find_element(:id, 'logoutText').click -end - -def skip_tutorial - # Getting Started screen takes a second to animate in and out - find_element(:id, 'skip').click if exists(3){ find_element(:id, 'skip') } - if candroid_app - wait_true(timeout:10, interval: 0.100){ find_element(:id, 'toolbar') } - else - wait_true(timeout:10, interval: 0.100){ find_element(:id, 'courseSwitcher') } - end -end - -# ====================================================================================================================== -# Navigation -# ====================================================================================================================== - -# TODO: add support for speedgrader -def navigate_to_course(course_name) - if candroid_app - tags('android.widget.ImageButton')[0].click unless exists{ find_element(:id, 'scrollview') } - find_element(:id, 'courses').click - text_exact(course_name).click - end -end - -def navigate_to(location) - case location - when 'Bookmarks', 'Grades', 'Inbox' - open_hamburger - scroll_vertically_in_view(find_element(:id, 'scrollview'), 2000, 'down') unless exists{ text_exact(location) } - else - # location needs to differentiate between course grades in the navigation menu and grades in scroll view - location = 'Grades' if location == 'Course_Grades' - wait_true(timeout: 10, interval: 0.100){ find_navigation_indicator.click } - list_view = tag('android.widget.ListView') - scroll_vertically_in_view(list_view, 2000, 'down') unless exists{ text_exact(location) } - end - text_exact(location).click -end - -def find_navigation_indicator - return find_element(:id, 'indicator') if exists{ find_element(:id, 'indicator') } - return find_element(:id, 'arrow') if exists{ find_element(:id, 'arrow') } -end - -def open_hamburger - # hamburger will always be the first image view returned - tag('android.widget.ImageButton').click unless exists{ find_element(:id, 'scrollview') } -end - -# ====================================================================================================================== -# General -# ====================================================================================================================== - -def find_ele_by_attr(type, id, attribute, regex) - elements = type == 'id' ? ids(id) : tags(id) - elements.each do |element| - case attribute - when 'text' - return element if element.text =~ regex - when 'name' - return element if element.name =~ regex - else - raise('unsupported option for finding element') - end - end - nil -end - -def press_keycodes(text) - text.each_byte do |code| - if code == 32 - press_keycode(62) # space - else - press_keycode(code - 68) - end - end -end - -def wait_for_super_panda - loop do - break unless exists(0.250){ find_element(:id, 'pandaLoading') } - end -end diff --git a/spec/selenium/appium/android/helpers/landing_page_common.rb b/spec/selenium/appium/android/helpers/landing_page_common.rb deleted file mode 100644 index 0b5da4e92bf..00000000000 --- a/spec/selenium/appium/android/helpers/landing_page_common.rb +++ /dev/null @@ -1,174 +0,0 @@ -require_relative 'android_common' - -# ====================================================================================================================== -# Shared Examples for Candroid and Speedgrader Mobile Apps -# ====================================================================================================================== - -shared_examples 'candroid and speedgrader landing page' do |app_name| - it 'displays a landing page', priority: "1", test_id: pick_test_id_for_app(app_name, 221316, 295284) do - # TODO: ask dev team to implement this for speedgrader - expect(find_element(:id, 'help_button')).to be_truthy unless @app_name =~ /(speedgrader)/ - expect(find_element(:id, 'canvas_logo')).to be_truthy - expect(find_element(:id, 'enterURL')).to be_truthy - expect(find_element(:id, 'enterURL').text).to eq(default_url) - end - - # TODO: ask dev team to implement this - it 'routes to canvas guides', priority: "1", test_id: pick_test_id_for_app(app_name, 221317, 295285) do - skip('Android SpeedGrader app does not have a Help menu on landing page') if @app_name =~ /(speedgrader)/ - find_element(:id, 'help_button').click - find_element(:id, 'search_guides').click - expect(tags('android.widget.ImageButton')[0].name).to eq('Navigate up') - expect(text_exact('Canvas Guides')).to be_truthy - tags('android.widget.ImageButton')[0].click - end - - # TODO: ask dev team to implement this - it 'routes to report a problem', priority: "1", test_id: pick_test_id_for_app(app_name, 221318, 295286) do - skip('Android SpeedGrader app does not have a Help menu on landing page') if @app_name =~ /(speedgrader)/ - find_element(:id, 'help_button').click - find_element(:id, 'report_problem').click - begin - hide_keyboard - rescue Selenium::WebDriver::Error::UnknownError => ex # soft keyboard not present, cannot hide keyboard - raise unless ex.message == 'Soft keyboard not present, cannot hide keyboard' - end - - text_fields_id.each_with_index do |id, index| - verify_text_field(scroll_to_text_field(id), index) - end - - find_element(:id, 'severitySpinner').click - severity_levels = ids('text') - verify_severity_levels(severity_levels) - severity_levels[4].click - scroll_to_severity_spinner - expect(find_element(:id, 'text').text).to eq('EXTREME CRITICAL EMERGENCY!!') - expect(find_element(:id, 'dialog_custom_cancel').text).to eq('CANCEL') - expect(find_element(:id, 'dialog_custom_confirm').text).to eq('SEND') - - find_element(:id, 'dialog_custom_cancel').click - - hide_keyboard unless exists{ find_element(:id, 'help_button') } - expect(find_element(:id, 'help_button')).to be_truthy - expect(find_element(:id, 'canvas_logo')).to be_truthy - expect(find_element(:id, 'enterURL')).to be_truthy - expect(find_element(:id, 'enterURL').text).to eq(default_url) - end - - it 'lists possible schools when entering url and routes to school', priority: "1", test_id: pick_test_id_for_app(app_name, 221319, 295287) do - find_element(:id, 'enterURL').send_keys('t') - expect(find_element(:id, 'connect')).to be_truthy - expect(find_element(:id, 'canvasNetworkHeader')).to be_truthy - - # wait for list of schools to populate - sleep(0.100) - schools = ids('name') - expect(schools.size).to be >= 4 - school = schools[3] - school_name = school.text - school.click - - expect(tag('android.webkit.WebView')).to be_truthy - expect(find_ele_by_attr('tag', 'android.widget.TextView', 'text', /([a-z]+)(.instructure.com)/)) - .to be_an_instance_of(Selenium::WebDriver::Element) - back - - edit_url_text = find_element(:id,'enterURL') - expect(edit_url_text.text).to match(/([a-z]+)(.instructure.com)/) - expect(text_exact(school_name)).to be_truthy - edit_url_text.clear - expect(edit_url_text.text).to eq(default_url) - end - - it 'routes to school login page when school is typed in', priority: "1", test_id: pick_test_id_for_app(app_name, 221321, 295289) do - find_element(:id, 'enterURL').send_keys(@school) - find_element(:id, 'connect').click - - # wait for webview to load - wait_true(timeout: 10, interval: 0.100){ tag('android.webkit.WebView') } - - wait_true(timeout: 10, interval: 0.100){ first_textfield } - wait_true(timeout: 10, interval: 0.100){ last_textfield } - wait_true(timeout: 10, interval: 0.100){ button('Log in') } - - reset_password = find_ele_by_attr('tags', 'android.view.View', 'name', /I don't know my password/) - expect(reset_password.name).to eq('I don\'t know my password') - back - end -end - -# ====================================================================================================================== -# Helper Methods -# ====================================================================================================================== - -def scroll_to_severity_spinner - scroll_to_element(scroll_view: tag('android.widget.ScrollView'), - strategy: 'id', - id: 'severitySpinner', - time: 500, - direction: 'down', - attempts: 2) -end - -def scroll_to_text_field(id) - scroll_to_element(scroll_view: tag('android.widget.ScrollView'), - strategy: 'id', - id: id, - time: 500, - direction: 'down', - attempts: 2) -end - -def text_fields_id - %w( - dialog_custom_title - subject - subjectEditText - emailAddress - emailAddressEditText - description - descriptionEditText - severityPrompt - severitySpinner - ) -end - -def verify_text_field(text_field, index) - expect(text_field).to be_an_instance_of(Selenium::WebDriver::Element) - case index - when 0 - expect(text_field.text).to eq('Report A Problem') - when 1 - expect(text_field.text).to eq('Subject') - when 3 - expect(text_field.text).to eq('Email Address') - when 4 - expect(text_field.text).to match(/(Enter your email address)/) # '...' has issues with == - when 5 - expect(text_field.text).to eq('Description') - when 6 - expect(text_field.text).to match(/(Write Something)/) # '...' has issues with == - when 7 - expect(text_field.text).to eq('How is this affecting you?') - end -end - -def verify_severity_levels(severity_levels) - expect(severity_levels.size).to be(5) - severity_levels.each_index do |index| - case index - when 0 - # '...' has issues with == - expect(severity_levels[index].text).to match(/(Just a casual question, comment, idea, suggestion…)/) - when 1 - expect(severity_levels[index].text).to eq('I need some help but it\'s not urgent.') - when 2 - expect(severity_levels[index].text).to eq('Something\'s broken but I can work around it to get what I need done.') - when 3 - expect(severity_levels[index].text).to eq('I can\'t get things done until I hear back from you.') - when 4 - expect(severity_levels[index].text).to eq('EXTREME CRITICAL EMERGENCY!!') - end - end -end diff --git a/spec/selenium/appium/android/helpers/login_common.rb b/spec/selenium/appium/android/helpers/login_common.rb deleted file mode 100644 index 20e6fe26b75..00000000000 --- a/spec/selenium/appium/android/helpers/login_common.rb +++ /dev/null @@ -1,86 +0,0 @@ -require_relative 'android_common' - -# ====================================================================================================================== -# Shared Examples for Candroid and Speedgrader Mobile Apps -# ====================================================================================================================== - -shared_examples 'login credentials for candroid and speedgrader' do |app_name| - before(:all) do - user_with_pseudonym(username: 'teacher1', password: 'teacher') - end - - context 'user provides bad credentials' do - before(:each) do - enter_school - end - - # TODO: write test cases for bad credentials - end - - context 'user forgot their password' do - before(:each) do - enter_school - end - - after(:each) do - back - end - - it 'routes to password reset view', priority: "1", test_id: pick_test_id_for_app(app_name, 221322, 295519) do - find_ele_by_attr('tags', 'android.view.View', 'name', /(I don't know my password)/).click - - wait_true(timeout: 10, interval: 0.100){ button_exact('Request Password') } - expect(find_ele_by_attr('tags', 'android.view.View', 'name', /(change your password)/).name) - .to eq('Enter your Email and we\'ll send you a link to change your password.') - expect(first_textfield).to be_displayed # TODO: ask dev team for content descriptor - - back_to_login_view = find_ele_by_attr('tags', 'android.view.View', 'name', /Back to Login/) - expect(back_to_login_view.name).to eq('Back to Login') - back_to_login_view.click - end - end - - context 'user provides good credentials' do - before(:each) do - enter_school - provide_credentials(@user.primary_pseudonym.unique_id, user_password(@user)) - end - - after(:each) do - logout(false) - end - - it 'passes login and routes to home page', priority: "1", test_id: pick_test_id_for_app(app_name, 221323, 295521) do - wait_true(timeout: 10, interval: 0.250){ find_ele_by_attr('tags', 'android.view.View', 'name', app_login_message) } - - expect(find_ele_by_attr('tags', 'android.view.View', 'name', app_access_message)) - .to be_an_instance_of(Selenium::WebDriver::Element) - expect(find_ele_by_attr('tags', 'android.view.View', 'name', /(You are logging into this app as)/)) - .to be_an_instance_of(Selenium::WebDriver::Element) - expect(find_ele_by_attr('tags', 'android.view.View', 'name', /#{(@user.primary_pseudonym.unique_id)}/)) - .to be_an_instance_of(Selenium::WebDriver::Element) - - expect(find_ele_by_attr('tags', 'android.view.View', 'name', /Cancel/).name).to eq('Cancel') - remember_auth = find_ele_by_attr('tags', 'android.widget.CheckBox', 'name', /Remember my authorization for this service/) - expect(remember_auth.attribute('checked')).to eq('false') - remember_auth.click - expect(remember_auth.attribute('checked')).to eq('true') - button('Log in').click - - skip_tutorial - - # User avatar displays on Home Page but takes time to load - wait_true(timeout:10, interval: 0.250){ candroid_app ? find_element(:id, 'userProfilePic') : find_element(:id, 'courseSwitcher') } - end - end -end - -# ====================================================================================================================== -# Helper Methods -# ====================================================================================================================== - -def provide_credentials(username, password) - first_textfield.send_keys(username) - last_textfield.send_keys(password) - button('Log in').click -end diff --git a/spec/selenium/appium/android/speedgrader/general/landing_page/landing_page_spec.rb b/spec/selenium/appium/android/speedgrader/general/landing_page/landing_page_spec.rb deleted file mode 100644 index c53896f5820..00000000000 --- a/spec/selenium/appium/android/speedgrader/general/landing_page/landing_page_spec.rb +++ /dev/null @@ -1,10 +0,0 @@ -require_relative '../../../helpers/landing_page_common' - -describe 'speedgrader for android landing page' do - include_context 'in-process server appium tests' - include_context 'appium mobile specs', 'speedgrader_android' - let(:default_url){ 'myschool.instructure.com' } - - # examples located in: spec/selenium/appium/android/helpers/landing_page_common.rb - it_behaves_like 'candroid and speedgrader landing page', 'speedgrader_android' -end diff --git a/spec/selenium/appium/android/speedgrader/general/login/login_spec.rb b/spec/selenium/appium/android/speedgrader/general/login/login_spec.rb deleted file mode 100644 index b1e6ef9ac71..00000000000 --- a/spec/selenium/appium/android/speedgrader/general/login/login_spec.rb +++ /dev/null @@ -1,11 +0,0 @@ -require_relative '../../../helpers/login_common' - -describe 'speedgrader login credentials' do - include_context 'in-process server appium tests' - include_context 'appium mobile specs', 'speedgrader_android' - let(:app_login_message){ /(Canvas for Android)/ } # TODO: ask dev team to modify this for Speedgrader - let(:app_access_message){ /Canvas for Android is requesting access.*/ } # TODO: ask dev team to modify this for Speedgrader - - # examples located in: spec/selenium/appium/android/helpers/login_common.rb - it_behaves_like 'login credentials for candroid and speedgrader', 'speedgrader_android' -end diff --git a/spec/selenium/appium/environment_setup.rb b/spec/selenium/appium/environment_setup.rb deleted file mode 100644 index be9cfe74533..00000000000 --- a/spec/selenium/appium/environment_setup.rb +++ /dev/null @@ -1,271 +0,0 @@ -require 'socket' -require 'timeout' -require_relative '../../spec_helper' -require_relative '../test_setup/common_helper_methods/login_and_session_methods' -require_relative '../test_setup/common_helper_methods/other_helper_methods' - -module EnvironmentSetup - # All test environments must be seeded with this Developer Key. The only attribute allowed - # to change is the *redirect_uri* which contains the static IP address of the Canvas-lms - # environment, but do not change it; modify the *host_url* method instead. - def create_developer_key - if @appium_dev_key.nil? - truncate_table(DeveloperKey) if @appium_dev_key.nil? - @appium_dev_key = DeveloperKey.create!( - name: $appium_config[:mv_key_name], - tool_id: $appium_config[:mv_key_id], - email: $appium_config[:mv_key_email], - redirect_uri: "http://#{host_url}", - api_key: $appium_config[:mv_key] - ) - end - @appium_dev_key - end - - # TODO: update when Appium is integrated with Jenkins, append $server_port - def school_domain - $appium_config[:school_domain] - end - - # Static IP addresses entered into Mobile Verify. Comment/Uncomment to set the url. - # Mobile Apps will not connect to local test instances other than these. - def host_url - $appium_config[:appium_host_url] - end - - # This assumes Appium server will be running on the same host as the Canvas-lms. - def appium_server_url - URI("http://#{host_url}:4723/wd/hub") - end - - def android_course_name - 'QA | Android' - end - - def ios_course_name - 'QA | iOS' - end - - # Appium settings are device specific. To list connected devices for Android run: - # $ /platform-tools/adb devices - def android_device_name - $appium_config[:android_udid] - end - - # Appium settings are device specific. To get iOS device info: - # Settings > General > Version - # $ idevice_id -l ### lists connected devices by UDID - # $ idevice_id [UDID] ### prints device name - # $ idevice_id -l ### lists connected devices by UDID - def ios_device - { versionNumber: $appium_config[:ios_version], - deviceName: $appium_config[:ios_device_name], - udid: $appium_config[:ios_udid], - app: $appium_config[:ios_app_path] } - end - - def ios_app_path - $appium_config[:ios_app_path] - end - - def implicit_wait_time - 3 - end - - # ==================================================================================================================== - # Extracted from spec/selenium/test_setup/selenium_driver_setup.rb - # Modified for Mobile App automation: all things Selenium removed - # ==================================================================================================================== - - include I18nUtilities - - $appium_config = ConfigFile.load("appium") || {} - SERVER_IP = $appium_config[:server_ip] || UDPSocket.open do |s| - s.connect('8.8.8.8', 1) - s.addr.last - end - BIND_ADDRESS = $appium_config[:bind_address] || '0.0.0.0' - SECONDS_UNTIL_COUNTDOWN = 5 - SECONDS_UNTIL_GIVING_UP = 20 - MAX_SERVER_START_TIME = 60 - THIS_ENV = ENV['TEST_ENV_NUMBER'].to_i - THIS_ENV = 1 if ENV['TEST_ENV_NUMBER'].blank? - WEBSERVER = (ENV['WEBSERVER'] || 'thin').freeze - - $server_port = nil - $app_host_and_port = nil - - def host_and_port - if $appium_config[:host] && $appium_config[:port] && !$appium_config[:host_and_port] - $appium_config[:host_and_port] = "#{$appium_config[:host]}:#{$appium_config[:port]}" - end - end - - # Runs a port scan unless the appium.yml file defines :server_port - def self.setup_host_and_port - ENV['CANVAS_CDN_HOST'] = "canvas.instructure.com" - if $appium_config[:server_port] - $server_port = $appium_config[:server_port] - $app_host_and_port = "#{SERVER_IP}:#{$server_port}" - return $server_port - end - - # find an available socket - s = Socket.new(:INET, :STREAM) - s.setsockopt(:SOCKET, :REUSEADDR, true) - s.bind(Addrinfo.tcp(SERVER_IP, 0)) - - $server_port = s.local_address.ip_port - if $appium_config[:browser] == 'ie' - # makes default URL for selenium the external IP of the box for standalone sel servers - server_ip = `curl http://instance-data/latest/meta-data/public-ipv4` # command for aws boxes gets external ip - else - server_ip = s.local_address.ip_address - end - - $app_host_and_port = "#{server_ip}:#{s.local_address.ip_port}" - puts "Found available port: #{$app_host_and_port}" - - return $server_port - ensure - s.close() if s - end - - def self.start_webserver(webserver) - setup_host_and_port - case webserver - when 'thin' - self.start_in_process_thin_server - when 'webrick' - self.start_in_process_webrick_server - else - puts "No web server specified, defaulting to WEBrick" - self.start_in_process_webrick_server - end - end - - def self.shutdown_webserver(server) - shutdown = lambda do - server.shutdown - HostUrl.default_host = nil - HostUrl.file_host = nil - end - at_exit { shutdown.call } - shutdown - end - - def self.rack_app - app = Rack::Builder.new do - use Rails::Rack::Debugger unless Rails.env.test? - run CanvasRails::Application - end.to_app - - lambda do |env| - nope = [503, {}, [""]] - return nope unless allow_requests? - - # wrap request in a mutex so we can ensure it doesn't span spec - # boundaries (see clear_requests!) - result = request_mutex.synchronize { app.call(env) } - - # check if the spec just finished while we ran, and if so prevent - # side effects like redirects (and thus moar requests) - if allow_requests? - result - else - # make sure we clean up the body of requests we throw away - # https://github.com/rack/rack/issues/658#issuecomment-38476120 - result.last.close if result.last.respond_to?(:close) - nope - end - end - end - - class << self - def disallow_requests! - # ensure the current in-flight request (if any, AJAX or otherwise) - # finishes up its work, and prevent any subsequent requests before the - # next spec gets underway. otherwise race conditions can cause sadness - # with our shared conn and transactional fixtures (e.g. special - # accounts and their caching) - @allow_requests = false - request_mutex.synchronize { } - end - - def allow_requests! - @allow_requests = true - end - - def allow_requests? - @allow_requests - end - - def request_mutex - @request_mutex ||= Mutex.new - end - end - - def self.start_in_process_thin_server - require_relative '../test_setup/servers/thin_server' - SpecFriendlyThinServer.run(self.rack_app, BindAddress: BIND_ADDRESS, Port: $server_port, AccessLog: []) - self.shutdown_webserver(SpecFriendlyThinServer) - end - - def self.start_in_process_webrick_server - require_relative '../test_setup/servers/webrick_server' - SpecFriendlyWEBrickServer.run(self.rack_app, BindAddress: BIND_ADDRESS, Port: $server_port, AccessLog: []) - self.shutdown_webserver(SpecFriendlyWEBrickServer) - end - - # ==================================================================================================================== - # Extracted from spec/selenium/common.rb - # Modified for Mobile App automation: all things Selenium removed - # ==================================================================================================================== - - shared_context 'in-process server appium tests' do - include OtherHelperMethods - include LoginAndSessionMethods - - # set up so you can use rails urls helpers in your selenium tests - include Rails.application.routes.url_helpers - - prepend_before :all do - $in_proc_webserver_shutdown ||= EnvironmentSetup.start_webserver(WEBSERVER) - end - - # tricksy tricksy. grab the current connection, and then always return the same one - # (even if on a different thread - i.e. the server's thread), so that it will be in - # the same transaction and see the same data - before do - if self.use_transactional_fixtures - @db_connection = ActiveRecord::Base.connection - @dj_connection = Delayed::Backend::ActiveRecord::Job.connection - - # synchronize db connection methods for a modicum of thread safety - methods_to_sync = %w{execute exec_cache exec_no_cache query} - [@db_connection, @dj_connection].each do |conn| - methods_to_sync.each do |method_name| - if conn.respond_to?(method_name, true) && !conn.respond_to?("#{method_name}_with_synchronization", true) - conn.class.class_eval <<-RUBY - def #{method_name}_with_synchronization(*args) - @mutex ||= Mutex.new - @mutex.synchronize { #{method_name}_without_synchronization(*args) } - end - alias_method_chain :#{method_name}, :synchronization - RUBY - end - end - end - - ActiveRecord::ConnectionAdapters::ConnectionPool.any_instance.stubs(:connection).returns(@db_connection) - Delayed::Backend::ActiveRecord::Job.stubs(:connection).returns(@dj_connection) - Delayed::Backend::ActiveRecord::Job::Failed.stubs(:connection).returns(@dj_connection) - end - end - - after(:each) do - EnvironmentSetup.disallow_requests! - truncate_all_tables unless self.use_transactional_fixtures - end - end -end diff --git a/spec/selenium/appium/ios/helpers/ios_common.rb b/spec/selenium/appium/ios/helpers/ios_common.rb deleted file mode 100644 index c4b331cd34b..00000000000 --- a/spec/selenium/appium/ios/helpers/ios_common.rb +++ /dev/null @@ -1,140 +0,0 @@ -require_relative '../../mobile_common' - -# ====================================================================================================================== -# Log In / Out of Mobile App -# ====================================================================================================================== - -def icanvas_init(username, password, course_name) - enter_school - login_mobile(username, password) - dismiss_user_polling - navigate_to_course(course_name) -end - -def enter_school - find_ele_by_attr('UIATextField', 'value', 'Find your school or district').send_keys(@school) - visible_buttons = buttons - if visible_buttons.size == 1 - visible_buttons[0].click - else - visible_buttons[1].click - end - wait_true(timeout: 10, interval: 0.100){ tag('UIASecureTextField') } -end - -def enter_username(username) - email = tag('UIATextField') - email.click - email.send_keys(username) -end - -def enter_password(password) - pw = tag('UIASecureTextField') - pw.click - # bug found in iOS Instruments / Appium: when using Simulator, *send_keys* has undefined behavior - # *send_keys* may clear out the previously selected text field - sleep(1) # <--- Waiting to send keys after element is selected appears to be a stable temporary fix - pw.send_keys(password) -end - -def provide_credentials(username, password) - enter_username(username) - enter_password(password) - button_exact('Log In').click -end - -def login_mobile(username, password) - provide_credentials(username, password) - wait_true(timeout: 10, interval: 0.250){ text_exact('Canvas for iOS') } - button_exact('Log In').click -end - -# ==== Parameters -# change_user: App settings for multi-user access must be enabled if change_user is true -# If multi-user is disabled, change_user must be false -def logout(change_user) - if icanvas_app - navigate_to('Logout') - if change_user - find_element(:id, 'Change User').click - else - find_elements(:id, 'Logout')[1].click - end - else # speedgrader - first_button.click # hamburger - find_element(:id, 'Logout').click - end -end - -# This assumes the app is on the landing page. -def logout_all_users - while exists{ find_element(:id, 'icon x delete') } - find_element(:id, 'icon x delete').click - end -end - -# ====================================================================================================================== -# Navigation -# ====================================================================================================================== - -def goto_courses_root - # tap twice to guarantee root view - button_exact('Courses').click - button_exact('Courses').click -end - -# TODO: add support for Speedgrader -def navigate_to_course(course_name) - if icanvas_app - # Double tap should navigate to root courses view - wait_true(timeout: 10, interval: 0.250){ button_exact('Courses') } - goto_courses_root - scroll_to_element( - scroll_view: tag('UIACollectionView'), - id: course_name, - time: 1000, - direction: 'down', - attempts: 2 - ).click - end -end - -def navigate_to(location, opts = {course_name: ios_course_name}) - case location - when 'My Files', 'About', 'Help', 'Logout' - goto_courses_root - find_element(:name, 'Profile').click - find_element(:name, location).click - when 'Calendar', 'To Do List', 'Notifications', 'Messages' - button_exact(location).click - else - navigate_to_course(opts[:course_name]) - find_element(:name, location).click - end -end - -# ====================================================================================================================== -# General -# ====================================================================================================================== - -def double_tap(element) - element.click - element.click -end - -def device_is_iphone - $appium_config[:ios_type] == 'iPhone' -end - -def device_is_ipad - $appium_config[:ios_type] == 'iPad' -end - -def dismiss_user_polling - begin - find_element(:id, 'How do you like Canvas?') - find_element(:id, 'Don\'t ask me again').click - rescue => ex - raise ex unless ex.is_a?(Selenium::WebDriver::Error::NoSuchElementError) - end -end diff --git a/spec/selenium/appium/ios/helpers/landing_page_common.rb b/spec/selenium/appium/ios/helpers/landing_page_common.rb deleted file mode 100644 index 7728491bdd7..00000000000 --- a/spec/selenium/appium/ios/helpers/landing_page_common.rb +++ /dev/null @@ -1,106 +0,0 @@ -require_relative 'ios_common' - -# ====================================================================================================================== -# Shared Examples for iCanvas and Speedgrader Mobile Apps -# ====================================================================================================================== - -shared_examples 'icanvas and speedgrader landing page' do |app_name| - before(:all) do - logout_all_users - end - - it 'displays a landing page', priority: "1", test_id: pick_test_id_for_app(app_name, 9779, 303715) do - expect(find_ele_by_attr('UIATextField', 'value', 'Find your school or district')).to be_displayed - - help_button = open_help_menu(app_name) - verify_help_menu(true) - close_help_menu(help_button) - verify_help_menu(false) - end - - it 'routes to find school domain help page', priority: "1", test_id: pick_test_id_for_app(app_name, 235571, 303716) do - open_help_menu(app_name) - verify_help_menu(true) - button_exact('Find School Domain').click - verify_help_menu(false) - - expect(find_element(:id, 'Back')).not_to be_displayed - expect(find_element(:id, 'Help')).to be_displayed - expect(find_element(:id, 'Done')).to be_displayed - - wait_true(timeout: 10, interval: 0.100){ text_exact('How do I find my institution\'s URL to access Canvas apps on my mobile device?') } - - button_exact('Done').click - end - - context 'entering school url' do - let(:school_name){ 'Utah Education Network'} - let(:school_url){ 'uen.instructure.com'} - let(:minimum_list_size){ 4 } - - after(:each) do - find_element(:id, 'Cancel').click - wait_true(timeout: 10, interval: 0.250){ expect(find_ele_by_attr('UIATextField', 'value', 'Find your school or district')).to be_displayed } - end - - it 'lists possible schools when entering url and routes to school', priority: "1", test_id: pick_test_id_for_app(app_name, 235572, 303717) do - find_school_text = find_ele_by_attr('UIATextField', 'value', 'Find your school or district') - expect(tags('UIATableCell').size).to be 0 - - # Sending any key should generate a list of possible school - find_school_text.send_keys(school_name.split[0]) - school_list = tags('UIATableCell') - expect(school_list.size).to be > minimum_list_size - - # Click on auto-generated text - text_exact(school_name).click - wait_true(timeout: 10, interval: 0.250){ find_element(:id, school_url) } - end - - it 'routes to school login page when school is typed in', priority: "1", test_id: pick_test_id_for_app(app_name, 235573, 303718) do - enter_school - verify_empty_login_view - end - end -end - -# ====================================================================================================================== -# Helper Methods -# ====================================================================================================================== - -# TODO: fix when SpeedGrader Landing Page is updated -def open_help_menu(app_name) - if app_name == 'icanvas' - help_button = find_element(:id, 'Open help menu.') - else - help_button = buttons[0] - end - help_button.click - help_button -end - -# Help menu closes differently between iPhone and iPad devices -def close_help_menu(help_button) - if device_is_iphone - button_exact('Cancel').click - else - Appium::TouchAction.new.tap(x: help_button.location.x, y: help_button.location.y).perform - end -end - -def verify_help_menu(displayed) - expect(exists{ text_exact('Help Menu') }).to be displayed - expect(exists{ button_exact('Report a Problem') }).to be displayed - expect(exists{ button_exact('Request a Feature') }).to be displayed - expect(exists{ button_exact('Find School Domain') }).to be displayed - expect(exists{ button_exact('Cancel') }).to be displayed if device_is_iphone # not displayed on iPads -end - -def verify_empty_login_view - wait(timeout: 10, interval: 0.250){ tag('UIASecureTextField') } - expect(tag('UIATextField')).to be_displayed - expect(tag('UIASecureTextField')).to be_displayed - expect(button_exact('Log In')).to be_displayed - expect(text_exact('I don\'t know my password')).to be_displayed - expect(find_element(:id, 'Cancel')).to be_displayed -end diff --git a/spec/selenium/appium/ios/helpers/login_common.rb b/spec/selenium/appium/ios/helpers/login_common.rb deleted file mode 100644 index fb969d63adf..00000000000 --- a/spec/selenium/appium/ios/helpers/login_common.rb +++ /dev/null @@ -1,147 +0,0 @@ -require_relative 'ios_common' - -# ====================================================================================================================== -# Shared Examples for iCanvas and Speedgrader Mobile Apps -# ====================================================================================================================== - -shared_context 'icanvas and speedgrader login credentials' do |app_name| - before(:all) do - user_with_pseudonym(username: 'teacher1', password: 'teacher') - end - - context 'displays login view' do - # using *mobiledev* as school rather than test instance because test instance will not have *.instructure.com* - let(:school_name){ 'mobiledev' } - let(:school_url){ school_name + '.instructure.com' } - - before(:each) do - find_ele_by_attr('UIATextField', 'value', 'Find your school or district').send_keys(school_name) - visible_buttons = buttons - visible_buttons.size == 1 ? visible_buttons[0].click : visible_buttons[1].click - end - - after(:each) do - find_element(:id, 'Cancel').click if exists{ find_element(:id, 'Cancel') } - end - - it 'displays correct url for selected school', priority: "1", test_id: pick_test_id_for_app(app_name, 14040, 303727) do - wait_true(timeout: 10, interval: 0.250){ expect(find_element(:id, school_url)).to be_displayed } - end - - it 'displays cancel button which returns to landing page', priority: "1", test_id: pick_test_id_for_app(app_name, 251028, 303728) do - expect(find_element(:id, 'Cancel')).to be_displayed - find_element(:id, 'Cancel').click - expect(find_ele_by_attr('UIATextField', 'value', 'Find your school or district')).to be_displayed - end - end - - context 'user provides bad credentials' do - before(:all) do - enter_school - end - - # Clear Email field between runs - after(:each) do - tag('UIATextField').clear - end - - # Return to 'Find your school or district' - after(:all) do - button_exact('Cancel').click - end - - it 'fails login with incorrect username', priority: "1", test_id: pick_test_id_for_app(app_name, 14042, 303723) do - provide_credentials('Chester Copperpot', user_password(@user)) - verify_login_view('Chester Copperpot', 'Incorrect username and/or password') - end - - it 'fails login when password omitted', priority: "1", test_id: pick_test_id_for_app(app_name, 238142, 303726) do - provide_credentials('Chester Copperpot', '') - verify_login_view('Chester Copperpot', 'No password was given') - end - - it 'fails login with incorrect password', priority: "1", test_id: pick_test_id_for_app(app_name, 14043, 303724) do - provide_credentials(@user.primary_pseudonym.unique_id, '1234') - verify_login_view(@user.primary_pseudonym.unique_id, 'Incorrect username and/or password') - end - end - - context 'user forgot their password' do - before(:each) do - enter_school - end - - # Return to 'Find your school or district' - after(:each) do - button_exact('Cancel').click - end - - it 'routes to password reset view', priority: "1", test_id: pick_test_id_for_app(app_name, 235575, 303729) do - text_exact('I don\'t know my password').click - - message = 'Enter your Email and we\'ll send you a link to change your password.' - email = tag('UIATextField') - expect(text_exact(message)).to be_displayed - expect(email).to be_displayed - expect(email.name).to eq(message) - expect(email.text).to eq('Email') - expect(button_exact('Request Password')).to be_displayed - expect(text_exact('Back to Login')).to be_displayed - - # return to Login view - text_exact('Back to Login').click - expect(tag('UIASecureTextField')).to be_displayed - end - end - - context 'user provides good credentials' do - before(:each) do - enter_school - provide_credentials(@user.primary_pseudonym.unique_id, user_password(@user)) - end - - after(:each) do - logout(false) - end - - it 'passes login and routes to home page', priority: "1", test_id: pick_test_id_for_app(app_name, 14041, 303730) do - # App requests access to Canvas account; need to wait for next WebView to load - wait_true(timeout: 10, interval: 0.250){ text_exact(app_login_message) } - expect(text_exact(app_access_message)).to be_displayed - - # Verify paragraph text includes username - links = tags('UIALink') - expect(text_exact('You are logging into this app as')).to be_displayed - expect(links[0].name).to eq(@user.primary_pseudonym.unique_id) - expect(text_exact('.')).to be_displayed - expect(button_exact('Log In')).to be_displayed - expect(links[1].name).to eq('Cancel') - expect(text_exact('Cancel')).to be_displayed - expect(text_exact('Remember my authorization for this service')).to be_displayed - - # Toggle switch and click - auth_switch = tag('UIASwitch') - expect(auth_switch).to be_displayed - expect(auth_switch.name).to eq('Remember my authorization for this service') - auth_switch.click - button_exact('Log In').click - - # User is occasionally polled here - dismiss_user_polling - wait_true(timeout: 10, interval: 0.250){ find_element(:id, app_login_success) } - end - end -end - -# ====================================================================================================================== -# Helper Methods -# ====================================================================================================================== - -def verify_login_view(username, error_message) - wait_true(timeout: 10, interval: 0.250){ expect(tag('UIATextField').text).to eq(username) } - expect(tag('UIASecureTextField').text).to eq('Password') - find_ele_by_attr('UIAStaticText', 'name', error_message) - expect(button_exact('Log In')).to be_displayed - expect(text_exact('I don\'t know my password')).to be_displayed - expect(find_element(:id, 'Cancel')).to be_displayed -end diff --git a/spec/selenium/appium/ios/icanvas/general/landing_page/landing_page_spec.rb b/spec/selenium/appium/ios/icanvas/general/landing_page/landing_page_spec.rb deleted file mode 100644 index 6b843d75033..00000000000 --- a/spec/selenium/appium/ios/icanvas/general/landing_page/landing_page_spec.rb +++ /dev/null @@ -1,8 +0,0 @@ -require_relative '../../../helpers/landing_page_common' - -describe 'icanvas landing page' do - include_context 'in-process server appium tests' - include_context 'appium mobile specs', 'icanvas' - - it_behaves_like 'icanvas and speedgrader landing page', 'icanvas' -end diff --git a/spec/selenium/appium/ios/icanvas/general/login/login_spec.rb b/spec/selenium/appium/ios/icanvas/general/login/login_spec.rb deleted file mode 100644 index fa60cf19659..00000000000 --- a/spec/selenium/appium/ios/icanvas/general/login/login_spec.rb +++ /dev/null @@ -1,12 +0,0 @@ -require_relative '../../../helpers/login_common' - -describe 'user logging into icanvas app' do - include_context 'in-process server appium tests' - include_context 'appium mobile specs', 'icanvas' - let(:app_login_message){ 'Canvas for iOS' } - let(:app_access_message){ 'Canvas for iOS is requesting access to your account.' } - let(:app_login_success){ 'Profile' } - - # examples located in: spec/selenium/appium/ios/helpers/login_common.rb - it_behaves_like 'icanvas and speedgrader login credentials', 'icanvas' -end diff --git a/spec/selenium/appium/ios/speedgrader/general/landing_page/landing_page_spec.rb b/spec/selenium/appium/ios/speedgrader/general/landing_page/landing_page_spec.rb deleted file mode 100644 index 63750f0ab0f..00000000000 --- a/spec/selenium/appium/ios/speedgrader/general/landing_page/landing_page_spec.rb +++ /dev/null @@ -1,8 +0,0 @@ -require_relative '../../../helpers/landing_page_common' - -describe 'speedgrader for ios landing page' do - include_context 'in-process server appium tests' - include_context 'appium mobile specs', 'speedgrader_ios' - - it_behaves_like 'icanvas and speedgrader landing page', 'speedgrader_ios' -end diff --git a/spec/selenium/appium/ios/speedgrader/general/login/login_spec.rb b/spec/selenium/appium/ios/speedgrader/general/login/login_spec.rb deleted file mode 100644 index a85b8746ab1..00000000000 --- a/spec/selenium/appium/ios/speedgrader/general/login/login_spec.rb +++ /dev/null @@ -1,12 +0,0 @@ -require_relative '../../../helpers/login_common' - -describe 'user logging into speedgrader app' do - include_context 'in-process server appium tests' - include_context 'appium mobile specs', 'speedgrader_ios' - let(:app_login_message){ 'SpeedGrader' } - let(:app_access_message){ 'SpeedGrader is requesting access to your account.' } - let(:app_login_success){ 'CSGSlideMenuView' } - - # examples located in: spec/selenium/appium/ios/helpers/login_common.rb - it_behaves_like 'icanvas and speedgrader login credentials', 'speedgrader_ios' -end diff --git a/spec/selenium/appium/mobile_common.rb b/spec/selenium/appium/mobile_common.rb deleted file mode 100644 index ec71e4b7ee5..00000000000 --- a/spec/selenium/appium/mobile_common.rb +++ /dev/null @@ -1,233 +0,0 @@ -require 'appium_lib' -require_relative 'environment_setup' - -include EnvironmentSetup - -# Mobile canvas and speedgrader apps have features that behave the same in both apps. -# Because each app still has its own test case in TestRails, this method chooses -# the proper test_id for examples that can be shared between app specs. -def pick_test_id_for_app(app_name, canvas, speedgrader) - app_name =~ /(speedgrader)/ ? speedgrader : canvas -end - -# ====================================================================================================================== -# Shared Contexts and Helper Methods for Canvas Test Environment -# ====================================================================================================================== - -shared_context 'appium mobile specs' do |app_name| - before(:all) do - @app_name = app_name - # appium_init(app_name) # TODO: uncomment to run Appium tests - skip('Appium not yet integrated with Jenkins') # TODO: removed when Appium is integrated with Jenkins - create_developer_key - toggle_fail_fast(true) - end - - after(:all) do - toggle_fail_fast(false) - end -end - -shared_context 'teacher and student users' do |app_name| - before(:all) do - course(course_name: app_name =~ /(android)/ ? android_course_name : ios_course_name) - @course.offer - @teacher = user_with_pseudonym(username: 'teacher1', unique_id: 'teacher1', password: 'teacher', active_user: true) - @student = user_with_pseudonym(username: 'student1', unique_id: 'student1', password: 'student', active_user: true) - @course.enroll_user(@teacher, 'TeacherEnrollment').accept! - @course.enroll_user(@student).accept! - end -end - -shared_context 'course with all user groups' do |app_name| - before(:all) do - course(course_name: app_name =~ /(android)/ ? android_course_name : ios_course_name) - @course.offer - @teacher = user_with_pseudonym(username: 'teacher1', unique_id: 'teacher1', password: 'teacher', active_user: true) - @ta = user_with_pseudonym(username: 'assistant1', unique_id: 'assistant1', password: 'assistant', active_user: true) - @students = [] - @observers = [] - 5.times do |i| - @students << user_with_pseudonym(username: "student#{i+1}", unique_id: "student#{i+1}", password: 'student', active_user: true) - @observers << user_with_pseudonym(username: "observer#{i+1}", unique_id: "observer#{i+1}", password: 'observer', active_user: true) - @course.enroll_user(@students[i]).accept! - @course.enroll_user(@observers[i], 'ObserverEnrollment').accept! - end - @course.enroll_user(@teacher, 'TeacherEnrollment').accept! - @course.enroll_user(@ta, 'TaEnrollment').accept! - end -end - -shared_context 'course with a single user' do |role, app_name| - before(:all) do - basic_course_setup(role, app_name) - mobile_app_init(app_name) - end - - after(:all) do - logout(false) - end -end - -def basic_course_setup(role, app_name) - case role - when 'teacher' - course_with_teacher(course_arguments(role, app_name)) - when 'student' - course_with_student(course_arguments(role, app_name)) - else - raise('Unsupported role for custom user shared context. Additional roles coming soon...') - end -end - -def course_arguments(role, app_name) - { course_name: app_name =~ /(android)/ ? android_course_name : ios_course_name, - user: user_with_pseudonym(username: role + '1', unique_id: role + '1', password: role, active_user: true), - active_all: true } -end - -def mobile_app_init(app_name) - case app_name - when 'candroid', 'speedgrader_android' - android_app_init(@user.primary_pseudonym.unique_id, user_password(@user), @course.name) - when 'icanvas', 'speedgrader_ios' - icanvas_init(@user.primary_pseudonym.unique_id, user_password(@user), @course.name) - else - raise('Unsupported mobile application.') - end -end - -def user_password(user) - user.primary_pseudonym.unique_id =~ /[a-z]+1/ ? user.primary_pseudonym.unique_id.sub(/[0-9]/, '') : user.primary_pseudonym.unique_id -end - -def candroid_app - @app_name == 'candroid' -end - -def icanvas_app - @app_name == 'icanvas' -end - -def toggle_fail_fast(flag) - # this tells rspec to not run remaining tests in the spec if a test fails - # with mobile we can't guarantee the app is navigated to a specific location, so we fail quickly to not waste time - RSpec.configure do |c| - c.fail_fast = flag - end -end - -# ====================================================================================================================== -# Appium -# ====================================================================================================================== - -def start_appium_driver - Appium::Driver.new(caps: @capabilities, appium_lib: @appium_lib).start_driver - Appium.promote_appium_methods(RSpec::Core::ExampleGroup) - set_wait(implicit_wait_time) -end - -def appium_init_android - @capabilities = { - platformName: 'Android', - deviceName: android_device_name - } -end - -def appium_init_ios - device = ios_device - @capabilities = { - platformName: 'iOS', - versionNumber: device[:versionNumber], - deviceName: device[:deviceName], - udid: device[:udid], - app: device[:app], - autoAcceptAlerts: true, - # sendKeysStrategy: 'setValue', - waitForAppScript: '$.delay(7500); $.acceptAlert();' # auto-accepts device alerts when app launches, and prompts - } -end - -def appium_init(app_name) - @school = school_domain - @appium_lib = { server_url: appium_server_url } - case app_name - when 'candroid', 'speedgrader_android' - appium_init_android - when 'icanvas', 'speedgrader_ios' - appium_init_ios - else - raise('unsupported mobile platform') - end - start_appium_driver -end - -# ====================================================================================================================== -# Scrolling -# ====================================================================================================================== - -def scroll_within_y_coordinates(direction, top, bottom) - x = window_size.width / 2 - if direction == 'up' - start_y = top * 1.2 - end_y = bottom * 0.9 - else - start_y = bottom * 0.9 - end_y = top * 1.2 - end - - action = Appium::TouchAction.new.press(x: x, y: start_y).wait(2000).move_to(x: x, y: end_y).release - action.perform -end - -def scroll_to_element(opts) - count = 0 - begin - scroll_to_element_locator(opts) - rescue Selenium::WebDriver::Error::NoSuchElementError - scroll_vertically_in_view(opts[:scroll_view], opts[:time], opts[:direction]) - retry unless (count += 1) > opts[:attempts] - end -end - -def scroll_to_element_locator(opts) - case opts[:strategy] - when 'id' - return find_element(:id, opts[:id]) - when 'tag' - return tag(opts[:tag]) - when 'text_exact' - return text_exact(opts[:text_exact]) - else - raise('Unsupported locator strategy for scroll_to_element.') - end -end - -# Time is in milliseconds, so unless you want this to be a click send 1000 rather than 1 -def scroll_vertically_in_view(scroll_view, time, direction) - x = scroll_view.location.x + (0.5 * scroll_view.size.width) - - if direction == 'up' - start_y = scroll_view.location.y + (0.1 * scroll_view.size.height) - end_y = scroll_view.location.y + (0.9 * scroll_view.size.height) - else - start_y = scroll_view.location.y + (0.9 * scroll_view.size.height) - end_y = scroll_view.location.y + (0.1 * scroll_view.size.height) - end - - action = Appium::TouchAction.new.press(x: x, y: start_y).wait(time).move_to(x: x, y: end_y).release - action.perform -end - -def refresh_view(view) - scroll_vertically_in_view(view, 2, 'up') -end - -# ====================================================================================================================== -# Regex -# ====================================================================================================================== - -# returns regex which matches 12 and 24 hours time formats -def time_format - /(([1]{1}[0-2]{1}|[0-9]{1}):[0-5]{1}[0-9]{1}\s(AM|PM))|(([1-2]{1}[0-9]{1}|[0]?[0-9]{1}):[0-5]{1}[0-9]{1})/ -end diff --git a/spec/spec_helper.rb b/spec/spec_helper.rb index 14fe827acf6..6bcf450a20b 100644 --- a/spec/spec_helper.rb +++ b/spec/spec_helper.rb @@ -229,9 +229,6 @@ def truncate_table(model) begin old_proc = model.connection.raw_connection.set_notice_processor {} model.connection.execute("TRUNCATE TABLE #{model.connection.quote_table_name(model.table_name)} CASCADE") - - # mobile verify expects specific id seq for developer key, this forces the sequence to always start at 101 - model.connection.execute("SELECT setval('developer_keys_id_seq', 100);") if model == DeveloperKey ensure model.connection.raw_connection.set_notice_processor(&old_proc) end