diff --git a/tests/E2E/package.json b/tests/E2E/package.json index 91cf27e71cd71..8824a4840e7c3 100644 --- a/tests/E2E/package.json +++ b/tests/E2E/package.json @@ -8,6 +8,7 @@ "start-selenium": "node_modules/selenium-standalone/bin/selenium-standalone start", "high-test": "node_modules/mocha/bin/mocha test/campaigns/high/*", "full-test": "node_modules/mocha/bin/mocha test/campaigns/full/*", + "selenium-test": "node_modules/mocha/bin/mocha test/campaigns/selenium/*", "boom": "node_modules/mocha/bin/mocha test/campaigns/boom/*", "test": "node_modules/mocha/bin/mocha test/campaigns/regular/*", "install-upgrade-test": "node_modules/mocha/bin/mocha test/campaigns/install_upgrade/*", diff --git a/tests/E2E/prepare-shop.php b/tests/E2E/prepare-shop.php new file mode 100644 index 0000000000000..e28cab25c0700 --- /dev/null +++ b/tests/E2E/prepare-shop.php @@ -0,0 +1,155 @@ + + * @copyright 2007-2018 PrestaShop SA + * @license https://opensource.org/licenses/OSL-3.0 Open Software License (OSL 3.0) + * International Registered Trademark & Property of PrestaShop SA + */ + +define('_PS_MODE_DEV_', false); +require(__DIR__.'/../../config/config.inc.php'); + +// useful variables + +$language = Context::getContext()->language; +$shop = Context::getContext()->shop; +$dbPrefix = _DB_PREFIX_; + +// Enable URL rewriting + +function enableURLRewriting() +{ + Configuration::updateValue('PS_REWRITING_SETTINGS', 1); + Tools::generateHtaccess(); +} + +if (!Configuration::get('PS_REWRITING_SETTINGS')) { + enableURLRewriting(); +} + +echo "- URL rewriting enabled\n"; + +//Enable returns + +function enableReturns() +{ + Configuration::updateValue('PS_ORDER_RETURN', 1); +} + +if (!Configuration::get('PS_ORDER_RETURN')) { + enableReturns(); +} + +echo "- Returns enabled\n"; + +//Enable returns + +function enableVouchers() +{ + Configuration::updateValue('PS_CART_RULE_FEATURE_ACTIVE', 1); +} + +if (!CartRule::isFeatureActive()) { + enableVouchers(); +} + +echo "- Vouchers enabled\n"; + +function enableGiftFeature() +{ + Configuration::updateValue('PS_GIFT_WRAPPING', 1); + Configuration::updateValue('PS_GIFT_WRAPPING_PRICE', 5); +} + +enableGiftFeature(); + + +echo "- Gift feature display enabled\n"; + +// Setup modules + +function disableModule($moduleName) +{ + $module = Module::getInstanceByName($moduleName); + $module->disable(); + echo "- module `$moduleName` disabled\n"; +} + +function hookModule($moduleName, $hookName) +{ + $dbPrefix = _DB_PREFIX_; + $module = Module::getInstanceByName($moduleName); + $moduleId = $module->id; + Db::getInstance()->execute( + "DELETE FROM {$dbPrefix}hook_module WHERE id_module=$moduleId" + ); + $module->registerHook($hookName); + echo "- module `$moduleName` hooked to `$hookName`\n"; +} + + +// We need a customizable product: we add a single required text field to the product with id 1. + +$customizableProduct = new Product(1, false, $language->id); + +// Hijack the "_deleteOldLabels" method to remove existing labels +// (shouldn't be any but I want this script to be idempotent) +$refl = new ReflectionClass('Product'); +$meth = $refl->getMethod('_deleteOldLabels'); +$meth->setAccessible(true); +$meth->invoke($customizableProduct); + +// First, create the label +$customizableProduct->createLabels(($fileFields = 0), ($textFields = 1)); +$fields = $customizableProduct->getCustomizationFields(); +$id_customization_field = current(current(current($fields)))['id_customization_field']; +// And inform the product that it has become customizable +$customizableProduct->customizable = 1; +$customizableProduct->text_fields = 1; +$customizableProduct->save(); + +// Then define it. There is unfortunately no API, so we encode the data in $_POST... +$_POST[implode('_', ['label', 1, $id_customization_field, $language->id, $shop->id])] = 'my field'; +$_POST[implode('_', ['require', 1, $id_customization_field])] = true; +$customizableProduct->updateLabels(); + +echo "- added a required customizable text field to product #1\n"; + +// We need 2 languages for some tests +Language::checkAndAddLanguage('fr'); +echo "- added French language just so that we have 2\n"; +$languages = Language::getLanguages(); +echo " Number of languages : ".count($languages)."\n"; + +$order = new Order(5); +$history = new OrderHistory(); +$history->id_order = $order->id; +$history->id_employee = 1; + +$use_existings_payment = false; +if (!$order->hasInvoice()) { + $use_existings_payment = true; +} +$history->changeIdOrderState(5, $order, $use_existings_payment); +$history->add(); +echo "- Order number 5 is now delivered\n"; + +echo "Shop fixtures prepared for tests!\n"; diff --git a/tests/E2E/test/campaigns/common_scenarios/customer.js b/tests/E2E/test/campaigns/common_scenarios/customer.js index 8555af4a06edd..c80cfbcde1e61 100644 --- a/tests/E2E/test/campaigns/common_scenarios/customer.js +++ b/tests/E2E/test/campaigns/common_scenarios/customer.js @@ -1,6 +1,8 @@ const {Menu} = require('../../selectors/BO/menu.js'); const {Customer} = require('../../selectors/BO/customers/customer'); const {accountPage} = require('../../selectors/FO/add_account_page'); +const {productPage} = require('../../selectors/FO/product_page'); +const {CheckoutOrderPage} = require('../../selectors/FO/order_page'); const {BO} = require('../../selectors/BO/customers/index'); let promise = Promise.resolve(); @@ -121,5 +123,15 @@ module.exports = { test('should check the customer "Last name"', () => client.checkAttributeValue(accountPage.lastname_input, 'value', customerData.last_name)); test('should check that the customer "Email" is equal to "' + date_time + customerData.email_address + '"', () => client.checkAttributeValue(accountPage.email_input, 'value', date_time + customerData.email_address)); test('should check that the customer "Birthday" is equal to "' + customerData.birthday.month + '/' + customerData.birthday.day + '/' + customerData.birthday.year + '"', () => client.checkAttributeValue(accountPage.birthday_input, 'value', customerData.birthday.month + '/' + customerData.birthday.day + '/' + customerData.birthday.year, "contain")); + }, + fillGuestInfo: function (message, client) { + test(message, () => { + return promise + .then(() => client.waitAndSetValue(accountPage.firstname_input, "I am")) + .then(() => client.waitAndSetValue(accountPage.lastname_input, "a Guest")) + .then(() => client.waitAndSetValue(accountPage.email_input, "guest@example.com")) + .then(() => client.waitForExistAndClick(accountPage.customer_form_continue_button)) + .then(() => client.waitForVisible(accountPage.checkout_step_complete)); + }); } }; diff --git a/tests/E2E/test/campaigns/common_scenarios/order.js b/tests/E2E/test/campaigns/common_scenarios/order.js index e60c07c6febeb..2f09fc48c8c0f 100644 --- a/tests/E2E/test/campaigns/common_scenarios/order.js +++ b/tests/E2E/test/campaigns/common_scenarios/order.js @@ -33,7 +33,7 @@ module.exports = { test('should set the "Last name" input', () => client.waitAndSetValue(accountPage.lastname_input, data.customer.lastname)); if (authentication === "create_account") { test('should set the "Email" input', () => client.waitAndSetValue(accountPage.new_email_input, data.customer.email.replace("%ID", date_time))); - test('should set the "Password" input', () => client.waitAndSetValue(accountPage.new_password_input, data.customer.password)); + test('should set the "Password" input', () => client.waitAndSetValue(accountPage.password_account_input, data.customer.password)); } else { test('should set the "Email" input', () => client.waitAndSetValue(accountPage.new_email_input, data.customer.email.replace("%ID", '_guest' + date_time))); } @@ -194,5 +194,16 @@ module.exports = { test('should compare both informations', () => client.checkExportedFileInfo(1000)); test('should reset filter', () => client.waitForExistAndClick(ShoppingCart.reset_button)); }, 'order', true); + }, + initCheckout: function (client) { + test('should add some product to cart"', () => { + return promise + .then(() => client.waitForExistAndClick(productPage.cloths_category)) + .then(() => client.waitForExistAndClick(productPage.second_product_clothes_category)) + .then(() => client.waitForExistAndClick(CheckoutOrderPage.add_to_cart_button)) + .then(() => client.waitForVisible(CheckoutOrderPage.blockcart_modal)) + .then(() => client.waitForVisibleAndClick(CheckoutOrderPage.proceed_to_checkout_modal_button)) + .then(() => client.waitForExistAndClick(CheckoutOrderPage.proceed_to_checkout_button)); + }); } }; diff --git a/tests/E2E/test/campaigns/selenium/identity.js b/tests/E2E/test/campaigns/selenium/identity.js new file mode 100644 index 0000000000000..2f19722d7be0f --- /dev/null +++ b/tests/E2E/test/campaigns/selenium/identity.js @@ -0,0 +1,117 @@ +const {AccessPageFO} = require('../../selectors/FO/access_page'); +const {accountPage} = require('../../selectors/FO/add_account_page'); +const customer = require('../common_scenarios/customer'); +const order = require('../common_scenarios/order'); +let promise = Promise.resolve(); + +scenario('Customer Identity', () => { + scenario('Open the browser and access to the FO', client => { + test('should open the browser', () => client.open()); + test('should access to FO', () => client.accessToFO(AccessPageFO)); + test('should change the FO language to english', () => client.changeLanguage()); + }, 'customer'); + scenario('The Customer Identity Page', client => { + test('should show the customer form', () => { + return promise + .then(() => client.scrollWaitForExistAndClick(AccessPageFO.personal_info, 150, 2000)) + .then(() => client.waitAndSetValue(accountPage.signin_email_input, "pub@prestashop.com")) + .then(() => client.waitAndSetValue(accountPage.signin_password_input, "123456789")) + .then(() => client.waitForExistAndClick(AccessPageFO.login_button)) + .then(() => client.isVisible(accountPage.customer_form)) + .then(() => expect(global.isVisible).to.be.true); + }); + test('should refuse to save the customer if the wrong password is provided', () => { + return promise + .then(() => client.waitAndSetValue(accountPage.password_account_input, "wrongPassword")) + .then(() => client.waitForVisibleAndClick(accountPage.save_account_button)) + .then(() => client.waitForVisible(accountPage.danger_alert)) + }); + test('should save the customer if the correct password is provided', () => { + return promise + .then(() => client.waitAndSetValue(accountPage.password_account_input, "123456789")) + .then(() => client.waitForExistAndClick(accountPage.save_account_button)) + .then(() => client.waitForVisible(accountPage.success_alert)); + }); + test('should allow the customer to change their password', () => { + return promise + .then(() => client.waitAndSetValue(accountPage.password_account_input, "123456789")) + .then(() => client.waitAndSetValue(accountPage.new_password_input, "newPassword")) + .then(() => client.waitForExistAndClick(accountPage.save_account_button)) + .then(() => client.waitForVisible(accountPage.success_alert)); + }); + test('should allow the customer to use the new password', () => { + return promise + .then(() => client.waitForExistAndClick(AccessPageFO.sign_out_button)) + .then(() => client.waitAndSetValue(accountPage.signin_email_input, "pub@prestashop.com")) + .then(() => client.waitAndSetValue(accountPage.signin_password_input, "newPassword")) + .then(() => client.waitForExistAndClick(AccessPageFO.login_button)) + .then(() => client.waitAndSetValue(accountPage.password_account_input, "newPassword")) + .then(() => client.waitAndSetValue(accountPage.new_password_input, "123456789")) + .then(() => client.waitForExistAndClick(accountPage.save_account_button)) + .then(() => client.waitForVisible(accountPage.success_alert)); + }); + test('should logout', () => { + return promise + .then(() => client.waitForExistAndClick(AccessPageFO.sign_out_button)) + .then(() => client.waitForExistAndClick(AccessPageFO.logo_home_page)) + .then(() => client.changeLanguage()); + }); + }, 'customer'); + + scenario('The guest form during checkout', () => { + scenario('Add product to cart as a guest', client => { + order.initCheckout(client); + }, 'order'); + + scenario('the form can be filled a first time with an email address', client => { + customer.fillGuestInfo('Fill the personal information step as a guest', client); + }, 'customer'); + + scenario('there can be 2 guests using the same e-mail address', () => { + scenario('Delete cookies', client => { + test('should delete cookies', () => client.deleteCookie()); + test('should click on "Continue shopping', () => client.waitForExistAndClick(accountPage.continue_shopping)); + }, 'customer'); + scenario('Add product to cart as a guest', client => { + order.initCheckout(client); + }, 'order'); + scenario('should let another guest use the same e-mail address', client => { + customer.fillGuestInfo('should let another guest use the same e-mail address', client); + }, 'customer'); + }, 'customer'); + + scenario('Updating the guest account during checkout', client => { + test('should let the guest update their lastname', () => { + return promise + .then(() => client.waitForExistAndClick(accountPage.checkout_step)) + .then(() => client.waitAndSetValue(accountPage.lastname_input, "a Ghost")) + .then(() => client.waitForExistAndClick(accountPage.customer_form_continue_button)) + .then(() => client.waitForVisible(accountPage.checkout_step_complete)); + }); + test('should not let the guest change their email address to that of a real customer', () => { + return promise + .then(() => client.waitForExistAndClick(accountPage.checkout_step)) + .then(() => client.waitAndSetValue(accountPage.email_input, "pub@prestashop.com")) + .then(() => client.waitForExistAndClick(accountPage.customer_form_continue_button)) + .then(() => client.isNotExisting(accountPage.checkout_step_complete)); + }); + test('should let the guest change their email address if not used by a customer', () => { + return promise + .then(() => client.waitAndSetValue(accountPage.email_input, "guest.guest@example.com")) + .then(() => client.waitForExistAndClick(accountPage.customer_form_continue_button)) + .then(() => client.waitForVisible(accountPage.checkout_step_complete)); + }); + test('should let the guest add a password to create an account', () => { + return promise + .then(() => client.waitForExistAndClick(accountPage.checkout_step)) + .then(() => client.waitAndSetValue(accountPage.email_input, "test" + date_time + "@example.com")) + .then(() => client.waitAndSetValue(accountPage.password_account_input, "123456789")) + .then(() => client.waitForExistAndClick(accountPage.customer_form_continue_button)) + .then(() => client.waitForVisible(accountPage.checkout_step_complete)) + .then(() => client.waitForExistAndClick(accountPage.checkout_step)) + .then(() => client.isExisting(accountPage.checkout_step)) + + }); + }, 'customer'); + }, 'customer'); +}, 'customer', true); diff --git a/tests/E2E/test/clients/common_client.js b/tests/E2E/test/clients/common_client.js index ef61482c7fa89..e99db7eb3ff5f 100644 --- a/tests/E2E/test/clients/common_client.js +++ b/tests/E2E/test/clients/common_client.js @@ -505,6 +505,7 @@ class CommonClient { return deca[Math.floor(number / 10) - 2] + 'y-' + special[number % 10]; } + /** * This function searches the data in the table in case a filter input exists * @param selector @@ -538,8 +539,9 @@ class CommonClient { } } - refresh() { + deleteCookie() { return this.client + .deleteCookie() .refresh(); } diff --git a/tests/E2E/test/clients/customer.js b/tests/E2E/test/clients/customer.js index 0312be4cc0cb5..3fbb79e3da759 100644 --- a/tests/E2E/test/clients/customer.js +++ b/tests/E2E/test/clients/customer.js @@ -36,4 +36,4 @@ class Customer extends CommonClient { } -module.exports = Customer; \ No newline at end of file +module.exports = Customer; diff --git a/tests/E2E/test/selectors/FO/access_page.js b/tests/E2E/test/selectors/FO/access_page.js index 78e2b3d1bf407..1b80d41c8ccd6 100644 --- a/tests/E2E/test/selectors/FO/access_page.js +++ b/tests/E2E/test/selectors/FO/access_page.js @@ -47,6 +47,7 @@ module.exports = { display_top_link_widget:'//*[@id="header"]/div[2]/div/div[1]/div[2]/div[3]/div//p[contains(text(),"%DISPLAYTOP")]', second_display_top_link_widget:'//*[@id="header"]/div[2]/div/div[1]/div[2]/div[3]/div/div[2]/p', not_found_error_message: '//*[@id="main"]//h1', - product_name: '//*[@id="js-product-list"]//h2//a[contains(text(),"%PAGENAME")]' + product_name: '//*[@id="js-product-list"]//h2//a[contains(text(),"%PAGENAME")]', + personal_info: '//*[@id="footer_account_list"]//a[@title="Personal info"]' } }; diff --git a/tests/E2E/test/selectors/FO/add_account_page.js b/tests/E2E/test/selectors/FO/add_account_page.js index b609a53615721..7cda15c7548c3 100644 --- a/tests/E2E/test/selectors/FO/add_account_page.js +++ b/tests/E2E/test/selectors/FO/add_account_page.js @@ -1,11 +1,17 @@ module.exports = { accountPage: { create_button: '[data-link-action="display-register-form"]', - firstname_input: '[name="firstname"]', - lastname_input: '[name="lastname"]', - email_input: '//input[contains(@name,"email") and @class="form-control"]', + firstname_input: '//*[@id="customer-form"]//input[@name="firstname"]', + lastname_input: '//*[@id="customer-form"]//input[@name="lastname"]', + email_input: '//*[@id="customer-form"]//input[@name="email"]', password_input: '[name="password"]', birthday_input: '[name="birthday"]', + checkout_step: '//*[@id="checkout-personal-information-step"]/h1', + checkout_step_complete: '#checkout-personal-information-step.-complete', + checkout_step_identity: '#checkout-personal-information-step .identity', + alert_danger_email: '//*[@id="customer-form"]//li[@class="alert alert-danger"]', + continue_shopping: '//*[@id="main"]//div[contains(@class,"cart-grid")]//a', + customer_form_continue_button: '//*[@id="customer-form"]//button[@name="continue"]', save_account_button: '[data-link-action="save-customer"]', gender_radio_button: '(//*[@id="customer-form"]//input[contains(@name,"id_gender")])[2]', account_link: '//*[@id="_desktop_user_info"]//a[@class="account"]', @@ -16,7 +22,7 @@ module.exports = { adr_save: '//*[@id="content"]//footer/button', success_alert: '[data-alert="success"]', adr_update: '[data-link-action="edit-address"]', - //------------------ connect with existing account from checkout ---------------// + //------------------ connect with existing account from checkout ----------------// sign_tab: '//*[@id="checkout-personal-information-step"]//a[contains(text(), "Sign in")]', signin_email_input: '//*[@id="login-form"]//input[@name="email"]', signin_password_input: '//*[@id="login-form"]//input[@name="password"]', @@ -25,7 +31,9 @@ module.exports = { new_customer_btn: '[data-link-action="register-new-customer"]', new_address_btn: '[name="confirm-addresses"]', new_email_input: '//*[@id="customer-form"]//input[@name="email"]', - new_password_input: '//*[@id="customer-form"]//input[@name="password"]', + password_account_input: '//*[@id="customer-form"]//input[@name="password"]', + new_password_input: '//*[@id="customer-form"]//input[@name="new_password"]', + customer_form: '//*[@id="customer-form"]', shipping_continue_btn: '//*[@id="js-delivery"]/button[@name="confirmDeliveryOption"]', pay_by_check: '//*[@id="payment-option-1"]', terms_of_service: '//*[@id="conditions_to_approve[terms-and-conditions]"]', @@ -36,4 +44,4 @@ module.exports = { danger_alert: '//*[@id="customer-form"]//li[contains(@class,"alert-danger")]', add_new_address: '//*[@id="checkout-addresses-step"]//p[@class="add-address"]/a' } -}; \ No newline at end of file +}; diff --git a/tests/E2E/test/selectors/FO/order_page.js b/tests/E2E/test/selectors/FO/order_page.js index 0f5b99b0e83db..e85cec9a3f6f4 100644 --- a/tests/E2E/test/selectors/FO/order_page.js +++ b/tests/E2E/test/selectors/FO/order_page.js @@ -2,6 +2,7 @@ module.exports = { CheckoutOrderPage: { add_to_cart_button: '//*[@id="add-to-cart-or-refresh"]//button[contains(@class, "add-to-cart")]', proceed_to_checkout_modal_button: '//*[@id="blockcart-modal"]//div[@class="cart-content-btn"]//a', + blockcart_modal: '#blockcart-modal', continue_shopping_button: '//*[@id="blockcart-modal"]//div[@class="cart-content-btn"]//button', proceed_to_checkout_button: '//*[@id="main"]//div[contains(@class,"checkout")]//a', promo_code_link: '//*[@id="main"]//a[contains(@class, "promo-code")]', diff --git a/tests/E2E/test/selectors/FO/product_page.js b/tests/E2E/test/selectors/FO/product_page.js index d78321d826b4c..59c74dcad26ab 100644 --- a/tests/E2E/test/selectors/FO/product_page.js +++ b/tests/E2E/test/selectors/FO/product_page.js @@ -31,6 +31,8 @@ module.exports = { product_description: '//*[@id="description"]', product_detail_tab: '//*[@role="tablist"]//li[2]', attachments_tab: '//*[@id="main"]//div[@class="product-information"]//a[@aria-controls="attachments"]', + cloths_category: '//*[@id="category-3"]', + second_product_clothes_category: '//*[@id="js-product-list"]//article[2]', product_footer_linkwidget:'//*[@id="main"]/div[2]//p[contains(text(),"%DISPLAYFOOTERPRODUCT")]', second_product_footer_linkwidget:'//*[@id="main"]/div[2]/div/div[2]/p', widget_after_product_thumbs: '//*[@id="content"]//div[contains(@class,"links")]//p[contains(text(),"%NAME")]',