skip to Main Content

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:

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


  1. Chosen as BEST ANSWER

    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

    • The developer told me was that I could build my own MetaMask version with this security feature disabled. This lowers the security but in my case there is no problem because I want to implement this just on development environment to test my Next.js app with selenium-webdriver.

    Steps

    1. Go to metamask-extension GitHub repo and clone it in your computer: https://github.com/MetaMask/metamask-extension. You can read the README.md file to find more information.
    2. In the cloned folder, go to the index.js file, replace line 92 (scuttleGlobalThis: applyLavaMoat && shouldScuttle) with scuttleGlobalThis: false.
    3. In the root folder of the project, run cp .metamaskcr.dist .metamaskcr.
    4. Open the .metamaskcr file and change the following data:
    5. After this, run yarn install and yarn 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:

        def test_login_button():
            EXTENSION_PATH = '/home/adrib/Universidad/4o/TFG/Proyecto/metamask-extension/dist/chrome'
      
            opt = Options()
            opt.add_argument(f'--load-extension={EXTENSION_PATH}')
      
            driver = webdriver.Chrome(service=Service(ChromeDriverManager().install()), options=opt)
            time.sleep(3)
            driver.switch_to.window(driver.window_handles[1])
            original_window = driver.current_window_handle
      
            time.sleep(3)
      
            time.sleep(3)
            WebDriverWait(driver, 5).until(EC.presence_of_element_located((By.XPATH, "/html/body/div[1]/div/div[2]/div/div/div/ul/li[1]/div/input"))).click()
      
            WebDriverWait(driver, 5).until(EC.element_to_be_clickable((By.XPATH, "/html/body/div[1]/div/div[2]/div/div/div/ul/li[3]/button"))).click()
      
            WebDriverWait(driver, 5).until(EC.element_to_be_clickable((By.XPATH, "//*[@id='app-content']/div/div[2]/div/div/div[2]/div/div[2]/div[1]/button"))).click()
      
            WebDriverWait(driver, 5).until(EC.element_to_be_clickable((By.XPATH, "//*[@id='app-content']/div/div[2]/div/div/div/div[5]/div[1]/footer/button[1]"))).click()
      

    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.


  2. is That solution can keep my old data ?

    Login or Signup to reply.
Please signup or login to give your own answer.
Back To Top
Search