diff --git a/modules/hsbchk/__init__.py b/modules/hsbchk/__init__.py new file mode 100644 index 0000000000000000000000000000000000000000..a34dd8936fd6677d665acb31bc42a749e2a4f434 --- /dev/null +++ b/modules/hsbchk/__init__.py @@ -0,0 +1,3 @@ +from .module import HSBCHKModule + +__all__ = ['HSBCHKModule'] diff --git a/modules/hsbchk/browser.py b/modules/hsbchk/browser.py new file mode 100755 index 0000000000000000000000000000000000000000..bb3b017ddc406ebf50d835fb24ce20c339d797b0 --- /dev/null +++ b/modules/hsbchk/browser.py @@ -0,0 +1,165 @@ +# -*- coding: utf-8 -*- + +# Copyright(C) 2012-2013 Romain Bignon +# +# This file is part of a weboob module. +# +# This weboob module is free software: you can redistribute it and/or modify +# it under the terms of the GNU Lesser General Public License as published by +# the Free Software Foundation, either version 3 of the License, or +# (at your option) any later version. +# +# This weboob module is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU Lesser General Public License for more details. +# +# You should have received a copy of the GNU Lesser General Public License +# along with this weboob module. If not, see . + +from __future__ import unicode_literals + +from datetime import timedelta, date, datetime +from dateutil import parser + +from weboob.exceptions import NoAccountsException +from weboob.browser import PagesBrowser, URL, need_login, StatesMixin +from weboob.browser.selenium import SubSeleniumMixin +from weboob.browser.exceptions import LoggedOut, ClientError + +from .pages.account_pages import ( + OtherPage, JsonAccSum, JsonAccHist +) + +from .sbrowser import LoginBrowser + +__all__ = ['HSBCHK'] + + +class HSBCHK(StatesMixin, SubSeleniumMixin, PagesBrowser): + BASEURL = 'https://www.services.online-banking.hsbc.com.hk/gpib/group/gpib/cmn/layouts/default.html?uid=dashboard' + + STATE_DURATION = 15 + + app_gone = False + + acc_summary = URL(r'https://www.services.online-banking.hsbc.com.hk/gpib/channel/proxy/accountDataSvc/rtrvAcctSumm', JsonAccSum) + acc_history = URL('https://www.services.online-banking.hsbc.com.hk/gpib/channel/proxy/accountDataSvc/rtrvTxnSumm', JsonAccHist) + + # catch-all + other_page = URL( + r' https://www.services.online-banking.hsbc.com.hk/gpib/systemErrorRedirect.html.*', + OtherPage) + + __states__ = ('auth_token', 'logged', 'selenium_state') + + def __init__(self, username, password, secret, *args, **kwargs): + super(HSBCHK, self).__init__(*args, **kwargs) + # accounts index changes at each session + self.accounts_dict_idx = None + self.username = username + self.password = password + self.secret = secret + self.logged = False + self.auth_token = None + + def create_selenium_browser(self): + dirname = self.responses_dirname + if dirname: + dirname += '/selenium' + + return LoginBrowser( + self.username, + self.password, + self.secret, + logger=self.logger, + responses_dirname=dirname, + proxy=self.PROXIES + ) + + def load_selenium_session(self, selenium): + super(HSBCHK, self).load_selenium_session(selenium) + self.location( + selenium.url, + referrer="https://www.security.online-banking.hsbc.com.hk/gsa/SaaS30Resource/" + ) + + def load_state(self, state): + if ('expire' in state and parser.parse(state['expire']) > datetime.now()) or state.get('auth_token'): + return super(HSBCHK, self).load_state(state) + + def open(self, *args, **kwargs): + try: + return super(HSBCHK, self).open(*args, **kwargs) + except ClientError as e: + if e.response.status_code == 401: + self.auth_token = None + self.logged = False + self.session.cookies.clear() + raise LoggedOut() + if e.response.status_code == 409: + raise NoAccountsException() + raise + + def do_login(self): + self.auth_token = None + self.logger.debug("currrent state is Logged:%s", self.logged) + super(HSBCHK, self).do_login() + self.auth_token = self.session.cookies.get('SYNC_TOKEN') + self.logged = True + + @need_login + def iter_accounts(self): + # on new session initialize accounts dict + if not self.accounts_dict_idx: + self.accounts_dict_idx = dict() + + self.update_header() + jq = {"accountSummaryFilter":{"txnTypCdes":[],"entityCdes":[{"ctryCde":"HK","grpMmbr":"HBAP"}]}} + for a in self.acc_summary.go(json = jq).iter_accounts(): + self.accounts_dict_idx[a.id] = a + yield a + + @need_login + def get_history(self, account, coming=False, retry_li=True): + if not self.accounts_dict_idx: + self.iter_accounts() + + self.update_header() + + today = date.today() + fromdate = today - timedelta(100) + jq = { + "retreiveTxnSummaryFilter": { + "txnDatRnge": { + "fromDate": fromdate.isoformat(), + "toDate": today.isoformat() + }, + "numOfRec": -1, + "txnAmtRnge": None, + "txnHistType": None # "U" + }, + "acctIdr": { + "acctIndex": self.accounts_dict_idx[account.id]._idx, + "entProdTypCde": account._entProdTypCde, + "entProdCatCde": account._entProdCatCde + }, + "pagingInfo": { + "startDetail": None, + "pagingDirectionCode": "PD" + }, + "extensions": None + } + try: + self.acc_history.go(json = jq) + except NoAccountsException: + return [] + return self.page.iter_history() + + def update_header(self): + self.session.headers.update({ + "Origin":"https://www.services.online-banking.hsbc.com.hk", + "Referer":"https://www.services.online-banking.hsbc.com.hk/gpib/group/gpib/cmn/layouts/default.html?uid=dashboard", + "Content-type":"application/json", + "X-HDR-Synchronizer-Token": self.session.cookies.get('SYNC_TOKEN') + }) diff --git a/modules/hsbchk/favicon.png b/modules/hsbchk/favicon.png new file mode 100644 index 0000000000000000000000000000000000000000..d9c389378edb506f2f54118cac615b5c868830e2 Binary files /dev/null and b/modules/hsbchk/favicon.png differ diff --git a/modules/hsbchk/module.py b/modules/hsbchk/module.py new file mode 100755 index 0000000000000000000000000000000000000000..d78cc669d9a7da9523915ead9c320f9a496385f7 --- /dev/null +++ b/modules/hsbchk/module.py @@ -0,0 +1,67 @@ +# -*- coding: utf-8 -*- + +# Copyright(C) 2012-2013 Romain Bignon +# +# This file is part of a weboob module. +# +# This weboob module is free software: you can redistribute it and/or modify +# it under the terms of the GNU Lesser General Public License as published by +# the Free Software Foundation, either version 3 of the License, or +# (at your option) any later version. +# +# This weboob module is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU Lesser General Public License for more details. +# +# You should have received a copy of the GNU Lesser General Public License +# along with this weboob module. If not, see . + + +#from weboob.capabilities.bank import CapBankWealth, AccountNotFound +from weboob.capabilities.bank import CapBank, AccountNotFound +from weboob.capabilities.base import find_object +from weboob.tools.backend import Module, BackendConfig +from weboob.tools.value import ValueBackendPassword +from .browser import HSBCHK + + +__all__ = ['HSBCHKModule'] + + +class HSBCHKModule(Module, CapBank): + NAME = 'hsbchk' + MAINTAINER = u'sinopsysHK' + EMAIL = 'sinofwd@gmail.com' + VERSION = '1.6' + LICENSE = 'LGPLv3+' + DESCRIPTION = 'HSBC Hong Kong' + CONFIG = BackendConfig(ValueBackendPassword('login', label='User identifier', masked=False), + ValueBackendPassword('password', label='Password'), + ValueBackendPassword('secret', label=u'Memorable answer')) + BROWSER = HSBCHK + + def create_default_browser(self): + return self.create_browser( + self.config['login'].get(), + self.config['password'].get(), + self.config['secret'].get() + ) + + def iter_accounts(self): + for account in self.browser.iter_accounts(): + yield account + + def get_account(self, _id): + return find_object(self.browser.iter_accounts(), id=_id, error=AccountNotFound) + + def iter_history(self, account): + for tr in self.browser.get_history(account): + yield tr + + def iter_investment(self, account): + raise NotImplemented + + def iter_coming(self, account): + # No coming entries on HSBC HK + return [] diff --git a/modules/hsbchk/pages/__init__.py b/modules/hsbchk/pages/__init__.py new file mode 100644 index 0000000000000000000000000000000000000000..e69de29bb2d1d6434b8b29ae775ad8c2e48c5391 diff --git a/modules/hsbchk/pages/account_pages.py b/modules/hsbchk/pages/account_pages.py new file mode 100755 index 0000000000000000000000000000000000000000..2e5bc308b522428931805d185ab7d27a8e864ea6 --- /dev/null +++ b/modules/hsbchk/pages/account_pages.py @@ -0,0 +1,205 @@ +# -*- coding: utf-8 -*- + +# Copyright(C) 2010-2012 Julien Veyssier +# +# This file is part of a weboob module. +# +# This weboob module is free software: you can redistribute it and/or modify +# it under the terms of the GNU Lesser General Public License as published by +# the Free Software Foundation, either version 3 of the License, or +# (at your option) any later version. +# +# This weboob module is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU Lesser General Public License for more details. +# +# You should have received a copy of the GNU Lesser General Public License +# along with this weboob module. If not, see . + +from __future__ import unicode_literals + +import re +from decimal import Decimal +import requests +import json + +from weboob.browser.elements import DictElement, ItemElement, method +from weboob.browser.filters.json import Dict +from weboob.browser.filters.standard import ( + CleanDecimal, CleanText, Date +) +from weboob.browser.pages import HTMLPage, LoggedPage, pagination, JsonPage +from weboob.capabilities.bank import Account +from weboob.exceptions import ActionNeeded, BrowserIncorrectPassword +from weboob.tools.capabilities.bank.transactions import FrenchTransaction + + +class Transaction(FrenchTransaction): + PATTERNS = [ + (re.compile(r'^PAYMENT - THANK YOU'), FrenchTransaction.TYPE_CARD_SUMMARY), + (re.compile(r'^CREDIT CARD PAYMENT (?P.*)'), FrenchTransaction.TYPE_CARD_SUMMARY), + (re.compile(r'^CREDIT INTEREST'), FrenchTransaction.TYPE_BANK), + (re.compile(r'DEBIT INTEREST'), FrenchTransaction.TYPE_BANK), + (re.compile(r'^UNAUTHORIZED OD CHARGE'), FrenchTransaction.TYPE_BANK), + (re.compile(r'^ATM WITHDRAWAL\ *\((?P
\d{2})(?P\w{3})(?P\d{2})\)'), FrenchTransaction.TYPE_WITHDRAWAL), + (re.compile(r'^POS CUP\ *\((?P
\d{2})(?P\w{3})(?P\d{2})\)\ *(?P.*)'), FrenchTransaction.TYPE_CARD), + (re.compile(r'^EPS\d*\ *\((?P
\d{2})(?P\w{3})(?P\d{2})\)\ *(?P.*)'), FrenchTransaction.TYPE_CARD), + (re.compile(r'^CR TO (?P.*)\((?P
\d{2})(?P\w{3})(?P\d{2})\)'), FrenchTransaction.TYPE_TRANSFER), + (re.compile(r'^FROM (?P.*)\((?P
\d{2})(?P\w{3})(?P\d{2})\)'), FrenchTransaction.TYPE_TRANSFER), + ] + + +class JsonBasePage(JsonPage): + + def on_load(self): + coderet = Dict('responseInfo/reasons/0/code')(self.doc) + conditions = ( + coderet == '000', + ) + assert any(conditions), 'Error %s is not handled yet' % coderet + + +class JsonAccSum(LoggedPage, JsonBasePage): + + PRODUCT_TO_TYPE = { + u"SAV": Account.TYPE_SAVINGS, + u"CUR": Account.TYPE_CHECKING, + u"TD": Account.TYPE_DEPOSIT, + u"INV": Account.TYPE_MARKET, + u"CC": Account.TYPE_CARD, + } + LABEL = { + u"SAV": "Savings", + u"CUR": "Current", + u"TD": "Time deposit", + u"INV": "Investment", + u"CC": "Credit card", + } + + def get_acc_dict(self, json): + acc_dict = {} + if json.get('hasAcctDetails'): + acc_dict = { + 'bank_name': 'HSBC HK', + 'id': '%s-%s-%s' % ( + json.get('displyID'), + json.get('prodCatCde'), + json.get('ldgrBal').get('ccy') + ), + '_idx': json.get('acctIndex'), + '_entProdCatCde': json.get('entProdCatCde'), + '_entProdTypCde': json.get('entProdTypCde'), + 'number': json.get('displyID'), + 'type': self.PRODUCT_TO_TYPE.get(json.get('prodCatCde')) or Account.TYPE_UNKNOWN, + 'label': '%s %s' % ( + self.LABEL.get(json.get('prodCatCde')), + json.get('ldgrBal').get('ccy') + ), + 'currency': json.get('ldgrBal').get('ccy'), + 'balance': Decimal(json.get('ldgrBal').get('amt')), + } + else: + acc_dict = { + 'bank_name': 'HSBC HK', + 'id': '%s-%s' % ( + json.get('displyID'), + json.get('prodCatCde') + ), + '_idx': json.get('acctIndex'), + 'number': json.get('displyID'), + 'type': self.PRODUCT_TO_TYPE.get(json.get('prodCatCde')) or Account.TYPE_UNKNOWN, + 'label': '%s' % ( + self.LABEL.get(json.get('prodCatCde')) + ) + } + return acc_dict + + def iter_accounts(self): + + for country in self.get('countriesAccountList'): + for acc in country.get('acctLiteWrapper'): + acc_prod = acc.get('prodCatCde') + if acc_prod == "MST": + for subacc in acc.get('subAcctInfo'): + res = Account.from_dict(self.get_acc_dict(subacc)) + if subacc.get('hasAcctDetails'): + yield res + else: + self.logger.debug("skip account with no history: %s", res) + elif acc_prod == "CC": + self.logger.debug("acc: %s", str(acc)) + res = Account.from_dict(self.get_acc_dict(acc)) + if acc.get('hasAcctDetails'): + yield res + else: + self.logger.debug("skip account with no history: %s", res) + else: + self.logger.error("Unknown account product code [%s]", acc_prod) + + +class JsonAccHist(LoggedPage, JsonBasePage): + @pagination + @method + class iter_history(DictElement): + + item_xpath = "txnSumm" + + def next_page(self): + self.logger.debug( + "paging (%s): %s", + Dict('responsePagingInfo/moreRecords', default='N')(self.page.doc), + self.page.doc.get('responsePagingInfo')) + if Dict('responsePagingInfo/moreRecords', default='N')(self.page.doc) == 'Y': + self.logger.info("more values are available") + """ + prev_req = self.page.response.request + jq = json.loads(prev_req.body) + jq['pagingInfo']['startDetail']=Dict('responsePagingInfo/endDetail')(self.page.doc) + return requests.Request( + self.page.url, + headers = prev_req.headers, + json = jq + ) + """ + return + + class item(ItemElement): + klass = Transaction + + obj_rdate = Date(Dict('txnDate')) + obj_date = Date(Dict('txnPostDate')) + obj_amount = CleanDecimal(Dict('txnAmt/amt')) + + def obj_raw(self): + return Transaction.Raw(Dict('txnDetail/0'))(self) + + def obj_type(self): + for pattern, type in Transaction.PATTERNS: + if pattern.match(Dict('txnDetail/0')(self)): + return type + + if Dict('txnHistType', default=None)(self) in ['U', 'B']: + return Transaction.TYPE_CARD + return Transaction.TYPE_TRANSFER + + +class AppGonePage(HTMLPage): + def on_load(self): + self.browser.app_gone = True + self.logger.info('Application has gone. Relogging...') + self.browser.do_logout() + self.browser.do_login() + + +class OtherPage(HTMLPage): + ERROR_CLASSES = [ + ('Votre contrat est suspendu', ActionNeeded), + ("Vos données d'identification (identifiant - code secret) sont incorrectes", BrowserIncorrectPassword), + ('Erreur : Votre contrat est clôturé.', ActionNeeded), + ] + + def on_load(self): + for msg, exc in self.ERROR_CLASSES: + for tag in self.doc.xpath('//p[@class="debit"]//strong[text()[contains(.,$msg)]]', msg=msg): + raise exc(CleanText('.')(tag)) diff --git a/modules/hsbchk/pages/login.py b/modules/hsbchk/pages/login.py new file mode 100755 index 0000000000000000000000000000000000000000..ab6530d283f94d24a29ac0108e3bc5303ed1efcd --- /dev/null +++ b/modules/hsbchk/pages/login.py @@ -0,0 +1,84 @@ +# -*- coding: utf-8 -*- + +# Copyright(C) 2010-2012 Julien Veyssier +# +# This file is part of a weboob module. +# +# This weboob module is free software: you can redistribute it and/or modify +# it under the terms of the GNU Lesser General Public License as published by +# the Free Software Foundation, either version 3 of the License, or +# (at your option) any later version. +# +# This weboob module is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU Lesser General Public License for more details. +# +# You should have received a copy of the GNU Lesser General Public License +# along with this weboob module. If not, see . + +from __future__ import unicode_literals + +from weboob.browser.filters.standard import ( + CleanText +) +from weboob.browser.selenium import ( + SeleniumPage, VisibleXPath +) +from weboob.exceptions import BrowserIncorrectPassword, BrowserUnavailable + +from selenium.webdriver.common.by import By + + +class LoginPage(SeleniumPage): + + is_here = VisibleXPath('//h2[text()[contains(.,"Log on to Internet Banking")]]') + + @property + def logged(self): + if self.doc.xpath('//p[contains(text(), "You are now being redirected to your Personal Internet Banking.")]'): + return True + return False + + def on_load(self): + for message in self.doc.xpath('//div[contains(@class, "alertBox")]'): + error_msg = CleanText('.')(message) + if any(msg in error_msg for msg in ['The username you entered doesn\'t match our records. Please try again.', + 'Please enter your memorable answer and password.', + 'The information you entered does not match our records. Please try again.', + 'mot de passe invalide']): + raise BrowserIncorrectPassword(error_msg) + else: + raise BrowserUnavailable(error_msg) + + def get_error(self): + for message in self.doc.xpath('//div[contains(@data-dojo-type, "hsbcwidget/alertBox")]'): + error_msg = CleanText('.')(message) + if any(msg in error_msg for msg in ['The username you entered doesn\'t match our records. Please try again.', + 'Please enter a valid Username.', + 'mot de passe invalide']): + raise BrowserIncorrectPassword(error_msg) + else: + raise BrowserUnavailable(error_msg) + return + + def login(self, login): + self.driver.find_element_by_name("userid").send_keys(login) + self.driver.find_element_by_class_name("submit_input").click() + + def get_no_secure_key(self): + self.driver.find_element_by_xpath('//a[span[contains(text(), "Without Security Device")]]').click() + + def login_w_secure(self, password, secret): + self.driver.find_element_by_name("memorableAnswer").send_keys(secret) + if len(password) < 8: + raise BrowserIncorrectPassword('The password must be at least %d characters' % 8) + elif len(password) > 8: + # HSBC only use 6 first and last two from the password + password = password[:6] + password[-2:] + + elts = self.driver.find_elements(By.XPATH, "//input[@type='password' and contains(@id,'pass')]") + for elt in elts: + if elt.get_attribute('disabled') is None and elt.get_attribute('class') == "smallestInput active": + elt.send_keys(password[int(elt.get_attribute('id')[-1]) - 1]) + self.driver.find_element_by_xpath("//input[@class='submit_input']").click() diff --git a/modules/hsbchk/sbrowser.py b/modules/hsbchk/sbrowser.py new file mode 100755 index 0000000000000000000000000000000000000000..1542a5510e11fcb8c3d0676b642de8d0d25563da --- /dev/null +++ b/modules/hsbchk/sbrowser.py @@ -0,0 +1,146 @@ +# -*- coding: utf-8 -*- + +# Copyright(C) 2012-2013 Romain Bignon +# +# This file is part of a weboob module. +# +# This weboob module is free software: you can redistribute it and/or modify +# it under the terms of the GNU Lesser General Public License as published by +# the Free Software Foundation, either version 3 of the License, or +# (at your option) any later version. +# +# This weboob module is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU Lesser General Public License for more details. +# +# You should have received a copy of the GNU Lesser General Public License +# along with this weboob module. If not, see . + +from __future__ import unicode_literals + +import os + +try: + from selenium import webdriver +except ImportError: + raise ImportError('Please install python-selenium') + +from selenium.webdriver.common.desired_capabilities import DesiredCapabilities +from selenium.webdriver.common.proxy import Proxy, ProxyType +from selenium.common.exceptions import ( + TimeoutException +) +from selenium.webdriver.support.ui import WebDriverWait +from selenium.webdriver.support import expected_conditions as EC + +from weboob.exceptions import BrowserIncorrectPassword, BrowserUnavailable, BrowserHTTPError +from weboob.browser.selenium import (SeleniumBrowser, DirFirefoxProfile, VisibleXPath) +from weboob.browser import URL + +from .pages.login import ( + LoginPage +) + +class LoginBrowser(SeleniumBrowser): + BASEURL = 'https://www.hsbc.com.hk/' + + #DRIVER = webdriver.Remote + + app_gone = False + + preconnection = URL(r'https://www.ebanking.hsbc.com.hk/1/2/logon?LANGTAG=en&COUNTRYTAG=US', LoginPage) + login = URL(r'https://www.security.online-banking.hsbc.com.hk/gsa/SaaS30Resource/*', LoginPage) + + def __init__(self, username, password, secret, *args, **kwargs): + super(LoginBrowser, self).__init__(*args, **kwargs) + self.username = username + self.password = password + self.secret = secret + self.web_space = None + self.home_url = None + + def _build_capabilities(self): + capa = super(LoginBrowser, self)._build_capabilities() + capa['marionette'] = True + return capa + + def _setup_driver(self): + proxy = Proxy() + proxy.proxy_type = ProxyType.DIRECT + if 'http' in self.proxy: + proxy.http_proxy = self.proxy['http'] + if 'https' in self.proxy: + proxy.ssl_proxy = self.proxy['https'] + + capa = self._build_capabilities() + proxy.add_to_capabilities(capa) + + options = self._build_options() + # TODO some browsers don't need headless + # TODO handle different proxy setting? + options.set_headless(self.HEADLESS) + + if self.DRIVER is webdriver.Firefox: + if self.responses_dirname and not os.path.isdir(self.responses_dirname): + os.makedirs(self.responses_dirname) + + options.profile = DirFirefoxProfile(self.responses_dirname) + if self.responses_dirname: + capa['profile'] = self.responses_dirname + self.driver = self.DRIVER(options=options, capabilities=capa) + elif self.DRIVER is webdriver.Chrome: + self.driver = self.DRIVER(options=options, desired_capabilities=capa) + elif self.DRIVER is webdriver.PhantomJS: + if self.responses_dirname: + if not os.path.isdir(self.responses_dirname): + os.makedirs(self.responses_dirname) + log_path = os.path.join(self.responses_dirname, 'selenium.log') + else: + log_path = NamedTemporaryFile(prefix='weboob_selenium_', suffix='.log', delete=False).name + + self.driver = self.DRIVER(desired_capabilities=capa, service_log_path=log_path) + elif self.DRIVER is webdriver.Remote: + # self.HEADLESS = False + # for debugging purpose + self.driver = webdriver.Remote( + command_executor='http://:/wd/hub', + desired_capabilities=DesiredCapabilities.FIREFOX) + else: + raise NotImplementedError() + + if self.WINDOW_SIZE: + self.driver.set_window_size(*self.WINDOW_SIZE) + + + def load_state(self, state): + return + + def do_login(self): + self.logger.debug("start do_login") + + self.app_gone = False + self.preconnection.go() + try: + self.wait_until(VisibleXPath('//h2[text()[contains(.,"Log on to Internet Banking")]]'), timeout=20) + self.page.login(self.username) + self.wait_until_is_here(self.login, 10) + error = self.page.get_error() + if error: + raise BrowserIncorrectPassword(error) + + self.page.get_no_secure_key() + self.wait_until_is_here(self.login, 10) + error = self.page.get_error() + if error: + raise BrowserHTTPError(error) + self.page.login_w_secure(self.password, self.secret) + if self.login.is_here(): + error = self.page.get_error() + if error: + raise BrowserIncorrectPassword(error) + WebDriverWait(self.driver, 20).until(EC.title_contains("My banking")) + + except TimeoutException as e: + self.logger.exception("timeout while login") + raise BrowserUnavailable(e.msg) diff --git a/modules/hsbchk/test.py b/modules/hsbchk/test.py new file mode 100644 index 0000000000000000000000000000000000000000..92e3a6810e31bb6f49a09f268dcdae03f3270553 --- /dev/null +++ b/modules/hsbchk/test.py @@ -0,0 +1,38 @@ +# -*- coding: utf-8 -*- + +# Copyright(C) 2012 Romain Bignon +# +# This file is part of a weboob module. +# +# This weboob module is free software: you can redistribute it and/or modify +# it under the terms of the GNU Lesser General Public License as published by +# the Free Software Foundation, either version 3 of the License, or +# (at your option) any later version. +# +# This weboob module is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU Lesser General Public License for more details. +# +# You should have received a copy of the GNU Lesser General Public License +# along with this weboob module. If not, see . + + +from weboob.tools.test import BackendTest +from weboob.capabilities.bank import Account + +class HSBCHKTest(BackendTest): + MODULE = 'hsbchk' + + def test_hsbchk(self): + l = list(self.backend.iter_accounts()) + if len(l) > 0: + a = l[0] + list(self.backend.iter_history(a)) + +# def test_investments(self): +# life_insurance_accounts = [account for account in self.backend.iter_accounts() if account.type == Account.TYPE_LIFE_INSURANCE] +# investments = {acc.id: list(self.backend.iter_investment(acc)) for acc in life_insurance_accounts} +# for acc in life_insurance_accounts: +# invs = investments[acc.id] +# self.assertLessEquals(sum([inv.valuation for inv in invs]), acc.balance)