From 5c545e32448ddcd46716ab1fc0d20610504ddd21 Mon Sep 17 00:00:00 2001 From: Joren Van Onder Date: Thu, 21 Apr 2016 09:44:45 +0200 Subject: [PATCH] [IMP] point_of_sale: rewrite frontend tests There used to be some acceptance tests but they have not actually been run since 8.0. These acceptance tests are very useful for the POS because the POS is a somewhat unusual module and it happens occasionally that a change in another module unintentionally breaks the POS. Another reason these tests are nice to have is because the POS duplicates a lot of calculations in JS because it's meant to be able to work offline (eg. calculation of taxes, orderline totals etc.). Subtle differences (eg. rounding) between the frontend JS calculations and the backend calculations make up the most common category of bugs in the POS. These frontend tests will make it easy to write tests for these scenario's. When phantom_js finishes we look through all the received pos.order records and see that all of them are paid. If there would be a difference between frontend and backend calculations the order would not have the paid status because the payment on the order would be different from the total. An example test for a previous bug has been included. This will also come in handy for testing the upcoming pricelist functionality that will be implemented in the POS. --- addons/point_of_sale/static/src/js/tests.js | 195 +++++++++++--------- addons/point_of_sale/test/test_frontend.py | 8 - addons/point_of_sale/tests/__init__.py | 3 +- addons/point_of_sale/tests/test_frontend.py | 61 ++++++ 4 files changed, 175 insertions(+), 92 deletions(-) delete mode 100644 addons/point_of_sale/test/test_frontend.py create mode 100644 addons/point_of_sale/tests/test_frontend.py diff --git a/addons/point_of_sale/static/src/js/tests.js b/addons/point_of_sale/static/src/js/tests.js index 6be47e8ff10..e73a5b8eed8 100644 --- a/addons/point_of_sale/static/src/js/tests.js +++ b/addons/point_of_sale/static/src/js/tests.js @@ -1,90 +1,119 @@ odoo.define('point_of_sale.Tour', function (require) { -"use strict"; + "use strict"; -var Tour = require('web.Tour'); + var Tour = require('web.Tour'); -Tour.register({ - id: 'pos_basic_order', - name: 'Complete a basic order trough the Front-End', - path: '/web#model=pos.config&action=point_of_sale.action_pos_config_kanban', - mode: 'test', - steps: [ - { - title: 'Wait fot the bloody screen to be ready', - wait: 200, - }, - { - title: 'Load the Session', - waitNot: '.oe_loading:visible', - element: 'span:contains("Resume Session"),span:contains("Start Session")', - }, - { - title: 'Loading the Loading Screen', + function add_product_to_order(product_name) { + return [{ + title: 'buy ' + product_name, + element: '.product-list .product-name:contains("' + product_name + '")', + }, { + title: 'the ' + product_name + ' have been added to the order', + waitFor: '.order .product-name:contains("' + product_name + '")', + }]; + } + + function generate_keypad_steps(amount_str, keypad_selector) { + var i, steps = [], current_char; + for (i = 0; i < amount_str.length; ++i) { + current_char = amount_str[i]; + steps.push({ + title: 'press ' + current_char + ' on payment keypad', + element: keypad_selector + ' .input-button:contains("' + current_char + '"):visible' + }); + } + + return steps; + } + + function generate_payment_screen_keypad_steps(amount_str) { + return generate_keypad_steps(amount_str, '.payment-numpad'); + } + + function generate_product_screen_keypad_steps(amount_str) { + return generate_keypad_steps(amount_str, '.numpad'); + } + + function verify_order_total(total_str) { + return [{ + title: 'order total contains ' + total_str, + waitFor: '.order .total .value:contains("' + total_str + '")', + }]; + } + + function goto_payment_screen_and_select_payment_method() { + return [{ + title: "go to payment screen", + element: '.button.pay', + }, { + title: "pay with cash", + element: '.paymentmethod:contains("Cash")', + }]; + } + + function finish_order() { + return [{ + title: "validate the order", + element: '.button.next:visible', + }, { + title: "verify that the order is being sent to the backend", + waitFor: ".js_connecting:visible", + }, { + title: "verify that the order has been succesfully sent to the backend", + waitFor: ".js_connected:visible", + }, { + title: "next order", + element: '.button.next:visible', + }]; + } + + var steps = [{ + title: 'wait for loading screen', waitFor: '.loader' - }, - { - title: 'Waiting for the end of loading...', + }, { + title: 'waiting for loading to finish', waitFor: '.loader:hidden', - }, - { - title: 'Loading The Point of Sale', - waitFor: '.pos', - }, - { - title: 'On va manger des CHIPS!', - element: '.product-list .product-name:contains("250g Lays Pickels")', - }, - { - title: 'The chips have been added to the Order', - waitFor: '.order .product-name:contains("250g Lays Pickels")', - }, - { - title: 'The order total has been updated to the correct value', - wait: 2000, - waitFor: '.order .total .value:contains("1.48 €")', - }, - { - title: "Let's buy more chips", - element: '.product-list .product-name:contains("250g Lays Pickels")', - }, - { - title: "Let's veryify we pay the correct price for two bags of chips", - waitFor: '.order .total .value:contains("2.96 €")', - }, - { - title: "Let's pay with a debit card", - element: ".paypad-button:contains('Bank')", - }, - { - title: "Let's accept the payment", - onload: function(){ - // The test cannot validate or cancel the print() ... so we replace it by a noop !. - window._print = window.print; - window.print = function(){ console.log('Print!') }; - }, - element: ".button .iconlabel:contains('Validate'):visible", - }, - { - title: "Let's finish the order", - element: ".button:not(.disabled) .iconlabel:contains('Next'):visible", - }, - { - onload: function(){ - window.print = window._print; - window._print = undefined; - }, - title: "Let's wait for the order posting", - waitFor: ".oe_status.js_synch .js_connected:visible", - }, - { - title: "Let's close the Point of Sale", - element: ".header-button:contains('Close')", - }, - { - title: "Wait for the backend to ready itself", - element: 'span:contains("Resume Session"),span:contains("Start Session")', - }, - ], -}); + }]; + + steps = steps.concat(add_product_to_order('Peaches')); + steps = steps.concat(verify_order_total('5.10')); + + steps = steps.concat(add_product_to_order('Peaches')); // buy another kg of peaches + steps = steps.concat(verify_order_total('10.20')); + steps = steps.concat(goto_payment_screen_and_select_payment_method()); + steps = steps.concat(generate_payment_screen_keypad_steps("12.20")); + + steps = steps.concat([{ + title: "verify tendered", + waitFor: '.col-tendered:contains("12.20")', + }, { + title: "verify change", + waitFor: '.col-change:contains("2.00")', + }]); + + steps = steps.concat(finish_order()); + + // test opw-672118 orderline subtotal rounding + steps = steps.concat(add_product_to_order('Peaches')); + steps = steps.concat(generate_product_screen_keypad_steps('.999')); // sets orderline qty + steps = steps.concat(verify_order_total('5.09')); + steps = steps.concat(goto_payment_screen_and_select_payment_method()); + steps = steps.concat(generate_payment_screen_keypad_steps("10")); + steps = steps.concat(finish_order()); + + steps = steps.concat([{ + title: "close the Point of Sale frontend", + element: ".header-button", + }, { + title: "confirm closing the frontend", + element: ".header-button", + }]); + + Tour.register({ + id: 'pos_basic_order', + name: 'Complete a basic order trough the Front-End', + path: '/pos/web', + steps: steps, + }); }); diff --git a/addons/point_of_sale/test/test_frontend.py b/addons/point_of_sale/test/test_frontend.py deleted file mode 100644 index cc5aa78b35f..00000000000 --- a/addons/point_of_sale/test/test_frontend.py +++ /dev/null @@ -1,8 +0,0 @@ - -import openerp.tests - -@openerp.tests.common.at_install(False) -@openerp.tests.common.post_install(True) -class TestUi(openerp.tests.HttpCase): - def test_01_pos_basic_order(self): - self.phantom_js("/", "odoo.__DEBUG__.services['web.Tour'].run('pos_basic_order', 'test')", "odoo.__DEBUG__.services['web.Tour'].tours.pos_basic_order", login="admin") diff --git a/addons/point_of_sale/tests/__init__.py b/addons/point_of_sale/tests/__init__.py index 676a99e4a6a..9a85a649161 100644 --- a/addons/point_of_sale/tests/__init__.py +++ b/addons/point_of_sale/tests/__init__.py @@ -1,4 +1,5 @@ # -*- coding: utf-8 -*- # Part of Odoo. See LICENSE file for full copyright and licensing details. -from . import test_point_of_sale_flow +import test_point_of_sale_flow +import test_frontend diff --git a/addons/point_of_sale/tests/test_frontend.py b/addons/point_of_sale/tests/test_frontend.py new file mode 100644 index 00000000000..0637d471a3a --- /dev/null +++ b/addons/point_of_sale/tests/test_frontend.py @@ -0,0 +1,61 @@ +import odoo.tests +from openerp.api import Environment + +class TestUi(odoo.tests.HttpCase): + def test_01_pos_basic_order(self): + cr = self.registry.cursor() + assert cr == self.registry.test_cr + env = Environment(cr, self.uid, {}) + + journal_obj = env['account.journal'] + account_obj = env['account.account'] + main_company = env.ref('base.main_company') + main_pos_config = env.ref('point_of_sale.pos_config_main') + + account_receivable = account_obj.create({'code': 'X1012', + 'name': 'Account Receivable - Test', + 'user_type_id': env.ref('account.data_account_type_receivable').id, + 'reconcile': True}) + field = self.env['ir.model.fields'].search([('name', '=', 'property_account_receivable_id'), + ('model', '=', 'res.partner'), + ('relation', '=', 'account.account')], limit=1) + env['ir.property'].create({'name': 'property_account_receivable_id', + 'company_id': main_company.id, + 'fields_id': field.id, + 'value': 'account.account,' + str(account_receivable.id)}) + + # set the company currency to USD, otherwise it will assume + # euro's. this will cause issues as the sale journal is in + # USD, because of this all products would have a different + # price + main_company.currency_id = env.ref('base.USD') + + test_sale_journal = journal_obj.create({'name': 'Sale Journal - Test', + 'code': 'TSJ', + 'type': 'sale', + 'company_id': main_company.id}) + + main_pos_config.journal_id = test_sale_journal + main_pos_config.write({'journal_ids': [(0, 0, {'name': 'Cash Journal - Test', + 'code': 'TSC', + 'type': 'cash', + 'company_id': main_company.id, + 'journal_user': True})]}) + + # open a session, the /pos/web controller will redirect to it + main_pos_config.open_session_cb() + + # needed because tests are run before the module is marked as + # installed. In js web will only load qweb coming from modules + # that are returned by the backend in module_boot. Without + # this you end up with js, css but no qweb. + env['ir.module.module'].search([('name', '=', 'point_of_sale')], limit=1).state = 'installed' + cr.release() + + self.phantom_js("/pos/web", + "odoo.__DEBUG__.services['web.Tour'].run('pos_basic_order', 'test')", + "odoo.__DEBUG__.services['web.Tour'].tours.pos_basic_order", + login="admin") + + for order in env['pos.order'].search([]): + self.assertEqual(order.state, 'paid', "Validated order has payment of " + str(order.amount_paid) + " and total of " + str(order.amount_total))