diff --git a/basicswap/basicswap.py b/basicswap/basicswap.py index a14f77f..9b7856c 100644 --- a/basicswap/basicswap.py +++ b/basicswap/basicswap.py @@ -1,6 +1,6 @@ # -*- coding: utf-8 -*- -# Copyright (c) 2019-2021 tecnovert +# Copyright (c) 2019-2022 tecnovert # Distributed under the MIT software license, see the accompanying # file LICENSE or http://www.opensource.org/licenses/mit-license.php. @@ -326,6 +326,8 @@ class BasicSwap(BaseApp): else: self.thread_pool.shutdown() + self.zmqContext.destroy() + close_all_sessions() self.engine.dispose() @@ -5260,7 +5262,10 @@ class BasicSwap(BaseApp): # Requires? self.mxDB.acquire() try: session = scoped_session(self.session_factory) - inner_str = 'SELECT coin_id, MAX(created_at) as max_created_at FROM wallets GROUP BY coin_id' + where_str = '' + if opts is not None and 'coin_id' in opts: + where_str = 'WHERE coin_id = {}'.format(opts['coin_id']) + inner_str = f'SELECT coin_id, MAX(created_at) as max_created_at FROM wallets {where_str} GROUP BY coin_id' query_str = 'SELECT a.coin_id, wallet_data, created_at FROM wallets a, ({}) b WHERE a.coin_id = b.coin_id AND a.created_at = b.max_created_at'.format(inner_str) q = session.execute(query_str) @@ -5285,6 +5290,9 @@ class BasicSwap(BaseApp): session.close() session.remove() + if opts is not None and 'coin_id' in opts: + return rv + for c in Coins: if c not in chainparams: continue diff --git a/basicswap/chainparams.py b/basicswap/chainparams.py index 7a0d583..d600b26 100644 --- a/basicswap/chainparams.py +++ b/basicswap/chainparams.py @@ -1,6 +1,6 @@ # -*- coding: utf-8 -*- -# Copyright (c) 2019-2021 tecnovert +# Copyright (c) 2019-2022 tecnovert # Distributed under the MIT software license, see the accompanying # file LICENSE or http://www.opensource.org/licenses/mit-license.php. @@ -207,6 +207,20 @@ chainparams = { } +ticker_map = {} + + +for c, params in chainparams.items(): + ticker_map[params['ticker'].lower()] = c + + +def getCoinIdFromTicker(ticker): + try: + return ticker_map[ticker.lower()] + except Exception: + raise ValueError('Unknown coin') + + class CoinInterface: def __init__(self, network): self.setDefaults() @@ -235,6 +249,10 @@ class CoinInterface: ticker = 'rt' + ticker return ticker + def ticker_mainnet(self): + ticker = chainparams[self.coin_type()]['ticker'] + return ticker + def min_amount(self): return chainparams[self.coin_type()][self._network]['min_amount'] diff --git a/basicswap/http_server.py b/basicswap/http_server.py index e99389c..3098de5 100644 --- a/basicswap/http_server.py +++ b/basicswap/http_server.py @@ -1,6 +1,6 @@ # -*- coding: utf-8 -*- -# Copyright (c) 2019-2021 tecnovert +# Copyright (c) 2019-2022 tecnovert # Distributed under the MIT software license, see the accompanying # file LICENSE or http://www.opensource.org/licenses/mit-license.php. @@ -17,11 +17,13 @@ from jinja2 import Environment, PackageLoader from . import __version__ from .util import ( dumpj, + ensure, format_timestamp, ) from .chainparams import ( - chainparams, Coins, + chainparams, + getCoinIdFromTicker, ) from .basicswap_util import ( SwapTypes, @@ -370,16 +372,12 @@ class HttpHandler(BaseHTTPRequestHandler): continue ci = swap_client.ci(k) - fee_rate, fee_src = swap_client.getFeeRateForCoin(k) - est_fee = swap_client.estimateWithdrawFee(k, fee_rate) cid = str(int(k)) wf = { 'name': w['name'], 'version': w['version'], + 'ticker': ci.ticker_mainnet(), 'cid': cid, - 'fee_rate': ci.format_amount(int(fee_rate * ci.COIN())), - 'fee_rate_src': fee_src, - 'est_fee': 'Unknown' if est_fee is None else ci.format_amount(int(est_fee * ci.COIN())), 'balance': w['balance'], 'blocks': w['blocks'], 'synced': w['synced'], @@ -402,21 +400,6 @@ class HttpHandler(BaseHTTPRequestHandler): if float(w['anon_pending']) > 0.0: wf['anon_pending'] = w['anon_pending'] - elif k == Coins.XMR: - wf['main_address'] = w.get('main_address', 'Refresh necessary') - - if 'wd_type_from_' + cid in page_data: - wf['wd_type_from'] = page_data['wd_type_from_' + cid] - if 'wd_type_to_' + cid in page_data: - wf['wd_type_to'] = page_data['wd_type_to_' + cid] - - if 'wd_value_' + cid in page_data: - wf['wd_value'] = page_data['wd_value_' + cid] - if 'wd_address_' + cid in page_data: - wf['wd_address'] = page_data['wd_address_' + cid] - if 'wd_subfee_' + cid in page_data: - wf['wd_subfee'] = page_data['wd_subfee_' + cid] - wallets_formatted.append(wf) template = env.get_template('wallets.html') @@ -428,6 +411,146 @@ class HttpHandler(BaseHTTPRequestHandler): form_id=os.urandom(8).hex(), ), 'UTF-8') + def page_wallet(self, url_split, post_string): + ensure(len(url_split) > 2, 'Wallet not specified') + wallet_ticker = url_split[2] + swap_client = self.server.swap_client + + coin_id = getCoinIdFromTicker(wallet_ticker) + + page_data = {} + messages = [] + form_data = self.checkForm(post_string, 'settings', messages) + if form_data: + cid = str(int(coin_id)) + + if bytes('newaddr_' + cid, 'utf-8') in form_data: + swap_client.cacheNewAddressForCoin(coin_id) + elif bytes('reseed_' + cid, 'utf-8') in form_data: + try: + swap_client.reseedWallet(coin_id) + messages.append('Reseed complete ' + str(coin_id)) + except Exception as ex: + messages.append('Reseed failed ' + str(ex)) + swap_client.updateWalletsInfo(True, coin_id) + elif bytes('withdraw_' + cid, 'utf-8') in form_data: + try: + value = form_data[bytes('amt_' + cid, 'utf-8')][0].decode('utf-8') + page_data['wd_value_' + cid] = value + except Exception as e: + messages.append('Error: Missing value') + try: + address = form_data[bytes('to_' + cid, 'utf-8')][0].decode('utf-8') + page_data['wd_address_' + cid] = address + except Exception as e: + messages.append('Error: Missing address') + + subfee = True if bytes('subfee_' + cid, 'utf-8') in form_data else False + page_data['wd_subfee_' + cid] = subfee + + if coin_id == Coins.PART: + try: + type_from = form_data[bytes('withdraw_type_from_' + cid, 'utf-8')][0].decode('utf-8') + type_to = form_data[bytes('withdraw_type_to_' + cid, 'utf-8')][0].decode('utf-8') + page_data['wd_type_from_' + cid] = type_from + page_data['wd_type_to_' + cid] = type_to + except Exception as e: + messages.append('Error: Missing type') + + if len(messages) == 0: + ci = swap_client.ci(coin_id) + ticker = ci.ticker() + if coin_id == Coins.PART: + try: + txid = swap_client.withdrawParticl(type_from, type_to, value, address, subfee) + messages.append('Withdrew {} {} ({} to {}) to address {}
In txid: {}'.format(value, ticker, type_from, type_to, address, txid)) + except Exception as e: + messages.append('Error: {}'.format(str(e))) + else: + try: + txid = swap_client.withdrawCoin(coin_id, value, address, subfee) + messages.append('Withdrew {} {} to address {}
In txid: {}'.format(value, ticker, address, txid)) + except Exception as e: + messages.append('Error: {}'.format(str(e))) + swap_client.updateWalletsInfo(True, coin_id) + + swap_client.updateWalletsInfo() + wallets = swap_client.getCachedWalletsInfo({'coin_id': coin_id}) + for k in wallets.keys(): + w = wallets[k] + if 'error' in w: + wallet_data = { + 'cid': str(int(k)), + 'error': w['error'] + } + continue + + if 'balance' not in w: + wallet_data = { + 'name': w['name'], + 'havedata': False, + 'updating': w['updating'], + } + continue + + ci = swap_client.ci(k) + fee_rate, fee_src = swap_client.getFeeRateForCoin(k) + est_fee = swap_client.estimateWithdrawFee(k, fee_rate) + cid = str(int(k)) + wallet_data = { + 'name': w['name'], + 'version': w['version'], + 'ticker': ci.ticker_mainnet(), + 'cid': cid, + 'fee_rate': ci.format_amount(int(fee_rate * ci.COIN())), + 'fee_rate_src': fee_src, + 'est_fee': 'Unknown' if est_fee is None else ci.format_amount(int(est_fee * ci.COIN())), + 'balance': w['balance'], + 'blocks': w['blocks'], + 'synced': w['synced'], + 'deposit_address': w['deposit_address'], + 'expected_seed': w['expected_seed'], + 'balance_all': float(w['balance']) + float(w['unconfirmed']), + 'updating': w['updating'], + 'lastupdated': format_timestamp(w['lastupdated']), + 'havedata': True, + } + if float(w['unconfirmed']) > 0.0: + wallet_data['unconfirmed'] = w['unconfirmed'] + + if k == Coins.PART: + wallet_data['stealth_address'] = w['stealth_address'] + wallet_data['blind_balance'] = w['blind_balance'] + if float(w['blind_unconfirmed']) > 0.0: + wallet_data['blind_unconfirmed'] = w['blind_unconfirmed'] + wallet_data['anon_balance'] = w['anon_balance'] + if float(w['anon_pending']) > 0.0: + wallet_data['anon_pending'] = w['anon_pending'] + + elif k == Coins.XMR: + wallet_data['main_address'] = w.get('main_address', 'Refresh necessary') + + if 'wd_type_from_' + cid in page_data: + wallet_data['wd_type_from'] = page_data['wd_type_from_' + cid] + if 'wd_type_to_' + cid in page_data: + wallet_data['wd_type_to'] = page_data['wd_type_to_' + cid] + + if 'wd_value_' + cid in page_data: + wallet_data['wd_value'] = page_data['wd_value_' + cid] + if 'wd_address_' + cid in page_data: + wallet_data['wd_address'] = page_data['wd_address_' + cid] + if 'wd_subfee_' + cid in page_data: + wallet_data['wd_subfee'] = page_data['wd_subfee_' + cid] + + template = env.get_template('wallet.html') + return bytes(template.render( + title=self.server.title, + h2=self.server.title, + messages=messages, + w=wallet_data, + form_id=os.urandom(8).hex(), + ), 'UTF-8') + def page_settings(self, url_split, post_string): swap_client = self.server.swap_client @@ -764,7 +887,7 @@ class HttpHandler(BaseHTTPRequestHandler): ), 'UTF-8') def page_offer(self, url_split, post_string): - assert(len(url_split) > 2), 'Offer ID not specified' + ensure(len(url_split) > 2, 'Offer ID not specified') try: offer_id = bytes.fromhex(url_split[2]) assert(len(offer_id) == 28) @@ -772,7 +895,7 @@ class HttpHandler(BaseHTTPRequestHandler): raise ValueError('Bad offer ID') swap_client = self.server.swap_client offer, xmr_offer = swap_client.getXmrOffer(offer_id) - assert(offer), 'Unknown offer ID' + ensure(offer, 'Unknown offer ID') extend_data = { # Defaults 'nb_validmins': 10, @@ -928,11 +1051,11 @@ class HttpHandler(BaseHTTPRequestHandler): if have_data_entry(form_data, 'sort_by'): sort_by = get_data_entry(form_data, 'sort_by') - assert(sort_by in ['created_at', 'rate']), 'Invalid sort by' + ensure(sort_by in ['created_at', 'rate'], 'Invalid sort by') filters['sort_by'] = sort_by if have_data_entry(form_data, 'sort_dir'): sort_dir = get_data_entry(form_data, 'sort_dir') - assert(sort_dir in ['asc', 'desc']), 'Invalid sort dir' + ensure(sort_dir in ['asc', 'desc'], 'Invalid sort dir') filters['sort_dir'] = sort_dir if form_data and have_data_entry(form_data, 'pageback'): @@ -973,7 +1096,7 @@ class HttpHandler(BaseHTTPRequestHandler): ), 'UTF-8') def page_bid(self, url_split, post_string): - assert(len(url_split) > 2), 'Bid ID not specified' + ensure(len(url_split) > 2, 'Bid ID not specified') try: bid_id = bytes.fromhex(url_split[2]) assert(len(bid_id) == 28) @@ -1023,7 +1146,7 @@ class HttpHandler(BaseHTTPRequestHandler): show_lock_transfers = True bid, xmr_swap, offer, xmr_offer, events = swap_client.getXmrBidAndOffer(bid_id) - assert(bid), 'Unknown bid ID' + ensure(bid, 'Unknown bid ID') data = describeBid(swap_client, bid, xmr_swap, offer, xmr_offer, events, edit_bid, show_txns, view_tx_ind, show_lock_transfers=show_lock_transfers) @@ -1077,11 +1200,11 @@ class HttpHandler(BaseHTTPRequestHandler): if form_data and have_data_entry(form_data, 'applyfilters'): if have_data_entry(form_data, 'sort_by'): sort_by = get_data_entry(form_data, 'sort_by') - assert(sort_by in ['created_at', ]), 'Invalid sort by' + ensure(sort_by in ['created_at', ], 'Invalid sort by') filters['sort_by'] = sort_by if have_data_entry(form_data, 'sort_dir'): sort_dir = get_data_entry(form_data, 'sort_dir') - assert(sort_dir in ['asc', 'desc']), 'Invalid sort dir' + ensure(sort_dir in ['asc', 'desc'], 'Invalid sort dir') filters['sort_dir'] = sort_dir if form_data and have_data_entry(form_data, 'pageback'): @@ -1144,7 +1267,7 @@ class HttpHandler(BaseHTTPRequestHandler): edit_address_id = int(form_data[b'edit_address_id'][0].decode('utf-8')) edit_addr = form_data[b'edit_address'][0].decode('utf-8') active_ind = int(form_data[b'active_ind'][0].decode('utf-8')) - assert(active_ind == 0 or active_ind == 1), 'Invalid sort by' + ensure(active_ind in (0, 1), 'Invalid sort by') addressnote = '' if b'addressnote' not in form_data else form_data[b'addressnote'][0].decode('utf-8') if not validateTextInput(addressnote, 'Address note', messages, max_length=30): listaddresses = False @@ -1196,7 +1319,7 @@ class HttpHandler(BaseHTTPRequestHandler): ), 'UTF-8') def page_identity(self, url_split, post_string): - assert(len(url_split) > 2), 'Address not specified' + ensure(len(url_split) > 2, 'Address not specified') identity_address = url_split[2] swap_client = self.server.swap_client @@ -1333,6 +1456,8 @@ class HttpHandler(BaseHTTPRequestHandler): return self.page_active(url_split, post_string) if url_split[1] == 'wallets': return self.page_wallets(url_split, post_string) + if url_split[1] == 'wallet': + return self.page_wallet(url_split, post_string) if url_split[1] == 'settings': return self.page_settings(url_split, post_string) if url_split[1] == 'rpc': diff --git a/basicswap/templates/wallet.html b/basicswap/templates/wallet.html new file mode 100644 index 0000000..f5f79b0 --- /dev/null +++ b/basicswap/templates/wallet.html @@ -0,0 +1,82 @@ +{% include 'header.html' %} + + +

refresh

+

back

+ +

{{ w.name }} Wallet

+{% if refresh %} +

Page Refresh: {{ refresh }} seconds

+{% endif %} + +{% for m in messages %} +

{{ m }}

+{% endfor %} + +
+ + +{% if w.updating %} +
Updating
+{% endif %} +{% if w.havedata %} +{% if w.error %} +

Error: {{ w.error }}

+{% else %} + + + + +{% if w.unconfirmed %}{% endif %} + +{% if w.cid == '1' %} +{% if w.blind_unconfirmed %}{% endif %} +{% if w.anon_pending %}{% endif %} +{% endif %} + + + + +{% if w.expected_seed != true %}{% endif %} +{% if w.cid == '1' %} + +{% endif %} +{% if w.cid == '6' %} + + +{% else %} + +{% endif %} + +{% if w.cid == '1' %} + +{% endif %} + +
Last updated:{{ w.lastupdated }}
Version:{{ w.version }}
Balance:{{ w.balance }}Unconfirmed:{{ w.unconfirmed }}
Blind Balance:{{ w.blind_balance }}Blind Unconfirmed:{{ w.blind_unconfirmed }}
Anon Balance:{{ w.anon_balance }}Anon Pending:{{ w.anon_pending }}
Blocks:{{ w.blocks }}
Synced:{{ w.synced }}
Expected Seed:{{ w.expected_seed }}
Stealth Address{{ w.stealth_address }}
Main Address{{ w.main_address }}
{{ w.deposit_address }}
{{ w.deposit_address }}
Amount: Address: Subtract fee:
Type From, To + +
Fee Rate:{{ w.fee_rate }}Est Fee:{{ w.est_fee }}
+{% endif %} +{% endif %} + + +
+ +

home

+ + + diff --git a/basicswap/templates/wallets.html b/basicswap/templates/wallets.html index b302f94..c2bbf5f 100644 --- a/basicswap/templates/wallets.html +++ b/basicswap/templates/wallets.html @@ -1,6 +1,6 @@ {% include 'header.html' %} -

refresh

+

refresh

Wallets

{% if refresh %} @@ -35,31 +35,8 @@ Blocks:{{ w.blocks }} Synced:{{ w.synced }} -Expected Seed:{{ w.expected_seed }}{% if w.expected_seed != true %}{% endif %} -{% if w.cid == '1' %} -Stealth Address{{ w.stealth_address }} -{% endif %} -{% if w.cid == '6' %} -Main Address{{ w.main_address }} -{{ w.deposit_address }} -{% else %} -{{ w.deposit_address }} -{% endif %} -Amount: Address: Subtract fee: -{% if w.cid == '1' %} -Type From, To - - -{% endif %} -Fee Rate:{{ w.fee_rate }}Est Fee:{{ w.est_fee }} +Expected Seed:{{ w.expected_seed }} +Manage {% endif %} {% endif %} diff --git a/bin/basicswap_prepare.py b/bin/basicswap_prepare.py index 5a00928..b95f0d4 100755 --- a/bin/basicswap_prepare.py +++ b/bin/basicswap_prepare.py @@ -12,6 +12,7 @@ import mmap import stat import gnupg import signal +import socket import hashlib import tarfile import zipfile @@ -114,7 +115,12 @@ def downloadFile(url, path): opener = urllib.request.build_opener() opener.addheaders = [('User-agent', 'Mozilla/5.0')] urllib.request.install_opener(opener) + + # Set one second timeout for urlretrieve connections + old_timeout = socket.getdefaulttimeout() + socket.setdefaulttimeout(1) urlretrieve(url, path, make_reporthook()) + socket.setdefaulttimeout(old_timeout) def extractCore(coin, version_pair, settings, bin_dir, release_path): diff --git a/bin/basicswap_run.py b/bin/basicswap_run.py index b1fd4be..3506889 100755 --- a/bin/basicswap_run.py +++ b/bin/basicswap_run.py @@ -158,7 +158,7 @@ def runClient(fp, data_dir, chain): swap_client.start() if 'htmlhost' in settings: - swap_client.log.info('Starting server at %s:%d.' % (settings['htmlhost'], settings['htmlport'])) + swap_client.log.info('Starting server at http://%s:%d.' % (settings['htmlhost'], settings['htmlport'])) allow_cors = settings['allowcors'] if 'allowcors' in settings else cfg.DEFAULT_ALLOW_CORS tS1 = HttpThread(fp, settings['htmlhost'], settings['htmlport'], allow_cors, swap_client) threads.append(tS1) diff --git a/tests/basicswap/common.py b/tests/basicswap/common.py index 77443b5..0dcf5ad 100644 --- a/tests/basicswap/common.py +++ b/tests/basicswap/common.py @@ -7,9 +7,9 @@ import os import json +import urllib import signal import logging -import urllib from urllib.request import urlopen from basicswap.rpc import callrpc diff --git a/tests/basicswap/common_xmr.py b/tests/basicswap/common_xmr.py index fc495bd..1d4b5c0 100644 --- a/tests/basicswap/common_xmr.py +++ b/tests/basicswap/common_xmr.py @@ -1,7 +1,7 @@ #!/usr/bin/env python3 # -*- coding: utf-8 -*- -# Copyright (c) 2020-2021 tecnovert +# Copyright (c) 2020-2022 tecnovert # Distributed under the MIT software license, see the accompanying # file LICENSE or http://www.opensource.org/licenses/mit-license.php. @@ -149,18 +149,18 @@ class XmrTestBase(unittest.TestCase): waitForServer(self.delay_event, 12701) - def waitForDepositAddress(): + def waitForMainAddress(): for i in range(20): if self.delay_event.is_set(): raise ValueError('Test stopped.') try: wallets = json.loads(urlopen('http://127.0.0.1:12701/json/wallets').read()) - return wallets['6']['deposit_address'] + return wallets['6']['main_address'] except Exception as e: - print('Waiting for deposit address {}'.format(str(e))) + print('Waiting for main address {}'.format(str(e))) self.delay_event.wait(1) - raise ValueError('waitForDepositAddress timedout') - xmr_addr1 = waitForDepositAddress() + raise ValueError('waitForMainAddress timedout') + xmr_addr1 = waitForMainAddress() num_blocks = 100 @@ -178,11 +178,15 @@ class XmrTestBase(unittest.TestCase): for i in range(60): if self.delay_event.is_set(): raise ValueError('Test stopped.') - wallets = json.loads(urlopen('http://127.0.0.1:12701/json/wallets').read()) - particl_blocks = wallets['1']['blocks'] - print('particl_blocks', particl_blocks) - if particl_blocks >= num_blocks: - break + try: + wallets = json.loads(urlopen('http://127.0.0.1:12701/json/wallets').read()) + particl_blocks = wallets['1']['blocks'] + print('particl_blocks', particl_blocks) + if particl_blocks >= num_blocks: + break + except Exception as e: + print('Error reading wallets', str(e)) + self.delay_event.wait(1) assert(particl_blocks >= num_blocks) diff --git a/tests/basicswap/extended/test_xmr_persistent.py b/tests/basicswap/extended/test_xmr_persistent.py index b6e2009..ba5051d 100644 --- a/tests/basicswap/extended/test_xmr_persistent.py +++ b/tests/basicswap/extended/test_xmr_persistent.py @@ -1,7 +1,7 @@ #!/usr/bin/env python3 # -*- coding: utf-8 -*- -# Copyright (c) 2021 tecnovert +# Copyright (c) 2021-2022 tecnovert # Distributed under the MIT software license, see the accompanying # file LICENSE or http://www.opensource.org/licenses/mit-license.php. @@ -266,7 +266,7 @@ class Test(unittest.TestCase): wallets = json.loads(urlopen('http://127.0.0.1:{}/json/wallets'.format(UI_PORT + 1)).read()) - self.xmr_addr = wallets['6']['deposit_address'] + self.xmr_addr = wallets['6']['main_address'] num_blocks = 100 if callrpc_xmr_na(XMR_BASE_RPC_PORT + 1, 'get_block_count')['count'] < num_blocks: logging.info('Mining {} Monero blocks to {}.'.format(num_blocks, self.xmr_addr)) diff --git a/tests/basicswap/selenium/test_wallets.py b/tests/basicswap/selenium/test_wallets.py new file mode 100644 index 0000000..9c59bcc --- /dev/null +++ b/tests/basicswap/selenium/test_wallets.py @@ -0,0 +1,48 @@ +#!/usr/bin/env python3 +# -*- coding: utf-8 -*- + +# Copyright (c) 2022 tecnovert +# Distributed under the MIT software license, see the accompanying +# file LICENSE or http://www.opensource.org/licenses/mit-license.php. + +""" +cd /tmp +wget -4 https://chromedriver.storage.googleapis.com/96.0.4664.45/chromedriver_linux64.zip +7z x chromedriver_linux64.zip +sudo mv chromedriver /opt/chromedriver96 + + +python tests/basicswap/extended/test_xmr_persistent.py + +python tests/basicswap/selenium/test_wallets.py + +html = driver.page_source +print('html', html) + +""" + +import time +from selenium import webdriver +from selenium.webdriver.common.by import By +from selenium.webdriver.chrome.service import Service + + +def test_html(): + base_url = 'http://localhost:12701' + + driver = webdriver.Chrome(service=Service('/opt/chromedriver96')) + url = base_url + '/wallets' + driver.get(url) + + time.sleep(1) + driver.refresh() + + driver.find_element(By.ID, "refresh").click() + time.sleep(1) + driver.refresh() + + driver.close() + + +if __name__ == '__main__': + test_html()