Skip to content
Projects
Groups
Snippets
Help
This project
Loading...
Sign in / Register
Toggle navigation
E
ecommerce
Overview
Overview
Details
Activity
Cycle Analytics
Repository
Repository
Files
Commits
Branches
Tags
Contributors
Graph
Compare
Charts
Issues
0
Issues
0
List
Board
Labels
Milestones
Merge Requests
0
Merge Requests
0
CI / CD
CI / CD
Pipelines
Jobs
Schedules
Charts
Wiki
Wiki
Snippets
Snippets
Members
Members
Collapse sidebar
Close sidebar
Activity
Graph
Charts
Create a new issue
Jobs
Commits
Issue Boards
Open sidebar
edx
ecommerce
Commits
0135e93c
Commit
0135e93c
authored
Feb 17, 2017
by
Clinton Blackburn
Browse files
Options
Browse Files
Download
Email Patches
Plain Diff
Fixed checkout acceptance tests
ECOM-7099
parent
b10116a8
Hide whitespace changes
Inline
Side-by-side
Showing
8 changed files
with
132 additions
and
124 deletions
+132
-124
e2e/api.py
+20
-7
e2e/config.py
+9
-4
e2e/constants.py
+2
-3
e2e/mixins.py
+38
-53
e2e/requirements.txt
+5
-6
e2e/test_coupon_checkout.py
+5
-8
e2e/test_payment.py
+49
-39
requirements/test.txt
+4
-4
No files found.
e2e/api.py
View file @
0135e93c
import
requests
from
edx_rest_api_client.client
import
EdxRestApiClient
from
requests.auth
import
AuthBase
from
e2e.config
import
ACCESS_TOKEN
,
ENROLLMENT_API_URL
from
e2e.config
import
ENROLLMENT_API_URL
,
OAUTH_ACCESS_TOKEN_URL
,
OAUTH_CLIENT_ID
,
OAUTH_CLIENT_SECRET
def
get_access_token
():
""" Returns an access token and expiration date from the OAuth provider.
Returns:
(str, datetime)
"""
# TODO Use JWT auth once https://github.com/edx/edx-platform/pull/14577 is merged/released.
return
EdxRestApiClient
.
get_oauth_access_token
(
OAUTH_ACCESS_TOKEN_URL
,
OAUTH_CLIENT_ID
,
OAUTH_CLIENT_SECRET
,
)
class
BearerAuth
(
AuthBase
):
...
...
@@ -18,13 +31,13 @@ class BearerAuth(AuthBase):
class
EnrollmentApiClient
(
object
):
def
__init__
(
self
,
host
=
None
,
key
=
None
):
self
.
host
=
host
or
ENROLLMENT_API_URL
self
.
key
=
key
or
ACCESS_TOKEN
def
__init__
(
self
):
access_token
,
__
=
get_access_token
()
self
.
client
=
EdxRestApiClient
(
ENROLLMENT_API_URL
,
oauth_access_token
=
access_token
,
append_slash
=
False
)
def
get_enrollment_status
(
self
,
username
,
course_id
):
"""
Retrieve the enrollment status for given user in a given course.
"""
url
=
'{host}/enrollment/{username},{course_id}'
.
format
(
host
=
self
.
host
,
username
=
username
,
course_id
=
course_id
)
return
requests
.
get
(
url
,
auth
=
BearerAuth
(
self
.
key
))
.
json
()
param
=
'{username},{course_id}'
.
format
(
username
=
username
,
course_id
=
course_id
)
return
self
.
client
.
enrollment
(
param
)
.
get
()
e2e/config.py
View file @
0135e93c
...
...
@@ -2,9 +2,12 @@ import os
from
e2e.utils
import
str2bool
ACCESS_TOKEN
=
os
.
environ
.
get
(
'ACCESS_TOKEN'
)
if
ACCESS_TOKEN
is
None
:
raise
RuntimeError
(
'A valid OAuth2 access token is required.'
)
OAUTH_ACCESS_TOKEN_URL
=
os
.
environ
.
get
(
'OAUTH_ACCESS_TOKEN_URL'
)
OAUTH_CLIENT_ID
=
os
.
environ
.
get
(
'OAUTH_CLIENT_ID'
)
OAUTH_CLIENT_SECRET
=
os
.
environ
.
get
(
'OAUTH_CLIENT_SECRET'
)
if
not
all
([
OAUTH_ACCESS_TOKEN_URL
,
OAUTH_CLIENT_ID
,
OAUTH_CLIENT_SECRET
]):
raise
RuntimeError
(
'Valid OAuth details must be provided.'
)
HONOR_COURSE_ID
=
os
.
environ
.
get
(
'HONOR_COURSE_ID'
)
VERIFIED_COURSE_ID
=
os
.
environ
.
get
(
'VERIFIED_COURSE_ID'
)
...
...
@@ -20,9 +23,11 @@ except AttributeError:
ECOMMERCE_API_URL
=
os
.
environ
.
get
(
'ECOMMERCE_API_URL'
,
ECOMMERCE_URL_ROOT
+
'/api/v2'
)
MAX_COMPLETION_RETRIES
=
int
(
os
.
environ
.
get
(
'MAX_COMPLETION_RETRIES'
,
3
))
PAYPAL_EMAIL
=
os
.
environ
.
get
(
'PAYPAL_EMAIL'
)
PAYPAL_PASSWORD
=
os
.
environ
.
get
(
'PAYPAL_PASSWORD'
)
ENABLE_CYBERSOURCE_TESTS
=
str2bool
(
os
.
environ
.
get
(
'ENABLE_CYBERSOURCE_TESTS'
,
True
))
if
not
all
([
PAYPAL_EMAIL
,
PAYPAL_PASSWORD
]):
raise
RuntimeError
(
'PayPal credentials are required to fully test payment.'
)
try
:
MARKETING_URL_ROOT
=
os
.
environ
.
get
(
'MARKETING_URL_ROOT'
)
.
strip
(
'/'
)
...
...
e2e/constants.py
View file @
0135e93c
...
...
@@ -2,8 +2,7 @@ from datetime import date
CODE
=
'ABCD'
# cybersource data
CYBERSOURCE_DATA1
=
{
ADDRESS_US
=
{
'country'
:
'US'
,
'state'
:
'MA'
,
'line1'
:
'141 Portland Ave.'
,
...
...
@@ -11,7 +10,7 @@ CYBERSOURCE_DATA1 = {
'city'
:
'Cambridge'
,
'postal_code'
:
'02141'
,
}
CYBERSOURCE_DATA2
=
{
ADDRESS_FR
=
{
'country'
:
'FR'
,
'state'
:
None
,
'line1'
:
'Champ de Mars'
,
...
...
e2e/mixins.py
View file @
0135e93c
import
logging
import
time
import
uuid
import
requests
...
...
@@ -9,22 +10,11 @@ from selenium.webdriver.support import expected_conditions as EC
from
selenium.webdriver.support.select
import
Select
from
selenium.webdriver.support.ui
import
WebDriverWait
from
e2e.api
import
EnrollmentApiClient
from
e2e.api
import
EnrollmentApiClient
,
get_access_token
from
e2e.config
import
(
LMS_AUTO_AUTH
,
ECOMMERCE_URL_ROOT
,
LMS_PASSWORD
,
LMS_EMAIL
,
LMS_URL_ROOT
,
BASIC_AUTH_USERNAME
,
BASIC_AUTH_PASSWORD
,
ECOMMERCE_API_URL
,
LMS_USERNAME
,
ACCESS_TOKEN
,
MAX_COMPLETION_RETRIES
,
PAYPAL_PASSWORD
,
PAYPAL_EMAIL
,
LMS_HTTPS
LMS_AUTO_AUTH
,
ECOMMERCE_URL_ROOT
,
LMS_PASSWORD
,
LMS_EMAIL
,
LMS_URL_ROOT
,
BASIC_AUTH_USERNAME
,
BASIC_AUTH_PASSWORD
,
ECOMMERCE_API_URL
,
LMS_USERNAME
,
MAX_COMPLETION_RETRIES
,
PAYPAL_PASSWORD
,
PAYPAL_EMAIL
,
LMS_HTTPS
,
MARKETING_URL_ROOT
)
from
e2e.expected_conditions
import
input_provided
from
e2e.pages
import
submit_lms_login_form
...
...
@@ -98,7 +88,8 @@ class LMSLogoutMixin(object):
self
.
lms_logout_page
=
LMSLogoutPage
(
self
.
browser
)
def
assert_on_lms_logout_page
(
self
):
self
.
assertTrue
(
self
.
lms_logout_page
.
is_browser_on_page
())
lms_homepage_url
=
MARKETING_URL_ROOT
or
LMS_URL_ROOT
self
.
assertTrue
(
self
.
browser
.
current_url
.
startswith
(
lms_homepage_url
))
def
logout_via_lms
(
self
):
self
.
lms_logout_page
.
visit
()
...
...
@@ -163,7 +154,8 @@ class EnrollmentApiMixin(object):
class
EcommerceApiMixin
(
object
):
@property
def
ecommerce_api_client
(
self
):
return
EdxRestApiClient
(
ECOMMERCE_API_URL
,
oauth_access_token
=
ACCESS_TOKEN
)
access_token
,
__
=
get_access_token
()
return
EdxRestApiClient
(
ECOMMERCE_API_URL
,
oauth_access_token
=
access_token
)
def
assert_order_created_and_completed
(
self
):
orders
=
self
.
ecommerce_api_client
.
orders
.
get
()[
'results'
]
...
...
@@ -223,20 +215,28 @@ class PaymentMixin(object):
except
TimeoutException
:
pass
def
wait_for_payment_form
(
self
):
""" Wait for the payment form to load. """
wait
=
WebDriverWait
(
self
.
browser
,
10
)
wait
.
until
(
EC
.
presence_of_element_located
((
By
.
ID
,
'paymentForm'
)))
def
checkout_with_paypal
(
self
):
""" Completes the checkout process via PayPal. """
self
.
wait_for_payment_form
()
# Wait for the button handler to be setup
time
.
sleep
(
0.5
)
# Click the payment button
self
.
browser
.
find_element_by_css_selector
(
'
#paypal
'
)
.
click
()
self
.
browser
.
find_element_by_css_selector
(
'
button.payment-button[data-processor-name=paypal]
'
)
.
click
()
# Wait for login form to load. PayPal's test environment is slow.
wait
=
WebDriverWait
(
self
.
browser
,
30
)
iframe_present
=
EC
.
presence_of_element_located
((
By
.
CSS_SELECTOR
,
'#injectedUnifiedLogin > iframe'
))
iframe
=
wait
.
until
(
iframe_present
)
login_iframe
=
wait
.
until
(
EC
.
presence_of_element_located
((
By
.
CSS_SELECTOR
,
'#injectedUnifiedLogin > iframe'
)))
# TODO Determine how to get past the TypeError here.
self
.
browser
.
switch_to
.
frame
(
login_iframe
)
# Log into PayPal
self
.
browser
.
switch_to
.
frame
(
iframe
)
email
=
self
.
browser
.
find_element_by_css_selector
(
'input#email'
)
password
=
self
.
browser
.
find_element_by_css_selector
(
'input#password'
)
...
...
@@ -269,28 +269,16 @@ class PaymentMixin(object):
confirmation_button
.
click
()
def
checkout_with_cybersource
(
self
,
address
):
""" Completes the checkout process via CyberSource. """
# Click the payment button
self
.
browser
.
find_element_by_css_selector
(
'#cybersource'
)
.
click
()
self
.
dismiss_alert
()
# Wait for form to load
wait
=
WebDriverWait
(
self
.
browser
,
10
)
billing_details_present
=
EC
.
presence_of_element_located
((
By
.
ID
,
'billing_details'
))
wait
.
until
(
billing_details_present
)
# Select the credit card type (Visa) first since it triggers the display of additional fields
self
.
browser
.
find_element_by_css_selector
(
'#card_type_001'
)
.
click
()
# Visa
def
checkout_with_credit_card
(
self
,
address
):
""" Submit the payment form. """
self
.
wait_for_payment_form
()
# Select the appropriate <option> elements
select_fields
=
(
(
'#
bill_to_address
_country'
,
address
[
'country'
]),
(
'#
bill_to_address_state_us_ca
'
,
address
[
'state'
]),
(
'#card
_expiry_month'
,
'01
'
),
(
'#card
_expiry_year'
,
'202
0'
)
(
'#
id
_country'
,
address
[
'country'
]),
(
'#
id_state
'
,
address
[
'state'
]),
(
'#card
-expiry-month'
,
'12
'
),
(
'#card
-expiry-year'
,
'203
0'
)
)
for
selector
,
value
in
select_fields
:
if
value
:
...
...
@@ -299,24 +287,21 @@ class PaymentMixin(object):
# Fill in the text fields
billing_information
=
{
'bill_to_forename'
:
'Ed'
,
'bill_to_surname'
:
'Xavier'
,
'bill_to_address_line1'
:
address
[
'line1'
],
'bill_to_address_line2'
:
address
[
'line2'
],
'bill_to_address_city'
:
address
[
'city'
],
'bill_to_address_postal_code'
:
address
[
'postal_code'
],
'bill_to_email'
:
'edx@example.com'
,
'card_number'
:
'4111111111111111'
,
'card_cvn'
:
'1234'
'id_first_name'
:
'Ed'
,
'id_last_name'
:
'Xavier'
,
'id_address_line1'
:
address
[
'line1'
],
'id_address_line2'
:
address
[
'line2'
],
'id_city'
:
address
[
'city'
],
'id_postal_code'
:
address
[
'postal_code'
],
'card-number'
:
'4111111111111111'
,
'card-cvn'
:
'123'
}
for
field
,
value
in
billing_information
.
items
():
self
.
browser
.
find_element_by_css_selector
(
'#'
+
field
)
.
send_keys
(
value
)
# Click the payment button
self
.
browser
.
find_element_by_css_selector
(
'input[type=submit]'
)
.
click
()
self
.
dismiss_alert
()
self
.
browser
.
find_element_by_css_selector
(
'#payment-button'
)
.
click
()
def
assert_receipt_page_loads
(
self
):
""" Verifies the receipt page loaded in the browser. """
...
...
e2e/requirements.txt
View file @
0135e93c
# Packages required to run e2e tests
bok-choy==0.
5.0
ddt==1.
0
.1
django-nose==1.4.
2
edx-rest-api-client==1.
5.0
bok-choy==0.
6.2
ddt==1.
1
.1
django-nose==1.4.
4
edx-rest-api-client==1.
7.1
nose-ignore-docstring==0.2
requests==2.9.1
selenium>=2.53.1
selenium>=3.0.2
e2e/test_coupon_checkout.py
View file @
0135e93c
from
unittest
import
skipUnless
import
ddt
from
bok_choy.web_app_test
import
WebAppTest
from
e2e.config
import
VERIFIED_COURSE_ID
,
ENABLE_CYBERSOURCE_TESTS
from
e2e.constants
import
CODE
,
CYBERSOURCE_DATA1
,
CYBERSOURCE_DATA2
from
e2e.mixins
import
(
CouponMixin
,
EcommerceApiMixin
,
EnrollmentApiMixin
,
LogistrationMixin
,
UnenrollmentMixin
,
PaymentMixin
)
from
e2e.config
import
VERIFIED_COURSE_ID
from
e2e.constants
import
CODE
,
ADDRESS_US
,
ADDRESS_FR
from
e2e.mixins
import
(
CouponMixin
,
EcommerceApiMixin
,
EnrollmentApiMixin
,
LogistrationMixin
,
UnenrollmentMixin
,
PaymentMixin
)
from
e2e.pages.basket
import
BasketPage
from
e2e.pages.coupons
import
CouponsCreatePage
,
CouponsDetailsPage
,
CouponsListPage
,
RedeemVoucherPage
from
e2e.pages.ecommerce
import
EcommerceDashboardHomePage
...
...
@@ -69,8 +67,7 @@ class CouponCheckoutTests(CouponMixin, UnenrollmentMixin, EcommerceApiMixin, Enr
self
.
assert_order_created_and_completed
()
self
.
assert_user_enrolled
(
self
.
username
,
self
.
course_id
,
'verified'
)
@skipUnless
(
ENABLE_CYBERSOURCE_TESTS
,
'CyberSource tests are not enabled.'
)
@ddt.data
(
CYBERSOURCE_DATA1
,
CYBERSOURCE_DATA2
)
@ddt.data
(
ADDRESS_US
,
ADDRESS_FR
)
def
test_discount_checkout_with_cybersource
(
self
,
address
):
""" Test redemption of discount code and purchase of course via Cybersource """
self
.
start_redeem_flow
(
is_discount
=
True
)
...
...
e2e/test_payment.py
View file @
0135e93c
from
abc
import
ABCMeta
from
unittest
import
skip
from
unittest
import
skipUnless
import
ddt
...
...
@@ -6,20 +8,28 @@ from selenium.webdriver.common.by import By
from
selenium.webdriver.support
import
expected_conditions
as
EC
from
selenium.webdriver.support.ui
import
WebDriverWait
from
e2e.config
import
(
VERIFIED_COURSE_ID
,
MARKETING_URL_ROOT
,
PAYPAL_PASSWORD
,
PAYPAL_EMAIL
,
ENABLE_CYBERSOURCE_TESTS
,
BULK_PURCHASE_SKU
)
from
e2e.constants
import
CYBERSOURCE_DATA1
,
CYBERSOURCE_DATA2
from
e2e.config
import
(
VERIFIED_COURSE_ID
,
MARKETING_URL_ROOT
,
PAYPAL_PASSWORD
,
PAYPAL_EMAIL
,
BULK_PURCHASE_SKU
)
from
e2e.constants
import
ADDRESS_US
,
ADDRESS_FR
from
e2e.mixins
import
(
LogistrationMixin
,
EnrollmentApiMixin
,
EcommerceApiMixin
,
PaymentMixin
,
UnenrollmentMixin
)
from
e2e.pages.basket
import
BasketAddProductPage
from
e2e.pages.lms
import
LMSCourseModePage
from
e2e.pages.marketing
import
MarketingCourseAboutPage
from
e2e.pages.basket
import
BasketAddProductPage
class
BasePaymentTest
(
UnenrollmentMixin
,
EcommerceApiMixin
,
EnrollmentApiMixin
,
LogistrationMixin
,
PaymentMixin
,
WebAppTest
):
__metaclass__
=
ABCMeta
def
setUp
(
self
):
super
(
BasePaymentTest
,
self
)
.
setUp
()
self
.
course_id
=
VERIFIED_COURSE_ID
self
.
username
,
self
.
password
,
self
.
email
=
self
.
get_lms_user
()
self
.
basket_add_product_page
=
BasketAddProductPage
(
self
.
browser
)
@ddt.ddt
class
VerifiedCertificatePaymentTests
(
UnenrollmentMixin
,
EcommerceApiMixin
,
EnrollmentApiMixin
,
LogistrationMixin
,
PaymentMixin
,
WebAppTest
):
class
VerifiedCertificatePaymentTests
(
BasePaymentTest
):
def
setUp
(
self
):
super
(
VerifiedCertificatePaymentTests
,
self
)
.
setUp
()
self
.
course_id
=
VERIFIED_COURSE_ID
...
...
@@ -45,10 +55,37 @@ class VerifiedCertificatePaymentTests(UnenrollmentMixin, EcommerceApiMixin, Enro
course_modes_page
=
LMSCourseModePage
(
self
.
browser
,
self
.
course_id
)
course_modes_page
.
visit
()
# Click the purchase button on the track selection page to take
# the browser to the payment selection page.
# Click the purchase button on the track selection page to take the browser to the payment selection page.
self
.
browser
.
find_element_by_css_selector
(
'input[name=verified_mode]'
)
.
click
()
@ddt.data
(
ADDRESS_US
,
ADDRESS_FR
)
def
test_checkout_with_credit_card
(
self
,
address
):
""" Test the client-side checkout page.
We use a U.S. address and a French address since the checkout page requires a state for the U.S. and
Canada, but not for other countries.
"""
self
.
_start_checkout
()
self
.
checkout_with_credit_card
(
address
)
self
.
assert_receipt_page_loads
()
self
.
assert_order_created_and_completed
()
self
.
assert_user_enrolled
(
self
.
username
,
self
.
course_id
,
'verified'
)
@skip
(
'See ECOM-7298.'
)
def
test_paypal
(
self
):
""" Test checkout with PayPal. """
self
.
_start_checkout
()
self
.
checkout_with_paypal
()
self
.
assert_receipt_page_loads
()
self
.
assert_order_created_and_completed
()
self
.
assert_user_enrolled
(
self
.
username
,
self
.
course_id
,
'verified'
)
@ddt.ddt
@skipUnless
(
BULK_PURCHASE_SKU
,
'A bulk purchase SKU must be provided to run bulk purchase tests!'
)
class
BulkSeatPaymentTests
(
BasePaymentTest
):
def
_start_bulk_seat_checkout
(
self
):
""" Begin the checkout process for a verified certificate. """
self
.
login_with_lms
(
self
.
email
,
self
.
password
)
...
...
@@ -61,43 +98,16 @@ class VerifiedCertificatePaymentTests(UnenrollmentMixin, EcommerceApiMixin, Enro
# the browser to the payment selection page.
self
.
browser
.
find_element_by_css_selector
(
'button[id=paypal]'
)
.
click
()
@skipUnless
(
ENABLE_CYBERSOURCE_TESTS
,
'CyberSource tests are not enabled.'
)
@ddt.data
(
CYBERSOURCE_DATA1
,
CYBERSOURCE_DATA2
)
def
test_cybersource
(
self
,
address
):
""" Test checkout with CyberSource. """
self
.
_start_checkout
()
self
.
checkout_with_cybersource
(
address
)
self
.
assert_receipt_page_loads
()
self
.
assert_order_created_and_completed
()
self
.
assert_user_enrolled
(
self
.
username
,
self
.
course_id
,
'verified'
)
@skipUnless
(
ENABLE_CYBERSOURCE_TESTS
and
BULK_PURCHASE_SKU
,
'CyberSource tests are not enabled, or Bulk Purchase SKU not provided, skipping Bulk Purchase tests.'
)
@ddt.data
(
CYBERSOURCE_DATA1
,
CYBERSOURCE_DATA2
)
def
test_bulk_seat_purchase_cybersource
(
self
,
address
):
def
test_bulk_seat_purchase_with_credit_card
(
self
):
""" Test bulk seat purchase checkout with CyberSource. """
self
.
_start_bulk_seat_checkout
()
self
.
browser
.
find_element_by_css_selector
(
'button[id=cybersource]'
)
.
click
()
self
.
checkout_with_c
ybersource
(
address
)
self
.
checkout_with_c
redit_card
(
ADDRESS_US
)
self
.
assert_receipt_page_loads
()
self
.
assert_user_not_enrolled
(
self
.
username
,
self
.
course_id
)
def
test_paypal
(
self
):
""" Test checkout with PayPal. """
if
not
(
PAYPAL_EMAIL
and
PAYPAL_PASSWORD
):
self
.
fail
(
'No PayPal credentials supplied!'
)
self
.
_start_checkout
()
self
.
checkout_with_paypal
()
self
.
assert_receipt_page_loads
()
self
.
assert_order_created_and_completed
()
self
.
assert_user_enrolled
(
self
.
username
,
self
.
course_id
,
'verified'
)
@skipUnless
(
BULK_PURCHASE_SKU
,
'Bulk Purchase SKU not provided, skipping Bulk Purchase tests.'
)
def
test_bulk_seat_purchase_paypal
(
self
):
def
test_bulk_seat_purchase_with_paypal
(
self
):
""" Test bulk seat purchase checkout with PayPal. """
if
not
(
PAYPAL_EMAIL
and
PAYPAL_PASSWORD
):
self
.
fail
(
'No PayPal credentials supplied!'
)
...
...
requirements/test.txt
View file @
0135e93c
# Packages required for testing
-r base.txt
bok-choy==0.
4.7
coverage==4.
2
ddt==1.
0.0
django-nose==1.4.
2
bok-choy==0.
6.2
coverage==4.
3.4
ddt==1.
1.1
django-nose==1.4.
4
factory-boy==2.8.1
freezegun==0.3.7
httpretty==0.8.14
...
...
Write
Preview
Markdown
is supported
0%
Try again
or
attach a new file
Attach a file
Cancel
You are about to add
0
people
to the discussion. Proceed with caution.
Finish editing this message first!
Cancel
Please
register
or
sign in
to comment