2

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.

adriB
  • 61
  • 7

1 Answers1

4

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.

desertnaut
  • 57,590
  • 26
  • 140
  • 166
adriB
  • 61
  • 7