Call Javascript from VBA on Excel on a Mac - javascript

I want to create a VBA macro on excel which at a click of button would open a browser (chrome or safari) login to a website, extract the desired float value, then populate a given cell in the sheet with that value.
There are examples online on how to achieve this using internet explorer but this is not available as on a mac. I have also seen guides using Selenium but this doesn't appear to work on mac.
The javascript itself is along the lines of (after opening a browser at a certain website):
document.getElementById("username").value = "username"
document.getElementById("password").value = "password"
document.getElementsByClassName("button")[0].click()
value = parseFloat(document.getElementsByClassName("value")[1].innerText.slice(1))

I've solved this by using a combination of python-selenium and xlwings. My VBA calls RunPython ("import python_script; python_script.fun()")
python_script.py
import xlwings as xw
from selenium import webdriver
def fun():
# Creates a reference to the calling Excel file
wb = xw.Book.caller()
# opens chrome
chrome_driver_path = '/usr/local/bin/chromedriver'
driver = webdriver.Chrome(chrome_driver_path)
# open website and login
driver.get('url')
driver.find_element_by_id('username').send_keys('username')
driver.find_element_by_id('password').send_keys('password')
driver.find_element_by_name('buttonId').click()
# finds member price sum
table_body = driver.find_element_by_xpath("//*[#class='classname']").text
price = float(table_body.split()[2][1:])
# closes chrome
driver.quit()
# changes cell value
sheet = wb.sheets['sheetname']
sheet.range('cell').value = price

Related

VBA & Selenium | Access iframe within HTML containing #document

I am trying to access the HTML within two iframes using Selenium Basic in VBA, as IE has been blocked on our machines, and Python, etc. are not available to us.
Previously I could access the html with this:
Dim IE As InternetExplorerMedium
Set IE = New InternetExplorerMedium
' actual website excluded as it is a work hosted website which requires login, etc.
website = "..."
IE.navigate (website)
Dim IEDocument As HTMLDocument
Set IEDocument = IE.document.getElementById(id1).contentDocument.getElementById(id2).contentDocument
From there I would have access to all the HTML elements which I could work with.
Now I am trying the following with Selenium Basic:
Set cd = New Selenium.ChromeDriver
website = "..."
cd.Start baseUrl:=website
cd.Get "/"
Dim af1 As Selenium.WebElement, af2 As Selenium.WebElement
Set af1 = cd.FindElementById("CRMApplicationFrame")
Set af2 = af1.FindElementById("WorkAreaFrame1")
It works up to the last line, as it is able to set af to the "CRMApplicationFrame" id; however, I am unable to get inside of it.
I think the solution lies in executing a bit of JavaScript, similar to as in this video:
https://www.youtube.com/watch?v=phYGCGXGtEw
Although I don't have a #ShadowDOM line, I do have a #document line.
Based on and trying to adapt the video I have tried the following:
Set af2 = cd.ExecuteScript(Script:="return arguments[0].contentDocument", arguments:=af1 )
However, that did not work.
I also tested:
Dim af1 As Selenium.WebElement
Set af1 = cd.FindElementById("CRMApplicationFrame")
call cd.SwitchToFrame (af1)
Debug.Print cd.PageSource
However, the SwitchToFrame line won't execute, with a 438 error: Object doesn't support this property or method.
Any advice or guidance on how I could succeed would be highly appreciated!
Replace:
call cd.SwitchToFrame (af1)
with:
cd.SwitchToFrame "CRMApplicationFrame"
You can find a relevant detailed discussion in Selenium VBA Excel - problem clicking a link within an iframe

Input a value into an HTML webpage using Python

I am attempting to automate inputting values into a webpage. However, the major issue is that the Mechanize library does not work because my webpage has no forms that Mechanize's form.name recognizes. This is due to the input being a <input>.
I have spent the past hour researching alternatives to Mechanize that might work, but to no avail. Google is of no help as it only thinks I want to take data from a website.
My current code:
from mechanize import Browser
import csv
csv_file = 'city_names.csv' # file name
cities = [] # array to save values from csv into
with open(csv_file, 'r') as f:
reader = csv.DictReader(f)
for row in reader:
cities.append(row.get('cities'))
br = Browser()
br.set_handle_robots(False) # ignore robots
br.set_handle_refresh(False) # can sometimes hang without this
br.open("https://iafisher.com/projects/cities/world") # The website if you are curious
for form in br.forms():
print(form.name) # Prints nothing
for i in range(len(cities)):
br.select_form(class="city-input") # ISSUE IS THROWN HERE
control = br.form.find_control("controlname")
# Browser passes through unknown attributes (including methods)
# to the selected HTMLForm (from ClientForm).
br[control] = [cities[i]] # (the method here is __setitem__)
response = br.submit() # submit current form
The input value as seen in developer tools:
<input data-v-018e983a="" id="city-input" type="text" placeholder="Try 'Tokyo' or 'Kingston, Jamaica'" autocomplete="off" spellcheck="false" class="city-input ">
If there is any alternative to Mechanize or a method in Mechanize that would work, it would be appreciated.
After trying multiple different search options, I finally stumbled across a tutorial that manually tells Selenium what to do.
Final code:
from selenium import webdriver
import csv
from selenium.webdriver.common.keys import Keys
csv_file = 'city_names.csv'
cities = []
with open(csv_file, 'r') as f:
reader = csv.DictReader(f)
for row in reader:
cities.append(row.get('cities'))
chromedriver_location = "C:/Users/blais/Downloads/chromedriver"
driver = webdriver.Chrome(chromedriver_location)
driver.get('https://iafisher.com/projects/cities/world')
submit = '//*[#id="city-input"]'
for c in range(len(cities)):
driver.find_element_by_xpath(submit).send_keys(cities[c] + Keys.ENTER)

How to use Chrome profile in Selenium (selenium-webdriver) JavaScript

Since there doesn't seem to be a way to use an existing Chrome window, how do I use the Google account (and all settings and passwords) of the user in the window that Selenium opens? Selenium seems to open windows for itself, but without a Google account, which is an essential part to my program.
My program is very time sensitive, so it needs to be logged in to the websites it accesses automatically, and the program is going to be used by multiple users.
var webdriver = require("selenium-webdriver");
var chrome = require("selenium-webdriver/chrome");
var options = new chrome.Options();
options.addArguments("user-data-dir=C:\\Users\robert.car\\AppData\\Local\\Google\\Chrome\\User Data")
options.addArguments("profile-directory=Profile 1")
var driver = let driver = new webdriver.Builder()
.forBrowser('chrome')
.setChromeOptions(options)
.build();
user-data-dir considers profile as default , and you don't have to specify that . If its something else specify it through profile-directory argument
Step to create a profile:
open : chrome://version in address bar
copy the user dir folder completely to eg c:\tmp\newdir
open the copied user data (newdir) and search for folder called Default . This is the profile folder.
rename the Default folder as "Profile 1"
Now to use this :
options.addArguments("user-data-dir=c:\\tmp\\newdir")
options.addArguments("profile-directory=Profile 1")

Can't narrow down correct element in Python/Selenium

So I'm trying to craft a website manipulation script to help automate teh creation of email mailboxes on our hosted provider.
I'm both new to Python and new to scripting web resources so if something looks weird or mediocre that's why :)
Here's my script:
import time
from selenium.webdriver import Firefox
from selenium.webdriver.firefox.options import Options
from selenium import webdriver
from selenium.webdriver.support.select import Select as driverselect
driver = webdriver.Firefox()
main_url = 'https://website.com:446'
opts = Options()
# noinspection PyDeprecation
# opts.set_headless()
#assert opts.headless # Operating in headless mode
browser = Firefox(options=opts)
browser.get(main_url)
search_form = browser.find_element_by_id('LoginName')
search_form.send_keys('username')
search_form = browser.find_element_by_id('Password')
search_form.send_keys('password')
search_form.submit()
time.sleep(5)
# provision = driverselect(driver.find_element_by_xpath("/html/body/div[2]/div[2]/nav/div/ul/li[4]"))
provision = driver.find_element_by_xpath('/html/body/div[2]/div[2]/nav/div/ul/li[4]/a/span[1]')
provision.submit()
# exchange = driver.find_element_by_name('Exchange')
# exchange.submit()
My error is:
Traceback (most recent call last): File
"/home/turd/PycharmProjects/Automate_NSGEmail/selenium_test.py", line
23, in provision =
driver.find_element_by_xpath('/html/body/div[2]/div[2]/nav/div/ul/li[4]/a/span1')
File "/home/turd/.local/lib/python3.8/site-packages/selenium/webdriver/remote/webdriver.py",
line 394, in find_element_by_xpath
return self.find_element(by=By.XPATH, value=xpath) File "/home/turd/.local/lib/python3.8/site-packages/selenium/webdriver/remote/webdriver.py",
line 976, in find_element
return self.execute(Command.FIND_ELEMENT, { File "/home/turd/.local/lib/python3.8/site-packages/selenium/webdriver/remote/webdriver.py",
line 321, in execute
self.error_handler.check_response(response) File "/home/turd/.local/lib/python3.8/site-packages/selenium/webdriver/remote/errorhandler.py",
line 242, in check_response
raise exception_class(message, screen, stacktrace) selenium.common.exceptions.NoSuchElementException: Message: Unable to
locate element: /html/body/div[2]/div[2]/nav/div/ul/li[4]/a/span1
Now that Xpath value I copied straight from the dev tools on that page, here's what this block of code looks like from the site:
I'm trying to grab and 'click' on the one Active Dynamic-Menu item in the pic above. I think that menu is JS but I'm not 100% positive.
Anyway I'd be much obliged if anyone could help me narrow this down and grab that blasted element.
So I discovered the answer myself.. I had some wrong code at the beginning of my script:
driver = webdriver.Firefox()
main_url = 'https://website.com:446'
opts = Options()
# noinspection PyDeprecation
# opts.set_headless()
#assert opts.headless # Operating in headless mode
browser = Firefox(options=opts)
browser.get(main_url)
I changed this section to:
driver = webdriver.Firefox()
url = 'https://website.com:446'
opts = Options()
driver.maximize_window()
driver.get(url)
I was opening two instances of Firebox before, the driver.* lines would attempting to locate the xpath tags on the FF instance that was not logged in.
Derp.

Run Excel VBA sub from javascript in a secure environment

I am attempting to do some automation of excel taks using javascipt. These workbooks already have VBA modules, so I will need to be able to run them from javascript. My challenge is that when I open a workbook using Javascript I do not get prompted to enable macros. I am not able to change my trust center settings in my work environment and they are currently set to "Disable all macros with notification," which asks the user to enable macros via a pop-up box if the file is opened manually.
How do I allow the user to enable macros when opening a workbook from javascript?
Error message and code below:
SCRIPT1004: Cannot run the macro 'C:\Users...\JPO Mdl
v07.04.022-FY16MM.01.01_A.xlsm!update_links()'. The macro may not be
available in this workbook or all macros may be disabled.
function open_files(A, B, C)
{
var excel = new ActiveXObject("Excel.Application");
excel.Visible=true;
excel.DisplayAlerts = true;
var wbA = excel.Workbooks.Open(document.getElementById(A).value);
var wbB = excel.Workbooks.Open(document.getElementById(B).value);
var wbC = excel.Workbooks.Open(document.getElementById(C).value);
excel.EnableEvents = false
excel.ScreenUpdating = false
excel.Calculation = -4135 //xlCalculationManual enumeration
var wb_collection = [wbA, wbB, wbC];
excel.Application.Run(wbA.fullname + '!update_links()');
}

Categories

Resources