I am developing a Next.js app and when I started making some tests with selenium-webdriver I started to get some problems. My webapp uses authentication with Metamask wallets. The problem is when trying to import a wallet on a test window with selenium to be able to log in after this in my webapp.
I have tried a lot of ways to do it:
- JavaScript tests trying to download metamask or trying to open a new chromewindow with metamask installed
- Python tests with auto-metamask, trying to import metamask in a new window…
I am on ubuntu 22.04. Chrome version is 113.0.5672.92, chromedriver version is 113.0.5672.63, selenium-webdriver version is 4.9.1, python3 version is 3.10.6.
This is the code:
import time
from selenium.webdriver.common.by import By
from selenium.webdriver.support import expected_conditions as EC
from selenium.webdriver.support.ui import WebDriverWait
from selenium import webdriver
from selenium.webdriver.support.ui import WebDriverWait
def test_login_button():
EXTENSION_PATH = '/nkbihfbeogaeaoehlefnkodbefgpgknn-10.30.1-www.Crx4Chrome.com.crx' # Path from the crx downloaded file in my pc (I have tried a bunch of them downloaded from different sites)
SECRET_RECOVERY_PHRASE = "my seed phrase"
NEW_PASSWORD = "password"
opt = webdriver.ChromeOptions()
opt.add_extension(EXTENSION_PATH)
#opt.add_argument("--disable-extensions-except=nkbihfbeogaeaoehlefnkodbefgpgknn") (this approach does not work, I tried to disable all extensions except metamask but it does not work when executing the code because it does not find the extension)
#opt.add_argument("--user-data-dir=./path/to/google-chrome/Profile 1") (this approach did not work when trying to open an existing profile)
driver = webdriver.Chrome(options=opt)
time.sleep(3)
driver.switch_to.window(driver.window_handles[1])
time.sleep(3)
wait = WebDriverWait(driver, 30)
element = wait.until(EC.presence_of_element_located((By.XPATH, '//button[text()="Importar una cartera existente"]'))) # This is the line which gives an error
element.click()
#driver.find_element(By.XPATH, '//*[text()="Importar una cartera existente"]').click()
# Rest of the code
ERROR obtained:
> element = wait.until(EC.element_to_be_clickable((By.XPATH, '//*[text()="Importar una cartera existente"]')))
self = <selenium.webdriver.remote.errorhandler.ErrorHandler object at 0x7fcb4e1556f0>
response = {'status': 500, 'value': '{"value":{"error":"unknown error","message":"unknown error: Runtime.callFunctionOn threw exc...\n#19 0x55775845dc97 \u003Cunknown>\n#20 0x55775846e113 \u003Cunknown>\n#21 0x7fdaf2c94b43 \u003Cunknown>\n"}}'}
selenium.common.exceptions.WebDriverException: Message: unknown error: Runtime.callFunctionOn threw exception: Error: LavaMoat - property "Proxy" of globalThis is inaccessible under scuttling mode. To learn more visit https://github.com/LavaMoat/LavaMoat/pull/360.
E at get (chrome-extension://nkbihfbeogaeaoehlefnkodbefgpgknn/runtime-lavamoat.js:11200:17)
E at serializationGuard (<anonymous>:198:67)
E at buildError (<anonymous>:319:27)
E (Session info: chrome=113.0.5672.92)`
I have followed different tutorials:
- Main one is: https://dev.to/ltmenezes/automated-dapps-scrapping-with-selenium-and-metamask-2ae9
- auto-metamask library: https://pypi.org/project/auto-metamask/
- Metamask automation with Selenium webdriver
I just want to be able to make some tests to interact with a metamask wallet for my application to be able to log in it and then interact with the smart contracts that provide the logic.
2
Answers
IMPORTANT: This solution is working at 2023/05/15, it is important because two years ago this issue did not happen because this security implementation was not uploaded to the MetaMask extension. In a future this feature may change so this approach may not be usefull
One of the developers from MetaMask wrote me and shared a link to an issue where he specified the solution to the issue. See here: https://github.com/LavaMoat/LavaMoat/pull/360#issuecomment-1547726986
Summary of the thread's comment: It seems LavaMoat is sandbox that is 'wrapping' the JavaScript code so that it has no access to some APIs that it should not and could cause a security issue. So when using an external webdriver, we will see this error because of that. His words are: 'This is a tradeoff we feel good about at the moment' because they prefer adding an extra security layer over letting the code accessing to APIs.
Solution
Steps
index.js
file, replace line 92 (scuttleGlobalThis: applyLavaMoat && shouldScuttle
) withscuttleGlobalThis: false
.cp .metamaskcr.dist .metamaskcr
..metamaskcr
file and change the following data:INFURA_PROJECT_ID='replace with your infura project id'
, see https://docs.infura.io/infura/, create an Infura account if you don't have one and create a project https://www.infura.io/.PASSWORD='replace with a password'
;this is so you do not have to introduce it every time.SEGMENT_WRITE_KEY='replace with your segment write key'
, see https://segment.com/docs/connections/find-writekey/, create a Segment account if you do not have one and create a project https://segment.com/docs/connections/find-writekey/.yarn install
andyarn dist
from the root folder of the project. This should generate a/dist
folder with/chrome
/firefox
and/sourcemaps
folders. The new EXTENSION_PATH from the python code should be the path to the/chrome
folder in the project (or/firefox
if you use firefox).So the final test code would be:
Special thanks to the developer from MetaMask who replied very quick and provided the thread from GitHub from which I got the answer to this problem, @weizman.
is That solution can keep my old data ?