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 <twilson@instructure.com>
Product-Review: Derek Hansen <dhansen@instructure.com>
QA-Review: Derek Hansen <dhansen@instructure.com>
This commit is contained in:
kinezu 2015-10-29 15:16:47 -06:00 committed by Derek Hansen
parent 6fcdcf6442
commit b858cba8ca
23 changed files with 0 additions and 1780 deletions

View File

@ -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

View File

@ -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"

View File

@ -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

View File

@ -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

View File

@ -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

View File

@ -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

View File

@ -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

View File

@ -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

View File

@ -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

View File

@ -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

View File

@ -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

View File

@ -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

View File

@ -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

View File

@ -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:
# $ <android_sdk_path>/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

View File

@ -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

View File

@ -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

View File

@ -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

View File

@ -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

View File

@ -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

View File

@ -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

View File

@ -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

View File

@ -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

View File

@ -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