├── .gitignore ├── LICENSE ├── README.md └── src ├── JsonConverter.bas ├── Sample.bas ├── WebDriver.cls ├── WebDriverOptions.cls └── WebElement.cls /.gitignore: -------------------------------------------------------------------------------- 1 | *.xlsm 2 | *.xlsx 3 | *.png -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2022 er-ri 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # SeleniumWrapperVBA 2 | A Selenium-WebDriver-based browser automation framework implemented for VBA. 3 | 4 | 5 |
6 | Table of Contents 7 |
    8 |
  1. About The Project
  2. 9 |
  3. Requirements
  4. 10 |
  5. Getting Started
  6. 11 |
  7. Usage
  8. 12 | 25 |
  9. Roadmap
  10. 26 |
  11. License
  12. 27 |
  13. Contribution
  14. 28 |
  15. References
  16. 29 |
30 |
31 | 32 | ## About The Project 33 | The project implements the `endpoint node command` defined in [W3C WebDriver specification](https://www.w3.org/TR/webdriver/#endpoints) through VBA. You can use the project to do browser automation without installing a programming language such as Python, Java, etc. An MS office app and a browser-specific driver are required. 34 | 35 | ## Requirements 36 | 1. MS Office(Excel, Word, PowerPoint, etc) 32bit or 64bit 37 | 2. Browser's driver(Supported Browsers: Firefox, Chrome, Edge and Internet Explorer) 38 | 39 | ## Getting Started 40 | 1. Download the browser-specific *drivers*, [`firefox`](https://github.com/mozilla/geckodriver/releases), 41 | [`chrome`](https://chromedriver.chromium.org/), 42 | [`edge`](https://developer.microsoft.com/en-us/microsoft-edge/tools/webdriver/), or 43 | [`ie`](https://www.selenium.dev/downloads/). 44 | 2. In the office, by clicking the *Developer* tab(if you enabled) or through the shortcut key `Alt + F11` to open the VBA Editor, and then import `WebDriverOptions.cls`, `WebDriver.cls`, `WebElement.cls` and `JsonConverter.bas` into your project. (*File > Import File*) 45 | * where `JsonConverter.bas`, a JSON Parser for VBA created and maintained by [@timhall](https://github.com/timhall). For more details, see [`here`](https://github.com/VBA-tools/VBA-JSON). 46 | 3. Include a reference to "Microsoft Scripting Runtime". (*Tools > References* Check "`Microsoft Scripting Runtime`") 47 | 48 | #### Note 49 | * Add browser's driver in the system `PATH`, or you can also specify the path when launching the corresponding browser's driver. 50 | * Some configurations are required before using `iedriver`, see [here](https://www.selenium.dev/documentation/ie_driver_server/#required-configuration) for more details about the configurations. 51 | 52 | #### Example 53 | ```vba 54 | Sub Example() 55 | Dim driver As New WebDriver 56 | 57 | driver.Chrome "to/your/path/chromedriver.exe" 58 | driver.OpenBrowser 59 | driver.NavigateTo "https://www.python.org/" 60 | driver.MaximizeWindow 61 | driver.FindElement(By.ID, "id-search-field").SendKeys "machine learning" 62 | driver.FindElement(By.ID, "submit").Click 63 | driver.TakeScreenshot ThisWorkbook.path + "./screenshot.png" 64 | driver.MinimizeWindow 65 | driver.CloseBrowser 66 | 67 | driver.Quit 68 | Set driver = Nothing 69 | End Sub 70 | ``` 71 | 72 | ## Usage 73 | ### Element Retrieval 74 | #### Find Element 75 | ```vba 76 | ' Locator Strategies: 77 | Dim e1, e2, e3, e4, e5, e6, e7, e8 As WebElement 78 | set e1 = driver.FindElement(By.ID, "id") 79 | set e2 = driver.FindElement(By.ClassName, "blue") 80 | set e3 = driver.FindElement(By.Name, "name") 81 | set e4 = driver.FindElement(By.LinkText, "www.google.com") 82 | set e5 = driver.FindElement(By.PartialLinkText, "www.googl") 83 | set e6 = driver.FindElement(By.TagName, "div") 84 | set e7 = driver.FindElement(By.XPath, "/html/body/div[1]/div[3]") 85 | set e8 = driver.FindElement(By.CSS, ".search input[type='text']") 86 | ``` 87 | 88 | #### Find Elements 89 | ```vba 90 | Dim elements() As WebElement 91 | elements = driver.FindElements(By.TagName, "a") 92 | 93 | Dim element As Variant 94 | For Each element In elements 95 | ' Do your stuff 96 | Next element 97 | ``` 98 | 99 | #### Find Element From Element 100 | ```vba 101 | Dim elementRoot As WebElement 102 | Set elementRoot = driver.FindElement(By.ID, "root1") 103 | Dim elementChild As WebElement 104 | Set elementChild = driver.FindElementFromElement(elmentRoot, By.TagName, "div") 105 | ``` 106 | 107 | #### Find Elements From Element 108 | ```vba 109 | Dim elementRoot As WebElement 110 | Set elementRoot = driver.FindElement(By.ID, "root1") 111 | Dim elementChildren() As WebElement 112 | elementChildren() = driver.FindElementsFromElement(elmentRoot, By.TagName, "p") 113 | ``` 114 | 115 | #### Find Element From ShadowRoot 116 | ```vba 117 | Dim element as WebElement 118 | Dim elementShadowRoot As WebElement 119 | Set elementShadowRoot = driver.FindElement(By.ID, "shadow_id").GetShadowRoot() 120 | Set element = driver.FindElementFromShadowRoot(eleShadowRoot, By.CSS, "#shadow_css") 121 | ``` 122 | 123 | #### Find Elements From ShadowRoot 124 | ```vba 125 | Dim elements() As WebElement 126 | Dim elementShadowRoot As WebElement 127 | Set elementShadowRoot = driver.FindElement(By.ID, "shadow_id").GetShadowRoot() 128 | elements() = driver.FindElementsFromShadowRoot(elementShadowRoot, By.CSS, "#shadow_css") 129 | ``` 130 | 131 | ### Timeouts 132 | #### Get Timeouts 133 | ```vba 134 | Dim timeoutsDict As Dictionary 135 | Set timeoutsDict = driver.GetTimeouts() 136 | Debug.Print timeoutsDict("script") ' 30000 137 | Debug.Print timeoutsDict("pageLoad") ' 300000 138 | Debug.Print timeoutsDict("implicit") ' 0 139 | ``` 140 | 141 | #### Set Timeouts 142 | ```vba 143 | ' Set "script":40000,"pageLoad":500000,"implicit":15000 144 | driver.SetTimeouts 40000, 500000, 15000 145 | ``` 146 | * Invoke the function before `OpenBrowser`. 147 | 148 | ### Working with iframe 149 | ```vba 150 | Set iframe1 = driver.FindElement(By.ID, "iframe1") 151 | driver.SwitchToFrame iframe1 152 | ' Perform some operations... 153 | driver.SwitchToParentFrame ' switch back 154 | ``` 155 | 156 | ### Working with multiple windows 157 | ```vba 158 | ' Get current windows's handle. 159 | Dim hWnd As String 160 | hWnd = driver.GetWindowHandle 161 | ' Get the handles of all the windows. 162 | Dim hWnds As New Collection 163 | Set hWnds = driver.GetWindowHandles 164 | ' Switch to another window. 165 | driver.SwitchToWindow (driver.GetWindowHandles(2)) 166 | ``` 167 | 168 | ### Execute JavaScript 169 | ```vba 170 | ' No parameters, no return value. 171 | driver.ExecuteScript "alert('Hello world!');" 172 | driver.DismissAlert 173 | ' Accept parameters, no return value. 174 | driver.ExecuteScript "alert('Proof: ' + arguments[0] + arguments[1]);", "1+1=", 2 175 | driver.DismissAlert 176 | ' Accept parameters, return the result. 177 | Dim result As Long 178 | result = driver.ExecuteScript("let result = arguments[0] + arguments[1];return result;", 1, 2) 179 | Debug.Print result ' 3 180 | ``` 181 | 182 | ### Execute Async JavaScript 183 | ```vba 184 | ' No parameters, no return value. 185 | driver.ExecuteAsyncScript "alert('Hello world!');" 186 | driver.WaitUntilAlertIsPresent 187 | driver.DismissAlert 188 | ' Accept parameters, no return value. 189 | driver.ExecuteAsyncScript "alert('Proof: ' + arguments[0] + arguments[1]);", "1+1=", 2 190 | driver.WaitUntilAlertIsPresent 191 | driver.DismissAlert 192 | ' Accept parameters, return the result. 193 | Dim result As Long 194 | result = driver.ExecuteAsyncScript("let result = arguments[0] + arguments[1];return result;", 1, 2) 195 | Debug.Print result ' 3 196 | ``` 197 | 198 | ### Working with alerts 199 | ```vba 200 | driver.WaitUntilAlertIsPresent 201 | driver.DismissAlert 202 | 203 | driver.ExecuteScript "prompt('question?')" 204 | driver.SendAlertText "answer" 205 | driver.AcceptAlert 206 | ``` 207 | 208 | ### Screenshot 209 | #### Take Screenshot 210 | ```vba 211 | ' Take the current webpage screenshot and save it to the specific path. 212 | driver.TakeScreenshot ThisWorkbook.path + "./1.png" 213 | ``` 214 | 215 | #### Take Element Screenshot 216 | ```vba 217 | ' Take the element screenshot directly. 218 | driver.FindElement(By.ID, "selenium_logo").TakeScreenshot ThisWorkbook.path + "./logo.png" 219 | ' or 220 | Dim seleniumLogo As WebElement 221 | Set seleniumLogo = driver.FindElement(By.ID, "selenium_logo") 222 | seleniumLogo.TakeScreenshot ThisWorkbook.path + "./logo.png" 223 | ``` 224 | 225 | ### Enable Edge IE-mode 226 | ```vba 227 | Dim driver As New WebDriver 228 | Dim ieOptions As New WebDriverOptions 229 | ieOptions.BrowserType = InternetExplorer 230 | ieOptions.IntroduceFlakinessByIgnoringSecurityDomains = True ' Optional 231 | ieOptions.IgnoreZoomSetting = True ' Optional 232 | ieOptions.AttachToEdgeChrome = True 233 | ieOptions.EdgeExecutablePath = "C:/Program Files (x86)/Microsoft/Edge/Application/msedge.exe" 234 | 235 | driver.InternetExplorer "C:\WebDriver\IEDriverServer_Win32_4.0.0\IEDriverServer.exe" 236 | driver.OpenBrowser ieOptions 237 | ``` 238 | 239 | ### Headless mode 240 | #### Start Chrome in headless mode 241 | ```vba 242 | Dim driver As New WebDriver 243 | Dim chromeOptions As New WebDriverOptions 244 | chromeOptions.BrowserType = Chrome 245 | chromeOptions.ChromeArguments.add "--headless" 246 | 247 | driver.Chrome "C:\WebDriver\chromedriver_win32\chromedriver.exe" 248 | driver.OpenBrowser chromeOptions 249 | ``` 250 | 251 | #### Start Firefox in headless mode 252 | ```vba 253 | Dim driver As New WebDriver 254 | Dim firefoxOptions As New WebDriverOptions 255 | firefoxOptions.BrowserType = Firefox 256 | firefoxOptions.FirefoxArguments.Add "-headless" 257 | 258 | driver.Firefox "C:\WebDriver\Firefox\geckodriver.exe" 259 | driver.OpenBrowser firefoxOptions 260 | ``` 261 | 262 | ### Customize User-Agent 263 | #### Customize User-Agent in Chrome 264 | ```vba 265 | Dim driver As New WebDriver 266 | Dim chromeOptions As New WebDriverOptions 267 | 268 | driver.Chrome 269 | chromeOptions.BrowserType = Chrome 270 | chromeOptions.ChromeArguments.Add "--user-agent=my customized user-agent" 271 | driver.OpenBrowser chromeOptions 272 | driver.NavigateTo "https://www.whatismybrowser.com/detect/what-is-my-user-agent/" 273 | ``` 274 | 275 | ## Roadmap 276 | | Endpoint Node Command | Function Name | Element Function Name | 277 | |--------------------------------|----------------------------|-----------------------| 278 | | New Session | OpenBrowser | | 279 | | Delete Session | CloseBrowser | | 280 | | Status | GetStatus | | 281 | | Get Timeouts | GetTimeouts | | 282 | | Set Timeouts | SetTimeouts | | 283 | | Navigate To | NavigateTo | | 284 | | Get Current URL | GetCurrentURL | | 285 | | Back | Back | | 286 | | Forward | Forward | | 287 | | Refresh | Refresh | | 288 | | Get Title | GetTitle | | 289 | | Get Window Handle | GetWindowHandle | | 290 | | Close Window | CloseWindow | | 291 | | Switch To Window | SwitchToWindow | | 292 | | Get Window Handles | GetWindowHandles | | 293 | | New Window | NewWindow | | 294 | | Switch To Frame | SwitchToFrame | | 295 | | Switch To Parent Frame | SwitchToParentFrame | | 296 | | Get Window Rect | GetWindowRect | | 297 | | Set Window Rect | SetWindowRect | | 298 | | Maximize Window | MaximizeWindow | | 299 | | Minimize Window | MinimizeWindow | | 300 | | Fullscreen Window | FullscreenWindow | | 301 | | Get Active Element | Not yet | | 302 | | Get Element Shadow Root | Not yet | | 303 | | Find Element | FindElement | | 304 | | Find Elements | FindElements | | 305 | | Find Element From Element | FindElementFromElement | FindElement | 306 | | Find Elements From Element | FindElementsFromElement | FindElements | 307 | | Find Element From Shadow Root | FindElementFromShadowRoot | | 308 | | Find Elements From Shadow Root | FindElementsFromShadowRoot | | 309 | | Get Element Shadow Root | GetElementShadowRoot | GetShadowRoot | 310 | | Is Element Selected | Not yet | | 311 | | Get Element Attribute | GetElementAttribute | GetAttribute | 312 | | Get Element Property | GetElementProperty | GetProperty | 313 | | Get Element CSS Value | Not yet | | 314 | | Get Element Text | GetElementText | GetText | 315 | | Get Element Tag Name | Not yet | | 316 | | Get Element Rect | Not yet | | 317 | | Is Element Enabled | Not yet | | 318 | | Get Computed Role | Not yet | | 319 | | Get Computed Label | Not yet | | 320 | | Element Click | ElementClick | Click | 321 | | Element Clear | ElementClear | Clear | 322 | | Element Send Keys | *ElementSendKeys* | *SendKeys* | 323 | | Get Page Source | GetPageSource | | 324 | | Execute Script | ExecuteScript | | 325 | | Execute Async Script | ExecuteAsyncScript | | 326 | | Get All Cookies | Not yet | | 327 | | Get Named Cookie | Not yet | | 328 | | Add Cookie | Not yet | | 329 | | Delete Cookie | Not yet | | 330 | | Delete All Cookies | Not yet | | 331 | | Perform Actions | Not yet | | 332 | | Release Actions | Not yet | | 333 | | Dismiss Alert | DismissAlert | | 334 | | Accept Alert | AcceptAlert | | 335 | | Get Alert Text | GetAlertText | | 336 | | Send Alert Text | SendAlertText | | 337 | | Take Screenshot | TakeScreenshot | | 338 | | Take Element Screenshot | TakeElementScreenshot | TakeScreenshot | 339 | | Print Page | Not yet | | 340 | * Browser Capabilities are not listed above. 341 | * Key action(Such as `Enter`, `Shift`, `Control`) has not been implemented. 342 | 343 | ## License 344 | Distributed under the MIT License. See `LICENSE.txt` for more information. 345 | 346 | ## Contribution 347 | Any suggestions for improvement or contribution to this project are appreciated! Creating an issue or pull request! 348 | 349 | ## References 350 | 1. W3C WebDriver Working Draft: 351 | * https://www.w3.org/TR/webdriver/ 352 | 2. The Selenium Browser Automation Project 353 | * https://www.selenium.dev/documentation/webdriver/ 354 | 3. The W3C WebDriver Spec, A Simplified Guide: 355 | * https://github.com/jlipps/simple-wd-spec 356 | 4. geckodriver, WebDriver Reference 357 | * https://developer.mozilla.org/en-US/docs/Web/WebDriver 358 | 5. Capabilities & ChromeOptions 359 | * https://chromedriver.chromium.org/capabilities 360 | 6. Capabilities and EdgeOptions 361 | * https://docs.microsoft.com/en-us/microsoft-edge/webdriver-chromium/capabilities-edge-options 362 | -------------------------------------------------------------------------------- /src/JsonConverter.bas: -------------------------------------------------------------------------------- 1 | Attribute VB_Name = "JsonConverter" 2 | '' 3 | ' VBA-JSON v2.3.1 4 | ' (c) Tim Hall - https://github.com/VBA-tools/VBA-JSON 5 | ' 6 | ' JSON Converter for VBA 7 | ' 8 | ' Errors: 9 | ' 10001 - JSON parse error 10 | ' 11 | ' @class JsonConverter 12 | ' @author tim.hall.engr@gmail.com 13 | ' @license MIT (http://www.opensource.org/licenses/mit-license.php) 14 | '' ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ ' 15 | ' 16 | ' Based originally on vba-json (with extensive changes) 17 | ' BSD license included below 18 | ' 19 | ' JSONLib, http://code.google.com/p/vba-json/ 20 | ' 21 | ' Copyright (c) 2013, Ryo Yokoyama 22 | ' All rights reserved. 23 | ' 24 | ' Redistribution and use in source and binary forms, with or without 25 | ' modification, are permitted provided that the following conditions are met: 26 | ' * Redistributions of source code must retain the above copyright 27 | ' notice, this list of conditions and the following disclaimer. 28 | ' * Redistributions in binary form must reproduce the above copyright 29 | ' notice, this list of conditions and the following disclaimer in the 30 | ' documentation and/or other materials provided with the distribution. 31 | ' * Neither the name of the nor the 32 | ' names of its contributors may be used to endorse or promote products 33 | ' derived from this software without specific prior written permission. 34 | ' 35 | ' THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND 36 | ' ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED 37 | ' WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE 38 | ' DISCLAIMED. IN NO EVENT SHALL BE LIABLE FOR ANY 39 | ' DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES 40 | ' (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; 41 | ' LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND 42 | ' ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT 43 | ' (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS 44 | ' SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. 45 | ' ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ ' 46 | Option Explicit 47 | 48 | ' === VBA-UTC Headers 49 | #If Mac Then 50 | 51 | #If VBA7 Then 52 | 53 | ' 64-bit Mac (2016) 54 | Private Declare PtrSafe Function utc_popen Lib "/usr/lib/libc.dylib" Alias "popen" _ 55 | (ByVal utc_Command As String, ByVal utc_Mode As String) As LongPtr 56 | Private Declare PtrSafe Function utc_pclose Lib "/usr/lib/libc.dylib" Alias "pclose" _ 57 | (ByVal utc_File As LongPtr) As LongPtr 58 | Private Declare PtrSafe Function utc_fread Lib "/usr/lib/libc.dylib" Alias "fread" _ 59 | (ByVal utc_Buffer As String, ByVal utc_Size As LongPtr, ByVal utc_Number As LongPtr, ByVal utc_File As LongPtr) As LongPtr 60 | Private Declare PtrSafe Function utc_feof Lib "/usr/lib/libc.dylib" Alias "feof" _ 61 | (ByVal utc_File As LongPtr) As LongPtr 62 | 63 | #Else 64 | 65 | ' 32-bit Mac 66 | Private Declare Function utc_popen Lib "libc.dylib" Alias "popen" _ 67 | (ByVal utc_Command As String, ByVal utc_Mode As String) As Long 68 | Private Declare Function utc_pclose Lib "libc.dylib" Alias "pclose" _ 69 | (ByVal utc_File As Long) As Long 70 | Private Declare Function utc_fread Lib "libc.dylib" Alias "fread" _ 71 | (ByVal utc_Buffer As String, ByVal utc_Size As Long, ByVal utc_Number As Long, ByVal utc_File As Long) As Long 72 | Private Declare Function utc_feof Lib "libc.dylib" Alias "feof" _ 73 | (ByVal utc_File As Long) As Long 74 | 75 | #End If 76 | 77 | #ElseIf VBA7 Then 78 | 79 | ' http://msdn.microsoft.com/en-us/library/windows/desktop/ms724421.aspx 80 | ' http://msdn.microsoft.com/en-us/library/windows/desktop/ms724949.aspx 81 | ' http://msdn.microsoft.com/en-us/library/windows/desktop/ms725485.aspx 82 | Private Declare PtrSafe Function utc_GetTimeZoneInformation Lib "kernel32" Alias "GetTimeZoneInformation" _ 83 | (utc_lpTimeZoneInformation As utc_TIME_ZONE_INFORMATION) As Long 84 | Private Declare PtrSafe Function utc_SystemTimeToTzSpecificLocalTime Lib "kernel32" Alias "SystemTimeToTzSpecificLocalTime" _ 85 | (utc_lpTimeZoneInformation As utc_TIME_ZONE_INFORMATION, utc_lpUniversalTime As utc_SYSTEMTIME, utc_lpLocalTime As utc_SYSTEMTIME) As Long 86 | Private Declare PtrSafe Function utc_TzSpecificLocalTimeToSystemTime Lib "kernel32" Alias "TzSpecificLocalTimeToSystemTime" _ 87 | (utc_lpTimeZoneInformation As utc_TIME_ZONE_INFORMATION, utc_lpLocalTime As utc_SYSTEMTIME, utc_lpUniversalTime As utc_SYSTEMTIME) As Long 88 | 89 | #Else 90 | 91 | Private Declare Function utc_GetTimeZoneInformation Lib "kernel32" Alias "GetTimeZoneInformation" _ 92 | (utc_lpTimeZoneInformation As utc_TIME_ZONE_INFORMATION) As Long 93 | Private Declare Function utc_SystemTimeToTzSpecificLocalTime Lib "kernel32" Alias "SystemTimeToTzSpecificLocalTime" _ 94 | (utc_lpTimeZoneInformation As utc_TIME_ZONE_INFORMATION, utc_lpUniversalTime As utc_SYSTEMTIME, utc_lpLocalTime As utc_SYSTEMTIME) As Long 95 | Private Declare Function utc_TzSpecificLocalTimeToSystemTime Lib "kernel32" Alias "TzSpecificLocalTimeToSystemTime" _ 96 | (utc_lpTimeZoneInformation As utc_TIME_ZONE_INFORMATION, utc_lpLocalTime As utc_SYSTEMTIME, utc_lpUniversalTime As utc_SYSTEMTIME) As Long 97 | 98 | #End If 99 | 100 | #If Mac Then 101 | 102 | #If VBA7 Then 103 | Private Type utc_ShellResult 104 | utc_Output As String 105 | utc_ExitCode As LongPtr 106 | End Type 107 | 108 | #Else 109 | 110 | Private Type utc_ShellResult 111 | utc_Output As String 112 | utc_ExitCode As Long 113 | End Type 114 | 115 | #End If 116 | 117 | #Else 118 | 119 | Private Type utc_SYSTEMTIME 120 | utc_wYear As Integer 121 | utc_wMonth As Integer 122 | utc_wDayOfWeek As Integer 123 | utc_wDay As Integer 124 | utc_wHour As Integer 125 | utc_wMinute As Integer 126 | utc_wSecond As Integer 127 | utc_wMilliseconds As Integer 128 | End Type 129 | 130 | Private Type utc_TIME_ZONE_INFORMATION 131 | utc_Bias As Long 132 | utc_StandardName(0 To 31) As Integer 133 | utc_StandardDate As utc_SYSTEMTIME 134 | utc_StandardBias As Long 135 | utc_DaylightName(0 To 31) As Integer 136 | utc_DaylightDate As utc_SYSTEMTIME 137 | utc_DaylightBias As Long 138 | End Type 139 | 140 | #End If 141 | ' === End VBA-UTC 142 | 143 | Private Type json_Options 144 | ' VBA only stores 15 significant digits, so any numbers larger than that are truncated 145 | ' This can lead to issues when BIGINT's are used (e.g. for Ids or Credit Cards), as they will be invalid above 15 digits 146 | ' See: http://support.microsoft.com/kb/269370 147 | ' 148 | ' By default, VBA-JSON will use String for numbers longer than 15 characters that contain only digits 149 | ' to override set `JsonConverter.JsonOptions.UseDoubleForLargeNumbers = True` 150 | UseDoubleForLargeNumbers As Boolean 151 | 152 | ' The JSON standard requires object keys to be quoted (" or '), use this option to allow unquoted keys 153 | AllowUnquotedKeys As Boolean 154 | 155 | ' The solidus (/) is not required to be escaped, use this option to escape them as \/ in ConvertToJson 156 | EscapeSolidus As Boolean 157 | End Type 158 | Public JsonOptions As json_Options 159 | 160 | ' ============================================= ' 161 | ' Public Methods 162 | ' ============================================= ' 163 | 164 | '' 165 | ' Convert JSON string to object (Dictionary/Collection) 166 | ' 167 | ' @method ParseJson 168 | ' @param {String} json_String 169 | ' @return {Object} (Dictionary or Collection) 170 | ' @throws 10001 - JSON parse error 171 | '' 172 | Public Function ParseJson(ByVal JsonString As String) As Object 173 | Dim json_Index As Long 174 | json_Index = 1 175 | 176 | ' Remove vbCr, vbLf, and vbTab from json_String 177 | JsonString = VBA.Replace(VBA.Replace(VBA.Replace(JsonString, VBA.vbCr, ""), VBA.vbLf, ""), VBA.vbTab, "") 178 | 179 | json_SkipSpaces JsonString, json_Index 180 | Select Case VBA.Mid$(JsonString, json_Index, 1) 181 | Case "{" 182 | Set ParseJson = json_ParseObject(JsonString, json_Index) 183 | Case "[" 184 | Set ParseJson = json_ParseArray(JsonString, json_Index) 185 | Case Else 186 | ' Error: Invalid JSON string 187 | Err.Raise 10001, "JSONConverter", json_ParseErrorMessage(JsonString, json_Index, "Expecting '{' or '['") 188 | End Select 189 | End Function 190 | 191 | '' 192 | ' Convert object (Dictionary/Collection/Array) to JSON 193 | ' 194 | ' @method ConvertToJson 195 | ' @param {Variant} JsonValue (Dictionary, Collection, or Array) 196 | ' @param {Integer|String} Whitespace "Pretty" print json with given number of spaces per indentation (Integer) or given string 197 | ' @return {String} 198 | '' 199 | Public Function ConvertToJson(ByVal JsonValue As Variant, Optional ByVal Whitespace As Variant, Optional ByVal json_CurrentIndentation As Long = 0) As String 200 | Dim json_Buffer As String 201 | Dim json_BufferPosition As Long 202 | Dim json_BufferLength As Long 203 | Dim json_Index As Long 204 | Dim json_LBound As Long 205 | Dim json_UBound As Long 206 | Dim json_IsFirstItem As Boolean 207 | Dim json_Index2D As Long 208 | Dim json_LBound2D As Long 209 | Dim json_UBound2D As Long 210 | Dim json_IsFirstItem2D As Boolean 211 | Dim json_Key As Variant 212 | Dim json_Value As Variant 213 | Dim json_DateStr As String 214 | Dim json_Converted As String 215 | Dim json_SkipItem As Boolean 216 | Dim json_PrettyPrint As Boolean 217 | Dim json_Indentation As String 218 | Dim json_InnerIndentation As String 219 | 220 | json_LBound = -1 221 | json_UBound = -1 222 | json_IsFirstItem = True 223 | json_LBound2D = -1 224 | json_UBound2D = -1 225 | json_IsFirstItem2D = True 226 | json_PrettyPrint = Not IsMissing(Whitespace) 227 | 228 | Select Case VBA.VarType(JsonValue) 229 | Case VBA.vbNull 230 | ConvertToJson = "null" 231 | Case VBA.vbDate 232 | ' Date 233 | json_DateStr = ConvertToIso(VBA.CDate(JsonValue)) 234 | 235 | ConvertToJson = """" & json_DateStr & """" 236 | Case VBA.vbString 237 | ' String (or large number encoded as string) 238 | If Not JsonOptions.UseDoubleForLargeNumbers And json_StringIsLargeNumber(JsonValue) Then 239 | ConvertToJson = JsonValue 240 | Else 241 | ConvertToJson = """" & json_Encode(JsonValue) & """" 242 | End If 243 | Case VBA.vbBoolean 244 | If JsonValue Then 245 | ConvertToJson = "true" 246 | Else 247 | ConvertToJson = "false" 248 | End If 249 | Case VBA.vbArray To VBA.vbArray + VBA.vbByte 250 | If json_PrettyPrint Then 251 | If VBA.VarType(Whitespace) = VBA.vbString Then 252 | json_Indentation = VBA.String$(json_CurrentIndentation + 1, Whitespace) 253 | json_InnerIndentation = VBA.String$(json_CurrentIndentation + 2, Whitespace) 254 | Else 255 | json_Indentation = VBA.Space$((json_CurrentIndentation + 1) * Whitespace) 256 | json_InnerIndentation = VBA.Space$((json_CurrentIndentation + 2) * Whitespace) 257 | End If 258 | End If 259 | 260 | ' Array 261 | json_BufferAppend json_Buffer, "[", json_BufferPosition, json_BufferLength 262 | 263 | On Error Resume Next 264 | 265 | json_LBound = LBound(JsonValue, 1) 266 | json_UBound = UBound(JsonValue, 1) 267 | json_LBound2D = LBound(JsonValue, 2) 268 | json_UBound2D = UBound(JsonValue, 2) 269 | 270 | If json_LBound >= 0 And json_UBound >= 0 Then 271 | For json_Index = json_LBound To json_UBound 272 | If json_IsFirstItem Then 273 | json_IsFirstItem = False 274 | Else 275 | ' Append comma to previous line 276 | json_BufferAppend json_Buffer, ",", json_BufferPosition, json_BufferLength 277 | End If 278 | 279 | If json_LBound2D >= 0 And json_UBound2D >= 0 Then 280 | ' 2D Array 281 | If json_PrettyPrint Then 282 | json_BufferAppend json_Buffer, vbNewLine, json_BufferPosition, json_BufferLength 283 | End If 284 | json_BufferAppend json_Buffer, json_Indentation & "[", json_BufferPosition, json_BufferLength 285 | 286 | For json_Index2D = json_LBound2D To json_UBound2D 287 | If json_IsFirstItem2D Then 288 | json_IsFirstItem2D = False 289 | Else 290 | json_BufferAppend json_Buffer, ",", json_BufferPosition, json_BufferLength 291 | End If 292 | 293 | json_Converted = ConvertToJson(JsonValue(json_Index, json_Index2D), Whitespace, json_CurrentIndentation + 2) 294 | 295 | ' For Arrays/Collections, undefined (Empty/Nothing) is treated as null 296 | If json_Converted = "" Then 297 | ' (nest to only check if converted = "") 298 | If json_IsUndefined(JsonValue(json_Index, json_Index2D)) Then 299 | json_Converted = "null" 300 | End If 301 | End If 302 | 303 | If json_PrettyPrint Then 304 | json_Converted = vbNewLine & json_InnerIndentation & json_Converted 305 | End If 306 | 307 | json_BufferAppend json_Buffer, json_Converted, json_BufferPosition, json_BufferLength 308 | Next json_Index2D 309 | 310 | If json_PrettyPrint Then 311 | json_BufferAppend json_Buffer, vbNewLine, json_BufferPosition, json_BufferLength 312 | End If 313 | 314 | json_BufferAppend json_Buffer, json_Indentation & "]", json_BufferPosition, json_BufferLength 315 | json_IsFirstItem2D = True 316 | Else 317 | ' 1D Array 318 | json_Converted = ConvertToJson(JsonValue(json_Index), Whitespace, json_CurrentIndentation + 1) 319 | 320 | ' For Arrays/Collections, undefined (Empty/Nothing) is treated as null 321 | If json_Converted = "" Then 322 | ' (nest to only check if converted = "") 323 | If json_IsUndefined(JsonValue(json_Index)) Then 324 | json_Converted = "null" 325 | End If 326 | End If 327 | 328 | If json_PrettyPrint Then 329 | json_Converted = vbNewLine & json_Indentation & json_Converted 330 | End If 331 | 332 | json_BufferAppend json_Buffer, json_Converted, json_BufferPosition, json_BufferLength 333 | End If 334 | Next json_Index 335 | End If 336 | 337 | On Error GoTo 0 338 | 339 | If json_PrettyPrint Then 340 | json_BufferAppend json_Buffer, vbNewLine, json_BufferPosition, json_BufferLength 341 | 342 | If VBA.VarType(Whitespace) = VBA.vbString Then 343 | json_Indentation = VBA.String$(json_CurrentIndentation, Whitespace) 344 | Else 345 | json_Indentation = VBA.Space$(json_CurrentIndentation * Whitespace) 346 | End If 347 | End If 348 | 349 | json_BufferAppend json_Buffer, json_Indentation & "]", json_BufferPosition, json_BufferLength 350 | 351 | ConvertToJson = json_BufferToString(json_Buffer, json_BufferPosition) 352 | 353 | ' Dictionary or Collection 354 | Case VBA.vbObject 355 | If json_PrettyPrint Then 356 | If VBA.VarType(Whitespace) = VBA.vbString Then 357 | json_Indentation = VBA.String$(json_CurrentIndentation + 1, Whitespace) 358 | Else 359 | json_Indentation = VBA.Space$((json_CurrentIndentation + 1) * Whitespace) 360 | End If 361 | End If 362 | 363 | ' Dictionary 364 | If VBA.TypeName(JsonValue) = "Dictionary" Then 365 | json_BufferAppend json_Buffer, "{", json_BufferPosition, json_BufferLength 366 | For Each json_Key In JsonValue.Keys 367 | ' For Objects, undefined (Empty/Nothing) is not added to object 368 | json_Converted = ConvertToJson(JsonValue(json_Key), Whitespace, json_CurrentIndentation + 1) 369 | If json_Converted = "" Then 370 | json_SkipItem = json_IsUndefined(JsonValue(json_Key)) 371 | Else 372 | json_SkipItem = False 373 | End If 374 | 375 | If Not json_SkipItem Then 376 | If json_IsFirstItem Then 377 | json_IsFirstItem = False 378 | Else 379 | json_BufferAppend json_Buffer, ",", json_BufferPosition, json_BufferLength 380 | End If 381 | 382 | If json_PrettyPrint Then 383 | json_Converted = vbNewLine & json_Indentation & """" & json_Key & """: " & json_Converted 384 | Else 385 | json_Converted = """" & json_Key & """:" & json_Converted 386 | End If 387 | 388 | json_BufferAppend json_Buffer, json_Converted, json_BufferPosition, json_BufferLength 389 | End If 390 | Next json_Key 391 | 392 | If json_PrettyPrint Then 393 | json_BufferAppend json_Buffer, vbNewLine, json_BufferPosition, json_BufferLength 394 | 395 | If VBA.VarType(Whitespace) = VBA.vbString Then 396 | json_Indentation = VBA.String$(json_CurrentIndentation, Whitespace) 397 | Else 398 | json_Indentation = VBA.Space$(json_CurrentIndentation * Whitespace) 399 | End If 400 | End If 401 | 402 | json_BufferAppend json_Buffer, json_Indentation & "}", json_BufferPosition, json_BufferLength 403 | 404 | ' Collection 405 | ElseIf VBA.TypeName(JsonValue) = "Collection" Then 406 | json_BufferAppend json_Buffer, "[", json_BufferPosition, json_BufferLength 407 | For Each json_Value In JsonValue 408 | If json_IsFirstItem Then 409 | json_IsFirstItem = False 410 | Else 411 | json_BufferAppend json_Buffer, ",", json_BufferPosition, json_BufferLength 412 | End If 413 | 414 | json_Converted = ConvertToJson(json_Value, Whitespace, json_CurrentIndentation + 1) 415 | 416 | ' For Arrays/Collections, undefined (Empty/Nothing) is treated as null 417 | If json_Converted = "" Then 418 | ' (nest to only check if converted = "") 419 | If json_IsUndefined(json_Value) Then 420 | json_Converted = "null" 421 | End If 422 | End If 423 | 424 | If json_PrettyPrint Then 425 | json_Converted = vbNewLine & json_Indentation & json_Converted 426 | End If 427 | 428 | json_BufferAppend json_Buffer, json_Converted, json_BufferPosition, json_BufferLength 429 | Next json_Value 430 | 431 | If json_PrettyPrint Then 432 | json_BufferAppend json_Buffer, vbNewLine, json_BufferPosition, json_BufferLength 433 | 434 | If VBA.VarType(Whitespace) = VBA.vbString Then 435 | json_Indentation = VBA.String$(json_CurrentIndentation, Whitespace) 436 | Else 437 | json_Indentation = VBA.Space$(json_CurrentIndentation * Whitespace) 438 | End If 439 | End If 440 | 441 | json_BufferAppend json_Buffer, json_Indentation & "]", json_BufferPosition, json_BufferLength 442 | End If 443 | 444 | ConvertToJson = json_BufferToString(json_Buffer, json_BufferPosition) 445 | Case VBA.vbInteger, VBA.vbLong, VBA.vbSingle, VBA.vbDouble, VBA.vbCurrency, VBA.vbDecimal 446 | ' Number (use decimals for numbers) 447 | ConvertToJson = VBA.Replace(JsonValue, ",", ".") 448 | Case Else 449 | ' vbEmpty, vbError, vbDataObject, vbByte, vbUserDefinedType 450 | ' Use VBA's built-in to-string 451 | On Error Resume Next 452 | ConvertToJson = JsonValue 453 | On Error GoTo 0 454 | End Select 455 | End Function 456 | 457 | ' ============================================= ' 458 | ' Private Functions 459 | ' ============================================= ' 460 | 461 | Private Function json_ParseObject(json_String As String, ByRef json_Index As Long) As Dictionary 462 | Dim json_Key As String 463 | Dim json_NextChar As String 464 | 465 | Set json_ParseObject = New Dictionary 466 | json_SkipSpaces json_String, json_Index 467 | If VBA.Mid$(json_String, json_Index, 1) <> "{" Then 468 | Err.Raise 10001, "JSONConverter", json_ParseErrorMessage(json_String, json_Index, "Expecting '{'") 469 | Else 470 | json_Index = json_Index + 1 471 | 472 | Do 473 | json_SkipSpaces json_String, json_Index 474 | If VBA.Mid$(json_String, json_Index, 1) = "}" Then 475 | json_Index = json_Index + 1 476 | Exit Function 477 | ElseIf VBA.Mid$(json_String, json_Index, 1) = "," Then 478 | json_Index = json_Index + 1 479 | json_SkipSpaces json_String, json_Index 480 | End If 481 | 482 | json_Key = json_ParseKey(json_String, json_Index) 483 | json_NextChar = json_Peek(json_String, json_Index) 484 | If json_NextChar = "[" Or json_NextChar = "{" Then 485 | Set json_ParseObject.Item(json_Key) = json_ParseValue(json_String, json_Index) 486 | Else 487 | json_ParseObject.Item(json_Key) = json_ParseValue(json_String, json_Index) 488 | End If 489 | Loop 490 | End If 491 | End Function 492 | 493 | Private Function json_ParseArray(json_String As String, ByRef json_Index As Long) As Collection 494 | Set json_ParseArray = New Collection 495 | 496 | json_SkipSpaces json_String, json_Index 497 | If VBA.Mid$(json_String, json_Index, 1) <> "[" Then 498 | Err.Raise 10001, "JSONConverter", json_ParseErrorMessage(json_String, json_Index, "Expecting '['") 499 | Else 500 | json_Index = json_Index + 1 501 | 502 | Do 503 | json_SkipSpaces json_String, json_Index 504 | If VBA.Mid$(json_String, json_Index, 1) = "]" Then 505 | json_Index = json_Index + 1 506 | Exit Function 507 | ElseIf VBA.Mid$(json_String, json_Index, 1) = "," Then 508 | json_Index = json_Index + 1 509 | json_SkipSpaces json_String, json_Index 510 | End If 511 | 512 | json_ParseArray.Add json_ParseValue(json_String, json_Index) 513 | Loop 514 | End If 515 | End Function 516 | 517 | Private Function json_ParseValue(json_String As String, ByRef json_Index As Long) As Variant 518 | json_SkipSpaces json_String, json_Index 519 | Select Case VBA.Mid$(json_String, json_Index, 1) 520 | Case "{" 521 | Set json_ParseValue = json_ParseObject(json_String, json_Index) 522 | Case "[" 523 | Set json_ParseValue = json_ParseArray(json_String, json_Index) 524 | Case """", "'" 525 | json_ParseValue = json_ParseString(json_String, json_Index) 526 | Case Else 527 | If VBA.Mid$(json_String, json_Index, 4) = "true" Then 528 | json_ParseValue = True 529 | json_Index = json_Index + 4 530 | ElseIf VBA.Mid$(json_String, json_Index, 5) = "false" Then 531 | json_ParseValue = False 532 | json_Index = json_Index + 5 533 | ElseIf VBA.Mid$(json_String, json_Index, 4) = "null" Then 534 | json_ParseValue = Null 535 | json_Index = json_Index + 4 536 | ElseIf VBA.InStr("+-0123456789", VBA.Mid$(json_String, json_Index, 1)) Then 537 | json_ParseValue = json_ParseNumber(json_String, json_Index) 538 | Else 539 | Err.Raise 10001, "JSONConverter", json_ParseErrorMessage(json_String, json_Index, "Expecting 'STRING', 'NUMBER', null, true, false, '{', or '['") 540 | End If 541 | End Select 542 | End Function 543 | 544 | Private Function json_ParseString(json_String As String, ByRef json_Index As Long) As String 545 | Dim json_Quote As String 546 | Dim json_Char As String 547 | Dim json_Code As String 548 | Dim json_Buffer As String 549 | Dim json_BufferPosition As Long 550 | Dim json_BufferLength As Long 551 | 552 | json_SkipSpaces json_String, json_Index 553 | 554 | ' Store opening quote to look for matching closing quote 555 | json_Quote = VBA.Mid$(json_String, json_Index, 1) 556 | json_Index = json_Index + 1 557 | 558 | Do While json_Index > 0 And json_Index <= Len(json_String) 559 | json_Char = VBA.Mid$(json_String, json_Index, 1) 560 | 561 | Select Case json_Char 562 | Case "\" 563 | ' Escaped string, \\, or \/ 564 | json_Index = json_Index + 1 565 | json_Char = VBA.Mid$(json_String, json_Index, 1) 566 | 567 | Select Case json_Char 568 | Case """", "\", "/", "'" 569 | json_BufferAppend json_Buffer, json_Char, json_BufferPosition, json_BufferLength 570 | json_Index = json_Index + 1 571 | Case "b" 572 | json_BufferAppend json_Buffer, vbBack, json_BufferPosition, json_BufferLength 573 | json_Index = json_Index + 1 574 | Case "f" 575 | json_BufferAppend json_Buffer, vbFormFeed, json_BufferPosition, json_BufferLength 576 | json_Index = json_Index + 1 577 | Case "n" 578 | json_BufferAppend json_Buffer, vbCrLf, json_BufferPosition, json_BufferLength 579 | json_Index = json_Index + 1 580 | Case "r" 581 | json_BufferAppend json_Buffer, vbCr, json_BufferPosition, json_BufferLength 582 | json_Index = json_Index + 1 583 | Case "t" 584 | json_BufferAppend json_Buffer, vbTab, json_BufferPosition, json_BufferLength 585 | json_Index = json_Index + 1 586 | Case "u" 587 | ' Unicode character escape (e.g. \u00a9 = Copyright) 588 | json_Index = json_Index + 1 589 | json_Code = VBA.Mid$(json_String, json_Index, 4) 590 | json_BufferAppend json_Buffer, VBA.ChrW(VBA.Val("&h" + json_Code)), json_BufferPosition, json_BufferLength 591 | json_Index = json_Index + 4 592 | End Select 593 | Case json_Quote 594 | json_ParseString = json_BufferToString(json_Buffer, json_BufferPosition) 595 | json_Index = json_Index + 1 596 | Exit Function 597 | Case Else 598 | json_BufferAppend json_Buffer, json_Char, json_BufferPosition, json_BufferLength 599 | json_Index = json_Index + 1 600 | End Select 601 | Loop 602 | End Function 603 | 604 | Private Function json_ParseNumber(json_String As String, ByRef json_Index As Long) As Variant 605 | Dim json_Char As String 606 | Dim json_Value As String 607 | Dim json_IsLargeNumber As Boolean 608 | 609 | json_SkipSpaces json_String, json_Index 610 | 611 | Do While json_Index > 0 And json_Index <= Len(json_String) 612 | json_Char = VBA.Mid$(json_String, json_Index, 1) 613 | 614 | If VBA.InStr("+-0123456789.eE", json_Char) Then 615 | ' Unlikely to have massive number, so use simple append rather than buffer here 616 | json_Value = json_Value & json_Char 617 | json_Index = json_Index + 1 618 | Else 619 | ' Excel only stores 15 significant digits, so any numbers larger than that are truncated 620 | ' This can lead to issues when BIGINT's are used (e.g. for Ids or Credit Cards), as they will be invalid above 15 digits 621 | ' See: http://support.microsoft.com/kb/269370 622 | ' 623 | ' Fix: Parse -> String, Convert -> String longer than 15/16 characters containing only numbers and decimal points -> Number 624 | ' (decimal doesn't factor into significant digit count, so if present check for 15 digits + decimal = 16) 625 | json_IsLargeNumber = IIf(InStr(json_Value, "."), Len(json_Value) >= 17, Len(json_Value) >= 16) 626 | If Not JsonOptions.UseDoubleForLargeNumbers And json_IsLargeNumber Then 627 | json_ParseNumber = json_Value 628 | Else 629 | ' VBA.Val does not use regional settings, so guard for comma is not needed 630 | json_ParseNumber = VBA.Val(json_Value) 631 | End If 632 | Exit Function 633 | End If 634 | Loop 635 | End Function 636 | 637 | Private Function json_ParseKey(json_String As String, ByRef json_Index As Long) As String 638 | ' Parse key with single or double quotes 639 | If VBA.Mid$(json_String, json_Index, 1) = """" Or VBA.Mid$(json_String, json_Index, 1) = "'" Then 640 | json_ParseKey = json_ParseString(json_String, json_Index) 641 | ElseIf JsonOptions.AllowUnquotedKeys Then 642 | Dim json_Char As String 643 | Do While json_Index > 0 And json_Index <= Len(json_String) 644 | json_Char = VBA.Mid$(json_String, json_Index, 1) 645 | If (json_Char <> " ") And (json_Char <> ":") Then 646 | json_ParseKey = json_ParseKey & json_Char 647 | json_Index = json_Index + 1 648 | Else 649 | Exit Do 650 | End If 651 | Loop 652 | Else 653 | Err.Raise 10001, "JSONConverter", json_ParseErrorMessage(json_String, json_Index, "Expecting '""' or '''") 654 | End If 655 | 656 | ' Check for colon and skip if present or throw if not present 657 | json_SkipSpaces json_String, json_Index 658 | If VBA.Mid$(json_String, json_Index, 1) <> ":" Then 659 | Err.Raise 10001, "JSONConverter", json_ParseErrorMessage(json_String, json_Index, "Expecting ':'") 660 | Else 661 | json_Index = json_Index + 1 662 | End If 663 | End Function 664 | 665 | Private Function json_IsUndefined(ByVal json_Value As Variant) As Boolean 666 | ' Empty / Nothing -> undefined 667 | Select Case VBA.VarType(json_Value) 668 | Case VBA.vbEmpty 669 | json_IsUndefined = True 670 | Case VBA.vbObject 671 | Select Case VBA.TypeName(json_Value) 672 | Case "Empty", "Nothing" 673 | json_IsUndefined = True 674 | End Select 675 | End Select 676 | End Function 677 | 678 | Private Function json_Encode(ByVal json_Text As Variant) As String 679 | ' Reference: http://www.ietf.org/rfc/rfc4627.txt 680 | ' Escape: ", \, /, backspace, form feed, line feed, carriage return, tab 681 | Dim json_Index As Long 682 | Dim json_Char As String 683 | Dim json_AscCode As Long 684 | Dim json_Buffer As String 685 | Dim json_BufferPosition As Long 686 | Dim json_BufferLength As Long 687 | 688 | For json_Index = 1 To VBA.Len(json_Text) 689 | json_Char = VBA.Mid$(json_Text, json_Index, 1) 690 | json_AscCode = VBA.AscW(json_Char) 691 | 692 | ' When AscW returns a negative number, it returns the twos complement form of that number. 693 | ' To convert the twos complement notation into normal binary notation, add 0xFFF to the return result. 694 | ' https://support.microsoft.com/en-us/kb/272138 695 | If json_AscCode < 0 Then 696 | json_AscCode = json_AscCode + 65536 697 | End If 698 | 699 | ' From spec, ", \, and control characters must be escaped (solidus is optional) 700 | 701 | Select Case json_AscCode 702 | Case 34 703 | ' " -> 34 -> \" 704 | json_Char = "\""" 705 | Case 92 706 | ' \ -> 92 -> \\ 707 | json_Char = "\\" 708 | Case 47 709 | ' / -> 47 -> \/ (optional) 710 | If JsonOptions.EscapeSolidus Then 711 | json_Char = "\/" 712 | End If 713 | Case 8 714 | ' backspace -> 8 -> \b 715 | json_Char = "\b" 716 | Case 12 717 | ' form feed -> 12 -> \f 718 | json_Char = "\f" 719 | Case 10 720 | ' line feed -> 10 -> \n 721 | json_Char = "\n" 722 | Case 13 723 | ' carriage return -> 13 -> \r 724 | json_Char = "\r" 725 | Case 9 726 | ' tab -> 9 -> \t 727 | json_Char = "\t" 728 | Case 0 To 31, 127 To 65535 729 | ' Non-ascii characters -> convert to 4-digit hex 730 | json_Char = "\u" & VBA.right$("0000" & VBA.Hex$(json_AscCode), 4) 731 | End Select 732 | 733 | json_BufferAppend json_Buffer, json_Char, json_BufferPosition, json_BufferLength 734 | Next json_Index 735 | 736 | json_Encode = json_BufferToString(json_Buffer, json_BufferPosition) 737 | End Function 738 | 739 | Private Function json_Peek(json_String As String, ByVal json_Index As Long, Optional json_NumberOfCharacters As Long = 1) As String 740 | ' "Peek" at the next number of characters without incrementing json_Index (ByVal instead of ByRef) 741 | json_SkipSpaces json_String, json_Index 742 | json_Peek = VBA.Mid$(json_String, json_Index, json_NumberOfCharacters) 743 | End Function 744 | 745 | Private Sub json_SkipSpaces(json_String As String, ByRef json_Index As Long) 746 | ' Increment index to skip over spaces 747 | Do While json_Index > 0 And json_Index <= VBA.Len(json_String) And VBA.Mid$(json_String, json_Index, 1) = " " 748 | json_Index = json_Index + 1 749 | Loop 750 | End Sub 751 | 752 | Private Function json_StringIsLargeNumber(json_String As Variant) As Boolean 753 | ' Check if the given string is considered a "large number" 754 | ' (See json_ParseNumber) 755 | 756 | Dim json_Length As Long 757 | Dim json_CharIndex As Long 758 | json_Length = VBA.Len(json_String) 759 | 760 | ' Length with be at least 16 characters and assume will be less than 100 characters 761 | If json_Length >= 16 And json_Length <= 100 Then 762 | Dim json_CharCode As String 763 | 764 | json_StringIsLargeNumber = True 765 | 766 | For json_CharIndex = 1 To json_Length 767 | json_CharCode = VBA.Asc(VBA.Mid$(json_String, json_CharIndex, 1)) 768 | Select Case json_CharCode 769 | ' Look for .|0-9|E|e 770 | Case 46, 48 To 57, 69, 101 771 | ' Continue through characters 772 | Case Else 773 | json_StringIsLargeNumber = False 774 | Exit Function 775 | End Select 776 | Next json_CharIndex 777 | End If 778 | End Function 779 | 780 | Private Function json_ParseErrorMessage(json_String As String, ByRef json_Index As Long, ErrorMessage As String) 781 | ' Provide detailed parse error message, including details of where and what occurred 782 | ' 783 | ' Example: 784 | ' Error parsing JSON: 785 | ' {"abcde":True} 786 | ' ^ 787 | ' Expecting 'STRING', 'NUMBER', null, true, false, '{', or '[' 788 | 789 | Dim json_StartIndex As Long 790 | Dim json_StopIndex As Long 791 | 792 | ' Include 10 characters before and after error (if possible) 793 | json_StartIndex = json_Index - 10 794 | json_StopIndex = json_Index + 10 795 | If json_StartIndex <= 0 Then 796 | json_StartIndex = 1 797 | End If 798 | If json_StopIndex > VBA.Len(json_String) Then 799 | json_StopIndex = VBA.Len(json_String) 800 | End If 801 | 802 | json_ParseErrorMessage = "Error parsing JSON:" & VBA.vbNewLine & _ 803 | VBA.Mid$(json_String, json_StartIndex, json_StopIndex - json_StartIndex + 1) & VBA.vbNewLine & _ 804 | VBA.Space$(json_Index - json_StartIndex) & "^" & VBA.vbNewLine & _ 805 | ErrorMessage 806 | End Function 807 | 808 | Private Sub json_BufferAppend(ByRef json_Buffer As String, _ 809 | ByRef json_Append As Variant, _ 810 | ByRef json_BufferPosition As Long, _ 811 | ByRef json_BufferLength As Long) 812 | ' VBA can be slow to append strings due to allocating a new string for each append 813 | ' Instead of using the traditional append, allocate a large empty string and then copy string at append position 814 | ' 815 | ' Example: 816 | ' Buffer: "abc " 817 | ' Append: "def" 818 | ' Buffer Position: 3 819 | ' Buffer Length: 5 820 | ' 821 | ' Buffer position + Append length > Buffer length -> Append chunk of blank space to buffer 822 | ' Buffer: "abc " 823 | ' Buffer Length: 10 824 | ' 825 | ' Put "def" into buffer at position 3 (0-based) 826 | ' Buffer: "abcdef " 827 | ' 828 | ' Approach based on cStringBuilder from vbAccelerator 829 | ' http://www.vbaccelerator.com/home/VB/Code/Techniques/RunTime_Debug_Tracing/VB6_Tracer_Utility_zip_cStringBuilder_cls.asp 830 | ' 831 | ' and clsStringAppend from Philip Swannell 832 | ' https://github.com/VBA-tools/VBA-JSON/pull/82 833 | 834 | Dim json_AppendLength As Long 835 | Dim json_LengthPlusPosition As Long 836 | 837 | json_AppendLength = VBA.Len(json_Append) 838 | json_LengthPlusPosition = json_AppendLength + json_BufferPosition 839 | 840 | If json_LengthPlusPosition > json_BufferLength Then 841 | ' Appending would overflow buffer, add chunk 842 | ' (double buffer length or append length, whichever is bigger) 843 | Dim json_AddedLength As Long 844 | json_AddedLength = IIf(json_AppendLength > json_BufferLength, json_AppendLength, json_BufferLength) 845 | 846 | json_Buffer = json_Buffer & VBA.Space$(json_AddedLength) 847 | json_BufferLength = json_BufferLength + json_AddedLength 848 | End If 849 | 850 | ' Note: Namespacing with VBA.Mid$ doesn't work properly here, throwing compile error: 851 | ' Function call on left-hand side of assignment must return Variant or Object 852 | Mid$(json_Buffer, json_BufferPosition + 1, json_AppendLength) = CStr(json_Append) 853 | json_BufferPosition = json_BufferPosition + json_AppendLength 854 | End Sub 855 | 856 | Private Function json_BufferToString(ByRef json_Buffer As String, ByVal json_BufferPosition As Long) As String 857 | If json_BufferPosition > 0 Then 858 | json_BufferToString = VBA.left$(json_Buffer, json_BufferPosition) 859 | End If 860 | End Function 861 | 862 | '' 863 | ' VBA-UTC v1.0.6 864 | ' (c) Tim Hall - https://github.com/VBA-tools/VBA-UtcConverter 865 | ' 866 | ' UTC/ISO 8601 Converter for VBA 867 | ' 868 | ' Errors: 869 | ' 10011 - UTC parsing error 870 | ' 10012 - UTC conversion error 871 | ' 10013 - ISO 8601 parsing error 872 | ' 10014 - ISO 8601 conversion error 873 | ' 874 | ' @module UtcConverter 875 | ' @author tim.hall.engr@gmail.com 876 | ' @license MIT (http://www.opensource.org/licenses/mit-license.php) 877 | '' ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ ' 878 | 879 | ' (Declarations moved to top) 880 | 881 | ' ============================================= ' 882 | ' Public Methods 883 | ' ============================================= ' 884 | 885 | '' 886 | ' Parse UTC date to local date 887 | ' 888 | ' @method ParseUtc 889 | ' @param {Date} UtcDate 890 | ' @return {Date} Local date 891 | ' @throws 10011 - UTC parsing error 892 | '' 893 | Public Function ParseUtc(utc_UtcDate As Date) As Date 894 | On Error GoTo utc_ErrorHandling 895 | 896 | #If Mac Then 897 | ParseUtc = utc_ConvertDate(utc_UtcDate) 898 | #Else 899 | Dim utc_TimeZoneInfo As utc_TIME_ZONE_INFORMATION 900 | Dim utc_LocalDate As utc_SYSTEMTIME 901 | 902 | utc_GetTimeZoneInformation utc_TimeZoneInfo 903 | utc_SystemTimeToTzSpecificLocalTime utc_TimeZoneInfo, utc_DateToSystemTime(utc_UtcDate), utc_LocalDate 904 | 905 | ParseUtc = utc_SystemTimeToDate(utc_LocalDate) 906 | #End If 907 | 908 | Exit Function 909 | 910 | utc_ErrorHandling: 911 | Err.Raise 10011, "UtcConverter.ParseUtc", "UTC parsing error: " & Err.Number & " - " & Err.Description 912 | End Function 913 | 914 | '' 915 | ' Convert local date to UTC date 916 | ' 917 | ' @method ConvertToUrc 918 | ' @param {Date} utc_LocalDate 919 | ' @return {Date} UTC date 920 | ' @throws 10012 - UTC conversion error 921 | '' 922 | Public Function ConvertToUtc(utc_LocalDate As Date) As Date 923 | On Error GoTo utc_ErrorHandling 924 | 925 | #If Mac Then 926 | ConvertToUtc = utc_ConvertDate(utc_LocalDate, utc_ConvertToUtc:=True) 927 | #Else 928 | Dim utc_TimeZoneInfo As utc_TIME_ZONE_INFORMATION 929 | Dim utc_UtcDate As utc_SYSTEMTIME 930 | 931 | utc_GetTimeZoneInformation utc_TimeZoneInfo 932 | utc_TzSpecificLocalTimeToSystemTime utc_TimeZoneInfo, utc_DateToSystemTime(utc_LocalDate), utc_UtcDate 933 | 934 | ConvertToUtc = utc_SystemTimeToDate(utc_UtcDate) 935 | #End If 936 | 937 | Exit Function 938 | 939 | utc_ErrorHandling: 940 | Err.Raise 10012, "UtcConverter.ConvertToUtc", "UTC conversion error: " & Err.Number & " - " & Err.Description 941 | End Function 942 | 943 | '' 944 | ' Parse ISO 8601 date string to local date 945 | ' 946 | ' @method ParseIso 947 | ' @param {Date} utc_IsoString 948 | ' @return {Date} Local date 949 | ' @throws 10013 - ISO 8601 parsing error 950 | '' 951 | Public Function ParseIso(utc_IsoString As String) As Date 952 | On Error GoTo utc_ErrorHandling 953 | 954 | Dim utc_Parts() As String 955 | Dim utc_DateParts() As String 956 | Dim utc_TimeParts() As String 957 | Dim utc_OffsetIndex As Long 958 | Dim utc_HasOffset As Boolean 959 | Dim utc_NegativeOffset As Boolean 960 | Dim utc_OffsetParts() As String 961 | Dim utc_Offset As Date 962 | 963 | utc_Parts = VBA.Split(utc_IsoString, "T") 964 | utc_DateParts = VBA.Split(utc_Parts(0), "-") 965 | ParseIso = VBA.DateSerial(VBA.CInt(utc_DateParts(0)), VBA.CInt(utc_DateParts(1)), VBA.CInt(utc_DateParts(2))) 966 | 967 | If UBound(utc_Parts) > 0 Then 968 | If VBA.InStr(utc_Parts(1), "Z") Then 969 | utc_TimeParts = VBA.Split(VBA.Replace(utc_Parts(1), "Z", ""), ":") 970 | Else 971 | utc_OffsetIndex = VBA.InStr(1, utc_Parts(1), "+") 972 | If utc_OffsetIndex = 0 Then 973 | utc_NegativeOffset = True 974 | utc_OffsetIndex = VBA.InStr(1, utc_Parts(1), "-") 975 | End If 976 | 977 | If utc_OffsetIndex > 0 Then 978 | utc_HasOffset = True 979 | utc_TimeParts = VBA.Split(VBA.left$(utc_Parts(1), utc_OffsetIndex - 1), ":") 980 | utc_OffsetParts = VBA.Split(VBA.right$(utc_Parts(1), Len(utc_Parts(1)) - utc_OffsetIndex), ":") 981 | 982 | Select Case UBound(utc_OffsetParts) 983 | Case 0 984 | utc_Offset = TimeSerial(VBA.CInt(utc_OffsetParts(0)), 0, 0) 985 | Case 1 986 | utc_Offset = TimeSerial(VBA.CInt(utc_OffsetParts(0)), VBA.CInt(utc_OffsetParts(1)), 0) 987 | Case 2 988 | ' VBA.Val does not use regional settings, use for seconds to avoid decimal/comma issues 989 | utc_Offset = TimeSerial(VBA.CInt(utc_OffsetParts(0)), VBA.CInt(utc_OffsetParts(1)), Int(VBA.Val(utc_OffsetParts(2)))) 990 | End Select 991 | 992 | If utc_NegativeOffset Then: utc_Offset = -utc_Offset 993 | Else 994 | utc_TimeParts = VBA.Split(utc_Parts(1), ":") 995 | End If 996 | End If 997 | 998 | Select Case UBound(utc_TimeParts) 999 | Case 0 1000 | ParseIso = ParseIso + VBA.TimeSerial(VBA.CInt(utc_TimeParts(0)), 0, 0) 1001 | Case 1 1002 | ParseIso = ParseIso + VBA.TimeSerial(VBA.CInt(utc_TimeParts(0)), VBA.CInt(utc_TimeParts(1)), 0) 1003 | Case 2 1004 | ' VBA.Val does not use regional settings, use for seconds to avoid decimal/comma issues 1005 | ParseIso = ParseIso + VBA.TimeSerial(VBA.CInt(utc_TimeParts(0)), VBA.CInt(utc_TimeParts(1)), Int(VBA.Val(utc_TimeParts(2)))) 1006 | End Select 1007 | 1008 | ParseIso = ParseUtc(ParseIso) 1009 | 1010 | If utc_HasOffset Then 1011 | ParseIso = ParseIso - utc_Offset 1012 | End If 1013 | End If 1014 | 1015 | Exit Function 1016 | 1017 | utc_ErrorHandling: 1018 | Err.Raise 10013, "UtcConverter.ParseIso", "ISO 8601 parsing error for " & utc_IsoString & ": " & Err.Number & " - " & Err.Description 1019 | End Function 1020 | 1021 | '' 1022 | ' Convert local date to ISO 8601 string 1023 | ' 1024 | ' @method ConvertToIso 1025 | ' @param {Date} utc_LocalDate 1026 | ' @return {Date} ISO 8601 string 1027 | ' @throws 10014 - ISO 8601 conversion error 1028 | '' 1029 | Public Function ConvertToIso(utc_LocalDate As Date) As String 1030 | On Error GoTo utc_ErrorHandling 1031 | 1032 | ConvertToIso = VBA.format$(ConvertToUtc(utc_LocalDate), "yyyy-mm-ddTHH:mm:ss.000Z") 1033 | 1034 | Exit Function 1035 | 1036 | utc_ErrorHandling: 1037 | Err.Raise 10014, "UtcConverter.ConvertToIso", "ISO 8601 conversion error: " & Err.Number & " - " & Err.Description 1038 | End Function 1039 | 1040 | ' ============================================= ' 1041 | ' Private Functions 1042 | ' ============================================= ' 1043 | 1044 | #If Mac Then 1045 | 1046 | Private Function utc_ConvertDate(utc_Value As Date, Optional utc_ConvertToUtc As Boolean = False) As Date 1047 | Dim utc_ShellCommand As String 1048 | Dim utc_Result As utc_ShellResult 1049 | Dim utc_Parts() As String 1050 | Dim utc_DateParts() As String 1051 | Dim utc_TimeParts() As String 1052 | 1053 | If utc_ConvertToUtc Then 1054 | utc_ShellCommand = "date -ur `date -jf '%Y-%m-%d %H:%M:%S' " & _ 1055 | "'" & VBA.format$(utc_Value, "yyyy-mm-dd HH:mm:ss") & "' " & _ 1056 | " +'%s'` +'%Y-%m-%d %H:%M:%S'" 1057 | Else 1058 | utc_ShellCommand = "date -jf '%Y-%m-%d %H:%M:%S %z' " & _ 1059 | "'" & VBA.format$(utc_Value, "yyyy-mm-dd HH:mm:ss") & " +0000' " & _ 1060 | "+'%Y-%m-%d %H:%M:%S'" 1061 | End If 1062 | 1063 | utc_Result = utc_ExecuteInShell(utc_ShellCommand) 1064 | 1065 | If utc_Result.utc_Output = "" Then 1066 | Err.Raise 10015, "UtcConverter.utc_ConvertDate", "'date' command failed" 1067 | Else 1068 | utc_Parts = Split(utc_Result.utc_Output, " ") 1069 | utc_DateParts = Split(utc_Parts(0), "-") 1070 | utc_TimeParts = Split(utc_Parts(1), ":") 1071 | 1072 | utc_ConvertDate = DateSerial(utc_DateParts(0), utc_DateParts(1), utc_DateParts(2)) + _ 1073 | TimeSerial(utc_TimeParts(0), utc_TimeParts(1), utc_TimeParts(2)) 1074 | End If 1075 | End Function 1076 | 1077 | Private Function utc_ExecuteInShell(utc_ShellCommand As String) As utc_ShellResult 1078 | #If VBA7 Then 1079 | Dim utc_File As LongPtr 1080 | Dim utc_Read As LongPtr 1081 | #Else 1082 | Dim utc_File As Long 1083 | Dim utc_Read As Long 1084 | #End If 1085 | 1086 | Dim utc_Chunk As String 1087 | 1088 | On Error GoTo utc_ErrorHandling 1089 | utc_File = utc_popen(utc_ShellCommand, "r") 1090 | 1091 | If utc_File = 0 Then: Exit Function 1092 | 1093 | Do While utc_feof(utc_File) = 0 1094 | utc_Chunk = VBA.Space$(50) 1095 | utc_Read = CLng(utc_fread(utc_Chunk, 1, Len(utc_Chunk) - 1, utc_File)) 1096 | If utc_Read > 0 Then 1097 | utc_Chunk = VBA.left$(utc_Chunk, CLng(utc_Read)) 1098 | utc_ExecuteInShell.utc_Output = utc_ExecuteInShell.utc_Output & utc_Chunk 1099 | End If 1100 | Loop 1101 | 1102 | utc_ErrorHandling: 1103 | utc_ExecuteInShell.utc_ExitCode = CLng(utc_pclose(utc_File)) 1104 | End Function 1105 | 1106 | #Else 1107 | 1108 | Private Function utc_DateToSystemTime(utc_Value As Date) As utc_SYSTEMTIME 1109 | utc_DateToSystemTime.utc_wYear = VBA.Year(utc_Value) 1110 | utc_DateToSystemTime.utc_wMonth = VBA.Month(utc_Value) 1111 | utc_DateToSystemTime.utc_wDay = VBA.Day(utc_Value) 1112 | utc_DateToSystemTime.utc_wHour = VBA.Hour(utc_Value) 1113 | utc_DateToSystemTime.utc_wMinute = VBA.Minute(utc_Value) 1114 | utc_DateToSystemTime.utc_wSecond = VBA.Second(utc_Value) 1115 | utc_DateToSystemTime.utc_wMilliseconds = 0 1116 | End Function 1117 | 1118 | Private Function utc_SystemTimeToDate(utc_Value As utc_SYSTEMTIME) As Date 1119 | utc_SystemTimeToDate = DateSerial(utc_Value.utc_wYear, utc_Value.utc_wMonth, utc_Value.utc_wDay) + _ 1120 | TimeSerial(utc_Value.utc_wHour, utc_Value.utc_wMinute, utc_Value.utc_wSecond) 1121 | End Function 1122 | 1123 | #End If 1124 | 1125 | -------------------------------------------------------------------------------- /src/Sample.bas: -------------------------------------------------------------------------------- 1 | Attribute VB_Name = "Sample" 2 | Option Explicit 3 | 4 | Public driver As WebDriver 5 | 6 | Sub Example() 7 | Set driver = New WebDriver 8 | 9 | driver.Chrome 10 | driver.OpenBrowser 11 | driver.MaximizeWindow 12 | driver.NavigateTo "https://www.selenium.dev/" 13 | driver.FindElement(By.XPath, "/html/body/header/nav/div/ul/li[4]/a").Click 14 | driver.TakeScreenshot ThisWorkbook.path + "./screenshot1.png" 15 | 16 | driver.CloseBrowser 17 | driver.Quit 18 | Set driver = Nothing 19 | End Sub 20 | 21 | Sub Example2() 22 | Set driver = New WebDriver 23 | 24 | driver.Edge 25 | driver.OpenBrowser 26 | driver.NavigateTo "https://www.python.org/" 27 | driver.MaximizeWindow 28 | driver.FindElement(By.ID, "id-search-field").SendKeys "machine learning" 29 | driver.FindElement(By.ID, "submit").Click 30 | driver.TakeScreenshot ThisWorkbook.path + "./screenshot2.png" 31 | driver.MinimizeWindow 32 | driver.CloseBrowser 33 | 34 | driver.Quit 35 | Set driver = Nothing 36 | End Sub 37 | -------------------------------------------------------------------------------- /src/WebDriver.cls: -------------------------------------------------------------------------------- 1 | VERSION 1.0 CLASS 2 | BEGIN 3 | MultiUse = -1 'True 4 | END 5 | Attribute VB_Name = "WebDriver" 6 | Attribute VB_GlobalNameSpace = False 7 | Attribute VB_Creatable = False 8 | Attribute VB_PredeclaredId = False 9 | Attribute VB_Exposed = False 10 | '------------------------------------------------------------------ 11 | ' SeleniumWrapperVBA v1.1.0 12 | ' 13 | ' Auther: er-ri (https://github.com/er-ri/selenium-wrapper-vba) 14 | ' Date: 2022/03 15 | ' License: MIT (https://opensource.org/licenses/MIT) 16 | '------------------------------------------------------------------ 17 | ' 18 | ' ================================================================= 19 | ' #Class WebDriver 20 | ' ================================================================= 21 | Option Explicit 22 | 23 | ' Standard WebDriver commands. 24 | Public W3C_NEW_SESSION 25 | Public W3C_DELETE_SESSION 26 | Public W3C_STATUS 27 | Public W3C_GET_TIMEOUTS 28 | Public W3C_SET_TIMEOUTS 29 | Public W3C_GO 30 | Public W3C_GET_CURRENT_URL 31 | Public W3C_BACK 32 | Public W3C_FORWARD 33 | Public W3C_REFRESH 34 | Public W3C_GET_TITLE 35 | Public W3C_GET_WINDOW_HANDLE 36 | Public W3C_CLOSE_WINDOW 37 | Public W3C_SWITCH_TO_WINDOW 38 | Public W3C_GET_WINDOW_HANDLES 39 | Public W3C_NEW_WINDOW 40 | Public W3C_SWITCH_TO_FRAME 41 | Public W3C_SWITCH_TO_PARENT_FRAME 42 | Public W3C_GET_WINDOW_RECT 43 | Public W3C_SET_WINDOW_RECT 44 | Public W3C_MAXIMIZE_WINDOW 45 | Public W3C_MINIMIZE_WINDOW 46 | Public W3C_FULLSCREEN_WINDOW 47 | Public W3C_FIND_ELEMENT 48 | Public W3C_FIND_ELEMENTS 49 | Public W3C_FIND_ELEMENT_FROM_ELEMENT 50 | Public W3C_FIND_ELEMENTS_FROM_ELEMENT 51 | Public W3C_FIND_ELEMENT_FROM_SHADOW_ROOT 52 | Public W3C_FIND_ELEMENTS_FROM_SHADOW_ROOT 53 | Public W3C_GET_ACTIVE_ELEMENT 54 | Public W3C_GET_ELEMENT_SHADOW_ROOT 55 | Public W3C_IS_ELEMENT_SELECTED 56 | Public W3C_GET_ELEMENT_ATTRIBUTE 57 | Public W3C_GET_ELEMENT_PROPERTY 58 | Public W3C_GET_ELEMENT_CSS_VALUE 59 | Public W3C_GET_ELEMENT_TEXT 60 | Public W3C_GET_ELEMENT_TAG_NAME 61 | Public W3C_GET_ELEMENT_RECT 62 | Public W3C_IS_ELEMENT_ENABLED 63 | Public W3C_ELEMENT_CLICK 64 | Public W3C_ELEMENT_CLEAR 65 | Public W3C_ELEMENT_SEND_KEYS 66 | Public W3C_GET_PAGE_SOURCE 67 | Public W3C_EXECUTE_SCRIPT 68 | Public W3C_EXECUTE_ASYNC_SCRIPT 69 | Public W3C_GET_ALL_COOKIES 70 | Public W3C_GET_NAMED_COOKIE 71 | Public W3C_ADD_COOKIE 72 | Public W3C_DELETE_COOKIE 73 | Public W3C_DELETE_ALL_COOKIES 74 | Public W3C_PERFORM_ACTIONS 75 | Public W3C_RELEASE_ACTIONS 76 | Public W3C_DISMISS_ALERT 77 | Public W3C_ACCEPT_ALERT 78 | Public W3C_GET_ALERT_TEXT 79 | Public W3C_SEND_ALERT_TEXT 80 | Public W3C_TAKE_SCREENSHOT 81 | Public W3C_TAKE_ELEMENT_SCREENSHOT 82 | 83 | Private Const ELEMENT_KEY = "element-6066-11e4-a52e-4f735466cecf" 84 | Private Const SHADOWROOT_KEY = "shadow-6066-11e4-a52e-4f735466cecf" 85 | 86 | Public Enum By 87 | ClassName = 1 88 | ID = 2 89 | name = 3 90 | LinkText = 4 91 | PartialLinkText = 5 92 | TagName = 6 93 | XPath = 7 94 | CSS = 8 95 | End Enum 96 | 97 | Private m_RemoteEndUrl As String 98 | Private m_PID As String 99 | Private m_MyOptions As WebDriverOptions 100 | 101 | '------------------------------------------------------------------ 102 | ' Encapsulation 103 | '------------------------------------------------------------------ 104 | Public Property Get RemoteEndUrl() As String 105 | RemoteEndUrl = m_RemoteEndUrl 106 | End Property 107 | 108 | Public Property Let RemoteEndUrl(ByVal new_RemoteEndUrl As String) 109 | m_RemoteEndUrl = new_RemoteEndUrl 110 | End Property 111 | 112 | Public Property Get PID() As String 113 | PID = m_PID 114 | End Property 115 | 116 | Public Property Let PID(ByVal new_PID As String) 117 | m_PID = new_PID 118 | End Property 119 | 120 | Public Property Get MyOptions() As WebDriverOptions 121 | Set MyOptions = m_MyOptions 122 | End Property 123 | 124 | Public Property Let MyOptions(ByVal new_MyOptions As WebDriverOptions) 125 | Set m_MyOptions = new_MyOptions 126 | End Property 127 | '------------------------------------------------------------------ 128 | ' Constructor 129 | '------------------------------------------------------------------ 130 | Private Sub Class_Initialize() 131 | W3C_NEW_SESSION = Array("POST", "/session") 132 | W3C_DELETE_SESSION = Array("DELETE", "/session/{session id}") 133 | W3C_STATUS = Array("GET", "/status") 134 | W3C_GET_TIMEOUTS = Array("GET", "/session/{session id}/timeouts") 135 | W3C_SET_TIMEOUTS = Array("POST", "/session/{session id}/timeouts") 136 | W3C_GO = Array("POST", "/session/{session id}/url") 137 | W3C_BACK = Array("POST", "/session/{session id}/back") 138 | W3C_REFRESH = Array("POST", "/session/{session id}/refresh") 139 | W3C_GET_WINDOW_HANDLE = Array("GET", "/session/{session id}/window") 140 | W3C_NEW_WINDOW = Array("POST", "/session/{session id}/window/new") 141 | W3C_SWITCH_TO_WINDOW = Array("POST", "/session/{session id}/window") 142 | W3C_SWITCH_TO_FRAME = Array("POST", "/session/{session id}/frame") 143 | W3C_GET_WINDOW_RECT = Array("GET", "/session/{session id}/window/rect") 144 | W3C_MAXIMIZE_WINDOW = Array("POST", "/session/{session id}/window/maximize") 145 | W3C_FULLSCREEN_WINDOW = Array("POST", "/session/{session id}/window/fullscreen") 146 | W3C_FIND_ELEMENTS = Array("POST", "/session/{session id}/elements") 147 | W3C_FIND_ELEMENTS_FROM_ELEMENT = Array("POST", "/session/{session id}/element/{element id}/elements") 148 | W3C_FIND_ELEMENT_FROM_SHADOW_ROOT = Array("POST", "/session/{session id}/shadow/{shadow id}/element") 149 | W3C_FIND_ELEMENTS_FROM_SHADOW_ROOT = Array("POST", "/session/{session id}/shadow/{shadow id}/elements") 150 | W3C_GET_ELEMENT_SHADOW_ROOT = Array("GET", "/session/{session id}/element/{element id}/shadow") 151 | W3C_IS_ELEMENT_SELECTED = Array("GET", "/session/{session id}/element/{element id}/selected") 152 | W3C_GET_ELEMENT_PROPERTY = Array("GET", "/session/{session id}/element/{element id}/property/{name}") 153 | W3C_GET_ELEMENT_TEXT = Array("GET", "/session/{session id}/element/{element id}/text") 154 | W3C_GET_ELEMENT_RECT = Array("GET", "/session/{session id}/element/{element id}/rect") 155 | W3C_ELEMENT_CLICK = Array("POST", "/session/{session id}/element/{element id}/click") 156 | W3C_ELEMENT_SEND_KEYS = Array("POST", "/session/{session id}/element/{element id}/value") 157 | W3C_EXECUTE_SCRIPT = Array("POST", "/session/{session id}/execute/sync") 158 | W3C_GET_ALL_COOKIES = Array("GET", "/session/{session id}/cookie") 159 | W3C_ADD_COOKIE = Array("POST", "/session/{session id}/cookie") 160 | W3C_DELETE_ALL_COOKIES = Array("DELETE", "/session/{session id)/cookie") 161 | W3C_RELEASE_ACTIONS = Array("DELETE", "/session/{session id}/actions") 162 | W3C_ACCEPT_ALERT = Array("POST", "/session/{session id}/alert/accept") 163 | W3C_SEND_ALERT_TEXT = Array("POST", "/session/{session id}/alert/text") 164 | W3C_TAKE_ELEMENT_SCREENSHOT = Array("GET", "/session/{session id}/element/{element id}/screenshot") 165 | W3C_GET_CURRENT_URL = Array("GET", "/session/{session id}/url") 166 | W3C_FORWARD = Array("POST", "/session/{session id}/forward") 167 | W3C_GET_TITLE = Array("GET", "/session/{session id}/title") 168 | W3C_CLOSE_WINDOW = Array("DELETE", "/session/{session id}/window") 169 | W3C_GET_WINDOW_HANDLES = Array("GET", "/session/{session id}/window/handles") 170 | W3C_SWITCH_TO_PARENT_FRAME = Array("POST", "/session/{session id}/frame/parent") 171 | W3C_SET_WINDOW_RECT = Array("POST", "/session/{session id}/window/rect") 172 | W3C_MINIMIZE_WINDOW = Array("POST", "/session/{session id}/window/minimize") 173 | W3C_FIND_ELEMENT = Array("POST", "/session/{session id}/element") 174 | W3C_FIND_ELEMENT_FROM_ELEMENT = Array("POST", "/session/{session id}/element/{element id}/element") 175 | W3C_GET_ACTIVE_ELEMENT = Array("GET", "/session/{session id}/element/active") 176 | W3C_GET_ELEMENT_ATTRIBUTE = Array("GET", "/session/{session id}/element/{element id}/attribute/{name}") 177 | W3C_GET_ELEMENT_CSS_VALUE = Array("GET", "/session/{session id}/element/{element id}/css/{property name}") 178 | W3C_GET_ELEMENT_TAG_NAME = Array("GET", "/session/{session id}/element/{element id}/name") 179 | W3C_IS_ELEMENT_ENABLED = Array("GET", "/session/{session id}/element/{element id}/enabled") 180 | W3C_ELEMENT_CLEAR = Array("POST", "/session/{session id}/element/{element id}/clear") 181 | W3C_GET_PAGE_SOURCE = Array("GET", "/session/{session id}/source") 182 | W3C_EXECUTE_ASYNC_SCRIPT = Array("POST", "/session/{session id}/execute/async") 183 | W3C_GET_NAMED_COOKIE = Array("GET", "/session/{session id}/cookie/{name}") 184 | W3C_DELETE_COOKIE = Array("DELETE", "/session/{session id}/cookie/{name}") 185 | W3C_PERFORM_ACTIONS = Array("POST", "/session/{session id}/actions") 186 | W3C_DISMISS_ALERT = Array("POST", "/session/{session id}/alert/dismiss") 187 | W3C_GET_ALERT_TEXT = Array("GET", "/session/{session id}/alert/text") 188 | W3C_TAKE_SCREENSHOT = Array("GET", "/session/{session id}/screenshot") 189 | W3C_TAKE_ELEMENT_SCREENSHOT = Array("GET", "/session/{session id}/element/{element id}/screenshot") 190 | 191 | MyOptions = New WebDriverOptions 192 | End Sub 193 | 194 | '------------------------------------------------------------------ 195 | ' Browser Implementation 196 | '------------------------------------------------------------------ 197 | Public Sub Edge(Optional ByVal driverPath As String = "msedgedriver.exe", _ 198 | Optional ByVal port As Integer = 9516, _ 199 | Optional ByVal winStyle As VbAppWinStyle = vbMinimizedNoFocus) 200 | Dim cmdLaunch As String 201 | 202 | cmdLaunch = driverPath & " --port=" & CStr(port) 203 | RemoteEndUrl = "http://localhost:" & CStr(port) 204 | PID = Shell(cmdLaunch, winStyle) 205 | End Sub 206 | 207 | Public Sub Chrome(Optional ByVal driverPath As String = "chromedriver.exe", _ 208 | Optional ByVal port As Integer = 9515, _ 209 | Optional ByVal winStyle As VbAppWinStyle = vbMinimizedNoFocus) 210 | Dim cmdLaunch As String 211 | 212 | cmdLaunch = driverPath & " --port=" & CStr(port) 213 | RemoteEndUrl = "http://localhost:" & CStr(port) 214 | PID = Shell(cmdLaunch, winStyle) 215 | End Sub 216 | 217 | Public Sub Firefox(Optional ByVal driverPath As String = "geckodriver.exe", _ 218 | Optional ByVal port As Integer = 4444, _ 219 | Optional ByVal winStyle As VbAppWinStyle = vbMinimizedNoFocus) 220 | Dim cmdLaunch As String 221 | 222 | cmdLaunch = driverPath & " --port " & CStr(port) 223 | RemoteEndUrl = "http://localhost:" & CStr(port) 224 | PID = Shell(cmdLaunch, winStyle) 225 | End Sub 226 | 227 | Public Sub InternetExplorer(Optional ByVal driverPath As String = "IEDriverServer.exe", _ 228 | Optional ByVal port = 5555, _ 229 | Optional ByVal winStyle As VbAppWinStyle = vbMinimizedNoFocus) 230 | Dim cmdLaunch As String 231 | 232 | cmdLaunch = driverPath & " /port=" & CStr(port) 233 | RemoteEndUrl = "http://localhost:" & CStr(port) 234 | PID = Shell(cmdLaunch, winStyle) 235 | End Sub 236 | 237 | Public Sub Quit() 238 | Shell "taskkill /T /F /pid " & PID 239 | End Sub 240 | 241 | '------------------------------------------------------------------ 242 | ' W3C Standard Endpoints 243 | ' https://www.w3.org/TR/webdriver/ 244 | '------------------------------------------------------------------ 245 | Public Sub OpenBrowser(Optional ByVal userOptions As WebDriverOptions = Nothing) 246 | Dim urlParameters As New Dictionary, _ 247 | requestBody As New Dictionary, _ 248 | responseBody As Dictionary 249 | 250 | If Not userOptions Is Nothing Then 251 | MyOptions = userOptions 252 | End If 253 | 254 | requestBody.Add "capabilities", MyOptions.ConstructCapabilities() 255 | 256 | Set responseBody = SendCommand2Webdriver(W3C_NEW_SESSION, urlParameters, requestBody) 257 | 258 | MyOptions.Capabilities = responseBody("value") 259 | End Sub 260 | 261 | Public Sub CloseBrowser() 262 | Dim urlParameters As New Dictionary 263 | 264 | urlParameters.Add "{session id}", MyOptions.Capabilities("sessionId") 265 | 266 | SendCommand2Webdriver W3C_DELETE_SESSION, urlParameters 267 | End Sub 268 | 269 | Public Function GetStatus() 270 | Dim urlParameters As New Dictionary, _ 271 | requestBody As New Dictionary, _ 272 | responseBody As Dictionary 273 | 274 | Set responseBody = SendCommand2Webdriver(W3C_STATUS, urlParameters, requestBody) 275 | 276 | Set GetStatus = responseBody("value") 277 | End Function 278 | 279 | Public Function GetTimeouts() 280 | Dim urlParameters As New Dictionary, _ 281 | requestBody As New Dictionary, _ 282 | responseBody As Dictionary 283 | 284 | urlParameters.Add "{session id}", MyOptions.Capabilities("sessionId") 285 | 286 | Set responseBody = SendCommand2Webdriver(W3C_GET_TIMEOUTS, urlParameters, requestBody) 287 | 288 | Set GetTimeouts = responseBody("value") 289 | End Function 290 | 291 | Public Sub SetTimeouts(Optional ByVal script As Long = 30000, _ 292 | Optional ByVal pageLoad As Long = 300000, _ 293 | Optional ByVal implicit As Long = 0) 294 | Dim urlParameters As New Dictionary, _ 295 | requestBody As New Dictionary 296 | 297 | urlParameters.Add "{session id}", MyOptions.Capabilities("sessionId") 298 | requestBody.Add "script", script 299 | requestBody.Add "pageLoad", pageLoad 300 | requestBody.Add "implicit", implicit ' a time to wait in milliseconds for the element location strategy 301 | 302 | SendCommand2Webdriver W3C_SET_TIMEOUTS, urlParameters, requestBody 303 | End Sub 304 | 305 | Public Sub NavigateTo(ByVal url As String) 306 | Dim urlParameters As New Dictionary, _ 307 | requestBody As New Dictionary 308 | 309 | urlParameters.Add "{session id}", MyOptions.Capabilities("sessionId") 310 | requestBody.Add "url", url 311 | 312 | SendCommand2Webdriver W3C_GO, urlParameters, requestBody 313 | End Sub 314 | 315 | Public Function GetCurrentURL() As String 316 | Dim urlParameters As New Dictionary, _ 317 | requestBody As New Dictionary, _ 318 | responseBody As Dictionary 319 | 320 | urlParameters.Add "{session id}", MyOptions.Capabilities("sessionId") 321 | 322 | Set responseBody = SendCommand2Webdriver(W3C_GET_CURRENT_URL, urlParameters, requestBody) 323 | 324 | GetCurrentURL = responseBody("value") 325 | End Function 326 | 327 | Public Sub Back() 328 | Dim urlParameters As New Dictionary, _ 329 | requestBody As New Dictionary 330 | 331 | urlParameters.Add "{session id}", MyOptions.Capabilities("sessionId") 332 | 333 | SendCommand2Webdriver W3C_BACK, urlParameters, requestBody 334 | End Sub 335 | 336 | Public Sub Forward() 337 | Dim urlParameters As New Dictionary, _ 338 | requestBody As New Dictionary 339 | 340 | urlParameters.Add "{session id}", MyOptions.Capabilities("sessionId") 341 | 342 | SendCommand2Webdriver W3C_FORWARD, urlParameters, requestBody 343 | End Sub 344 | 345 | Public Sub Refresh() 346 | Dim urlParameters As New Dictionary, _ 347 | requestBody As New Dictionary 348 | 349 | urlParameters.Add "{session id}", MyOptions.Capabilities("sessionId") 350 | 351 | SendCommand2Webdriver W3C_REFRESH, urlParameters, requestBody 352 | End Sub 353 | 354 | Public Function GetTitle() As String 355 | Dim urlParameters As New Dictionary, _ 356 | requestBody As New Dictionary, _ 357 | responseBody As Dictionary 358 | 359 | urlParameters.Add "{session id}", MyOptions.Capabilities("sessionId") 360 | 361 | Set responseBody = SendCommand2Webdriver(W3C_GET_TITLE, urlParameters, requestBody) 362 | 363 | GetTitle = responseBody("value") 364 | End Function 365 | 366 | Public Function GetWindowHandle() 367 | Dim urlParameters As New Dictionary, _ 368 | requestBody As New Dictionary, _ 369 | responseBody As Dictionary 370 | 371 | urlParameters.Add "{session id}", MyOptions.Capabilities("sessionId") 372 | 373 | Set responseBody = SendCommand2Webdriver(W3C_GET_WINDOW_HANDLE, urlParameters, requestBody) 374 | 375 | GetWindowHandle = responseBody("value") 376 | End Function 377 | 378 | Public Sub CloseWindow() 379 | Dim urlParameters As New Dictionary, _ 380 | requestBody As New Dictionary 381 | 382 | urlParameters.Add "{session id}", MyOptions.Capabilities("sessionId") 383 | 384 | SendCommand2Webdriver W3C_CLOSE_WINDOW, urlParameters, requestBody 385 | End Sub 386 | 387 | Public Sub SwitchToWindow(ByVal handle As String) 388 | Dim urlParameters As New Dictionary, _ 389 | requestBody As New Dictionary 390 | 391 | urlParameters.Add "{session id}", MyOptions.Capabilities("sessionId") 392 | requestBody.Add "handle", handle 393 | 394 | SendCommand2Webdriver W3C_SWITCH_TO_WINDOW, urlParameters, requestBody 395 | End Sub 396 | 397 | Public Function GetWindowHandles() 398 | Dim urlParameters As New Dictionary, _ 399 | requestBody As New Dictionary, _ 400 | responseBody As Dictionary 401 | 402 | urlParameters.Add "{session id}", MyOptions.Capabilities("sessionId") 403 | 404 | Set responseBody = SendCommand2Webdriver(W3C_GET_WINDOW_HANDLES, urlParameters, requestBody) 405 | 406 | Set GetWindowHandles = responseBody("value") 407 | End Function 408 | 409 | ' Return: JSON Object 410 | ' "handle": The value of handle. 411 | ' "type": Let type be "tab" if the newly created window shares an OS-level window with the current browsing context, or "window" otherwise. 412 | Public Function NewWindow() As Dictionary 413 | Dim urlParameters As New Dictionary, _ 414 | requestBody As New Dictionary, _ 415 | responseBody As Dictionary 416 | 417 | urlParameters.Add "{session id}", MyOptions.Capabilities("sessionId") 418 | 419 | Set responseBody = SendCommand2Webdriver(W3C_NEW_WINDOW, urlParameters, requestBody) 420 | 421 | Set NewWindow = responseBody("value") 422 | End Function 423 | 424 | Public Function SwitchToFrame(ByVal frame As WebElement) As Boolean 425 | Dim urlParameters As New Dictionary, _ 426 | requestBody As New Dictionary, _ 427 | responseBody As Dictionary 428 | 429 | urlParameters.Add "{session id}", frame.driver.MyOptions.Capabilities("sessionId") 430 | requestBody.Add "id", frame.elementJson 431 | 432 | Set responseBody = SendCommand2Webdriver(W3C_SWITCH_TO_FRAME, urlParameters, requestBody) 433 | 434 | SwitchToFrame = True 435 | End Function 436 | 437 | Public Function SwitchToParentFrame() As Boolean 438 | Dim urlParameters As New Dictionary, _ 439 | requestBody As New Dictionary, _ 440 | responseBody As Dictionary 441 | 442 | urlParameters.Add "{session id}", MyOptions.Capabilities("sessionId") 443 | 444 | Set responseBody = SendCommand2Webdriver(W3C_SWITCH_TO_PARENT_FRAME, urlParameters, requestBody) 445 | 446 | SwitchToParentFrame = True 447 | End Function 448 | 449 | Public Function GetWindowRect() As Dictionary 450 | Dim urlParameters As New Dictionary, _ 451 | requestBody As New Dictionary, _ 452 | responseBody As Dictionary 453 | 454 | urlParameters.Add "{session id}", MyOptions.Capabilities("sessionId") 455 | Set responseBody = SendCommand2Webdriver(W3C_GET_WINDOW_RECT, urlParameters, requestBody) 456 | 457 | Set GetWindowRect = responseBody("value") 458 | End Function 459 | 460 | Public Function SetWindowRect(Optional ByVal x As Integer, _ 461 | Optional ByVal y As Integer, _ 462 | Optional ByVal width As Integer, _ 463 | Optional ByVal height As Integer) 464 | Dim urlParameters As New Dictionary, _ 465 | requestBody As New Dictionary, _ 466 | responseBody As Dictionary 467 | 468 | urlParameters.Add "{session id}", MyOptions.Capabilities("sessionId") 469 | 470 | If x <> 0 Then requestBody.Add "x", x 471 | If y <> 0 Then requestBody.Add "y", y 472 | If width <> 0 Then requestBody.Add "width", width 473 | If height <> 0 Then requestBody.Add "height", height 474 | 475 | Set responseBody = SendCommand2Webdriver(W3C_SET_WINDOW_RECT, urlParameters, requestBody) 476 | 477 | Set SetWindowRect = responseBody("value") 478 | End Function 479 | 480 | ' Return: JSON Object 481 | ' "x": the screenX attribute of the window object 482 | ' "y": the screenY attribute of the window object 483 | ' "width": the width of the outer dimensions of the top-level browsing context, including browser chrome etc... 484 | ' "height": the height of the outer dimensions of the top-level browsing context, including browser chrome etc... 485 | Public Function MaximizeWindow() As Dictionary 486 | Dim urlParameters As New Dictionary, _ 487 | requestBody As New Dictionary, _ 488 | responseBody As Dictionary 489 | 490 | urlParameters.Add "{session id}", MyOptions.Capabilities("sessionId") 491 | 492 | Set responseBody = SendCommand2Webdriver(W3C_MAXIMIZE_WINDOW, urlParameters, requestBody) 493 | 494 | Set MaximizeWindow = responseBody("value") 495 | End Function 496 | 497 | ' Return: JSON Object 498 | ' "x": the screenX attribute of the window object 499 | ' "y": the screenY attribute of the window object 500 | ' "width": the width of the outer dimensions of the top-level browsing context, including browser chrome etc... 501 | ' "height": the height of the outer dimensions of the top-level browsing context, including browser chrome etc... 502 | Public Function MinimizeWindow() 503 | Dim urlParameters As New Dictionary, _ 504 | requestBody As New Dictionary, _ 505 | responseBody As Dictionary 506 | 507 | urlParameters.Add "{session id}", MyOptions.Capabilities("sessionId") 508 | 509 | Set responseBody = SendCommand2Webdriver(W3C_MINIMIZE_WINDOW, urlParameters, requestBody) 510 | 511 | Set MinimizeWindow = responseBody("value") 512 | End Function 513 | 514 | Public Function FullscreenWindow() 515 | Dim urlParameters As New Dictionary, _ 516 | requestBody As New Dictionary, _ 517 | responseBody As Dictionary 518 | 519 | urlParameters.Add "{session id}", MyOptions.Capabilities("sessionId") 520 | 521 | Set responseBody = SendCommand2Webdriver(W3C_FULLSCREEN_WINDOW, urlParameters, requestBody) 522 | 523 | Set FullscreenWindow = responseBody("value") 524 | End Function 525 | 526 | Public Function FindElement(ByVal by_strategy As By, _ 527 | ByVal target As String) As WebElement 528 | Dim urlParameters As New Dictionary, _ 529 | requestBody As New Dictionary, _ 530 | responseBody As Dictionary 531 | 532 | urlParameters.Add "{session id}", MyOptions.Capabilities("sessionId") 533 | Set requestBody = SelectLocationStrategies(by_strategy, target) 534 | 535 | Set responseBody = SendCommand2Webdriver(W3C_FIND_ELEMENT, urlParameters, requestBody) 536 | 537 | Set FindElement = Convert2WebElement(responseBody("value")) 538 | End Function 539 | 540 | Public Function FindElements(ByVal by_strategy As By, _ 541 | ByVal target As String) As Variant 542 | Dim urlParameters As New Dictionary, _ 543 | requestBody As New Dictionary, _ 544 | responseBody As Dictionary 545 | 546 | Dim elements() As WebElement 547 | FindElements = elements 548 | 549 | urlParameters.Add "{session id}", MyOptions.Capabilities("sessionId") 550 | Set requestBody = SelectLocationStrategies(by_strategy, target) 551 | 552 | Set responseBody = SendCommand2Webdriver(W3C_FIND_ELEMENTS, urlParameters, requestBody) 553 | If responseBody("value").Count = 0 Then Exit Function 554 | 555 | Dim counter As Integer 556 | Dim elementJson As Dictionary 557 | ReDim elements(responseBody("value").Count - 1) 558 | 559 | counter = 0 560 | For Each elementJson In responseBody("value") 561 | ReDim Preserve elements(counter) 562 | Set elements(counter) = Convert2WebElement(elementJson) 563 | counter = counter + 1 564 | Next 565 | 566 | FindElements = elements 567 | End Function 568 | 569 | Public Function FindElementFromElement(ByVal element As WebElement, _ 570 | ByVal by_strategy As By, _ 571 | ByVal target As String) As WebElement 572 | Dim urlParameters As New Dictionary, _ 573 | requestBody As New Dictionary, _ 574 | responseBody As Dictionary 575 | 576 | urlParameters.Add "{session id}", MyOptions.Capabilities("sessionId") 577 | urlParameters.Add "{element id}", element.elementJson(ELEMENT_KEY) 578 | 579 | Set requestBody = SelectLocationStrategies(by_strategy, target) 580 | 581 | Set responseBody = SendCommand2Webdriver(W3C_FIND_ELEMENT_FROM_ELEMENT, urlParameters, requestBody) 582 | 583 | Set FindElementFromElement = Convert2WebElement(responseBody("value")) 584 | End Function 585 | 586 | Public Function FindElementsFromElement(ByVal element As WebElement, _ 587 | ByVal by_strategy As By, _ 588 | ByVal target As String) As Variant 589 | Dim urlParameters As New Dictionary, _ 590 | requestBody As New Dictionary, _ 591 | responseBody As Dictionary 592 | 593 | Dim elements() As WebElement 594 | FindElementsFromElement = elements 595 | 596 | urlParameters.Add "{session id}", MyOptions.Capabilities("sessionId") 597 | urlParameters.Add "{element id}", element.elementJson(ELEMENT_KEY) 598 | 599 | Set requestBody = SelectLocationStrategies(by_strategy, target) 600 | 601 | Set responseBody = SendCommand2Webdriver(W3C_FIND_ELEMENTS_FROM_ELEMENT, urlParameters, requestBody) 602 | If responseBody("value").Count = 0 Then Exit Function 603 | 604 | Dim counter As Integer 605 | Dim elementJson As Dictionary 606 | ReDim elements(responseBody("value").Count - 1) 607 | 608 | counter = 0 609 | For Each elementJson In responseBody("value") 610 | ReDim Preserve elements(counter) 611 | Set elements(counter) = Convert2WebElement(elementJson) 612 | counter = counter + 1 613 | Next 614 | 615 | FindElementsFromElement = elements 616 | End Function 617 | 618 | Public Function FindElementFromShadowRoot(ByVal shadowRoot As WebElement, _ 619 | ByVal by_strategy As By, _ 620 | ByVal target As String) As Variant 621 | Dim urlParameters As New Dictionary, _ 622 | requestBody As New Dictionary, _ 623 | responseBody As Dictionary 624 | 625 | urlParameters.Add "{session id}", MyOptions.Capabilities("sessionId") 626 | urlParameters.Add "{shadow id}", shadowRoot.elementJson(SHADOWROOT_KEY) 627 | 628 | Set requestBody = SelectLocationStrategies(by_strategy, target) 629 | Set responseBody = SendCommand2Webdriver(W3C_FIND_ELEMENT_FROM_SHADOW_ROOT, urlParameters, requestBody) 630 | 631 | Set FindElementFromShadowRoot = Convert2WebElement(responseBody("value")) 632 | End Function 633 | 634 | Public Function FindElementsFromShadowRoot(ByVal shadowRoot As WebElement, _ 635 | ByVal by_strategy As By, _ 636 | ByVal target As String) As Variant 637 | Dim urlParameters As New Dictionary, _ 638 | requestBody As New Dictionary, _ 639 | responseBody As Dictionary 640 | 641 | Dim elements() As WebElement 642 | FindElementsFromShadowRoot = elements 643 | 644 | urlParameters.Add "{session id}", MyOptions.Capabilities("sessionId") 645 | urlParameters.Add "{shadow id}", shadowRoot.elementJson(SHADOWROOT_KEY) 646 | 647 | Set requestBody = SelectLocationStrategies(by_strategy, target) 648 | 649 | Set responseBody = SendCommand2Webdriver(W3C_FIND_ELEMENTS_FROM_SHADOW_ROOT, urlParameters, requestBody) 650 | If responseBody("value").Count = 0 Then Exit Function 651 | 652 | Dim counter As Integer 653 | Dim elementJson As Dictionary 654 | ReDim elements(responseBody("value").Count - 1) 655 | 656 | counter = 0 657 | For Each elementJson In responseBody("value") 658 | ReDim Preserve elements(counter) 659 | Set elements(counter) = Convert2WebElement(elementJson) 660 | counter = counter + 1 661 | Next 662 | 663 | FindElementsFromShadowRoot = elements 664 | End Function 665 | 666 | Public Function GetElementShadowRoot(ByVal element As WebElement) As Variant 667 | Dim urlParameters As New Dictionary, _ 668 | requestBody As New Dictionary, _ 669 | responseBody As Dictionary 670 | 671 | urlParameters.Add "{session id}", MyOptions.Capabilities("sessionId") 672 | urlParameters.Add "{element id}", element.elementJson(ELEMENT_KEY) 673 | 674 | Set responseBody = SendCommand2Webdriver(W3C_GET_ELEMENT_SHADOW_ROOT, urlParameters, requestBody) 675 | 676 | Set GetElementShadowRoot = Convert2WebElement(responseBody("value")) 677 | End Function 678 | 679 | Public Function GetElementAttribute(ByVal element As WebElement, _ 680 | ByVal name As String) 681 | Dim urlParameters As New Dictionary, _ 682 | requestBody As New Dictionary, _ 683 | responseBody As Dictionary 684 | 685 | urlParameters.Add "{session id}", MyOptions.Capabilities("sessionId") 686 | urlParameters.Add "{element id}", element.elementJson(ELEMENT_KEY) 687 | urlParameters.Add "{name}", name 688 | 689 | Set responseBody = SendCommand2Webdriver(W3C_GET_ELEMENT_ATTRIBUTE, urlParameters, requestBody) 690 | 691 | GetElementAttribute = responseBody("value") 692 | End Function 693 | 694 | Public Function GetElementProperty(ByVal element As WebElement, _ 695 | ByVal name As String) 696 | Dim urlParameters As New Dictionary, _ 697 | requestBody As New Dictionary, _ 698 | responseBody As Dictionary 699 | 700 | urlParameters.Add "{session id}", MyOptions.Capabilities("sessionId") 701 | urlParameters.Add "{element id}", element.elementJson(ELEMENT_KEY) 702 | urlParameters.Add "{name}", name 703 | 704 | Set responseBody = SendCommand2Webdriver(W3C_GET_ELEMENT_PROPERTY, urlParameters, requestBody) 705 | 706 | GetElementProperty = responseBody("value") 707 | End Function 708 | 709 | Public Function GetElementText(ByVal element As WebElement) As String 710 | Dim urlParameters As New Dictionary, _ 711 | requestBody As New Dictionary, _ 712 | responseBody As Dictionary 713 | 714 | urlParameters.Add "{session id}", MyOptions.Capabilities("sessionId") 715 | urlParameters.Add "{element id}", element.elementJson(ELEMENT_KEY) 716 | 717 | Set responseBody = SendCommand2Webdriver(W3C_GET_ELEMENT_TEXT, urlParameters, requestBody) 718 | 719 | GetElementText = responseBody("value") 720 | End Function 721 | 722 | Public Sub ElementClick(ByVal element As WebElement) 723 | Dim urlParameters As New Dictionary, _ 724 | requestBody As New Dictionary 725 | 726 | urlParameters.Add "{session id}", MyOptions.Capabilities("sessionId") 727 | urlParameters.Add "{element id}", element.elementJson(ELEMENT_KEY) 728 | 729 | SendCommand2Webdriver W3C_ELEMENT_CLICK, urlParameters, requestBody 730 | End Sub 731 | 732 | Public Sub ElementClear(ByVal element As WebElement) 733 | Dim urlParameters As New Dictionary, _ 734 | requestBody As New Dictionary 735 | 736 | urlParameters.Add "{session id}", MyOptions.Capabilities("sessionId") 737 | urlParameters.Add "{element id}", element.elementJson(ELEMENT_KEY) 738 | 739 | SendCommand2Webdriver W3C_ELEMENT_CLEAR, urlParameters, requestBody 740 | End Sub 741 | 742 | Public Sub ElementSendKeys(text As String, ByVal element As WebElement) 743 | Dim urlParameters As New Dictionary, _ 744 | requestBody As New Dictionary 745 | 746 | urlParameters.Add "{session id}", MyOptions.Capabilities("sessionId") 747 | urlParameters.Add "{element id}", element.elementJson(ELEMENT_KEY) 748 | 749 | requestBody.Add "text", text 750 | 751 | SendCommand2Webdriver W3C_ELEMENT_SEND_KEYS, urlParameters, requestBody 752 | End Sub 753 | 754 | Public Function GetPageSource() 755 | Dim urlParameters As New Dictionary, _ 756 | requestBody As New Dictionary, _ 757 | responseBody As Dictionary 758 | 759 | urlParameters.Add "{session id}", MyOptions.Capabilities("sessionId") 760 | 761 | Set responseBody = SendCommand2Webdriver(W3C_GET_PAGE_SOURCE, urlParameters, requestBody) 762 | 763 | GetPageSource = responseBody("value") 764 | End Function 765 | 766 | 'Change any WebElement objects passed in to instead be their elementJson 767 | Private Function FormatWebElementArgs(ByRef args() As Variant) As Variant 768 | Dim i As Integer 769 | Dim argsFormat() As Variant 770 | 771 | For i = 0 To UBound(args) 772 | ReDim Preserve argsFormat(i) 773 | 774 | If TypeOf args(i) Is WebElement Then 775 | Set argsFormat(i) = args(i).elementJson 776 | Else 777 | argsFormat(i) = args(i) 778 | End If 779 | Next i 780 | 781 | FormatWebElementArgs = argsFormat 782 | End Function 783 | 784 | Public Function ExecuteScript(ByVal script As String, ParamArray args() As Variant) As Variant 785 | Dim urlParameters As New Dictionary, _ 786 | requestBody As New Dictionary, _ 787 | responseBody As Dictionary 788 | Dim argsFormat() As Variant 789 | 790 | urlParameters.Add "{session id}", MyOptions.Capabilities("sessionId") 791 | 792 | argsFormat = args ' Pass a ParamArray to another function directly will raise "Invalid ParamArray use" Exception 793 | argsFormat = FormatWebElementArgs(argsFormat) 794 | 795 | requestBody.Add "script", script 796 | requestBody.Add "args", argsFormat 797 | 798 | Set responseBody = SendCommand2Webdriver(W3C_EXECUTE_SCRIPT, urlParameters, requestBody) 799 | 800 | ExecuteScript = responseBody("value") 801 | End Function 802 | 803 | Public Function ExecuteAsyncScript(ByVal script As String, ParamArray args() As Variant) As Variant 804 | Dim urlParameters As New Dictionary, _ 805 | requestBody As New Dictionary, _ 806 | responseBody As Dictionary 807 | Dim argsFormat() As Variant 808 | 809 | urlParameters.Add "{session id}", MyOptions.Capabilities("sessionId") 810 | 811 | argsFormat = args 812 | argsFormat = FormatWebElementArgs(argsFormat) 813 | 814 | requestBody.Add "script", script 815 | requestBody.Add "args", argsFormat 816 | 817 | Set responseBody = SendCommand2Webdriver(W3C_EXECUTE_ASYNC_SCRIPT, urlParameters, requestBody) 818 | 819 | ExecuteAsyncScript = responseBody("value") 820 | End Function 821 | 822 | 823 | Public Sub DismissAlert() 824 | Dim urlParameters As New Dictionary, _ 825 | requestBody As New Dictionary 826 | 827 | urlParameters.Add "{session id}", MyOptions.Capabilities("sessionId") 828 | 829 | SendCommand2Webdriver W3C_DISMISS_ALERT, urlParameters, requestBody 830 | End Sub 831 | 832 | Public Sub AcceptAlert() 833 | Dim urlParameters As New Dictionary, _ 834 | requestBody As New Dictionary 835 | 836 | urlParameters.Add "{session id}", MyOptions.Capabilities("sessionId") 837 | 838 | SendCommand2Webdriver W3C_ACCEPT_ALERT, urlParameters, requestBody 839 | End Sub 840 | 841 | Public Function GetAlertText() 842 | Dim urlParameters As New Dictionary, _ 843 | requestBody As New Dictionary, _ 844 | responseBody As Dictionary 845 | 846 | urlParameters.Add "{session id}", MyOptions.Capabilities("sessionId") 847 | 848 | Set responseBody = SendCommand2Webdriver(W3C_GET_ALERT_TEXT, urlParameters, requestBody) 849 | 850 | GetAlertText = responseBody("value") 851 | End Function 852 | 853 | Public Sub SendAlertText(text As String) 854 | Dim urlParameters As New Dictionary, _ 855 | requestBody As New Dictionary 856 | 857 | urlParameters.Add "{session id}", MyOptions.Capabilities("sessionId") 858 | 859 | requestBody.Add "text", text 860 | 861 | SendCommand2Webdriver W3C_SEND_ALERT_TEXT, urlParameters, requestBody 862 | End Sub 863 | 864 | Public Sub TakeScreenshot(ByVal savePath As String) 865 | Dim urlParameters As New Dictionary, _ 866 | requestBody As New Dictionary, _ 867 | responseBody As Dictionary 868 | 869 | urlParameters.Add "{session id}", MyOptions.Capabilities("sessionId") 870 | 871 | Set responseBody = SendCommand2Webdriver(W3C_TAKE_SCREENSHOT, urlParameters, requestBody) 872 | 873 | DecodeBase64ToFile responseBody("value"), savePath 874 | End Sub 875 | 876 | Public Sub TakeElementScreenshot(ByVal element As WebElement, ByVal savePath As String) 877 | Dim urlParameters As New Dictionary, _ 878 | requestBody As New Dictionary, _ 879 | responseBody As Dictionary 880 | 881 | urlParameters.Add "{session id}", MyOptions.Capabilities("sessionId") 882 | urlParameters.Add "{element id}", element.elementJson(ELEMENT_KEY) 883 | 884 | requestBody.Add "scroll", True 885 | 886 | Set responseBody = SendCommand2Webdriver(W3C_TAKE_ELEMENT_SCREENSHOT, urlParameters, requestBody) 887 | 888 | DecodeBase64ToFile responseBody("value"), savePath 889 | End Sub 890 | 891 | '------------------------------------------------------------------ 892 | ' Communication Interface between 'Local end' & 'Remote end' 893 | '------------------------------------------------------------------ 894 | Private Function SendCommand2Webdriver(ByVal driverCommand As Variant, _ 895 | Optional urlParameters As Dictionary = Nothing, _ 896 | Optional requestBody As Dictionary = Nothing) As Dictionary 897 | Dim method As String: method = driverCommand(0) 898 | Dim path As String: path = driverCommand(1) 899 | 900 | ' Replace url parameters with user defined values. 901 | ' {session id}, {element id}, etc.. 902 | Dim paramKey As Variant 903 | For Each paramKey In urlParameters 904 | path = Replace(path, paramKey, urlParameters(paramKey)) 905 | Next 906 | 907 | Dim xmlHttp As Object 908 | Set xmlHttp = CreateObject("MSXML2.ServerXMLHTTP") 909 | 910 | xmlHttp.Open method, RemoteEndUrl + path 911 | xmlHttp.setRequestHeader "Content-Type", "application/json; charset=utf-8" 912 | xmlHttp.setRequestHeader "Cache-Control", "no-cache" 913 | xmlHttp.Send JsonConverter.ConvertToJson(requestBody) 914 | 915 | Do While (xmlHttp.readyState <> 4) 916 | DoEvents 917 | Loop 918 | 919 | If MyOptions.LogFile <> vbNullString Then 920 | Log4Driver MyOptions.LogFile, xmlHttp.responseText 921 | End If 922 | 923 | If xmlHttp.Status = 200 Then 924 | Set SendCommand2Webdriver = JsonConverter.ParseJson(xmlHttp.responseText) 925 | Else 926 | Err.Raise vbObjectError + 513, "WebDriver", xmlHttp.responseText 927 | End If 928 | 929 | Set xmlHttp = Nothing 930 | End Function 931 | 932 | '------------------------------------------------------------------ 933 | ' Other Functions 934 | '------------------------------------------------------------------ 935 | ' Locator strategies 936 | ' Ref: 937 | ' https://github.com/SeleniumHQ/selenium/blob/13d8f8be751001d44df8e5f1797518f4fb4dec6b/java/client/src/org/openqa/selenium/remote/http/W3CHttpCommandCodec.java#L187 938 | Private Function SelectLocationStrategies(ByVal Strategy As By, ByVal value As String) As Dictionary 939 | Dim searchJson As New Dictionary 940 | 941 | Select Case True 942 | Case Strategy = By.ClassName 943 | searchJson.Add "using", "css selector" 944 | searchJson.Add "value", "." & value 945 | Case Strategy = By.ID 946 | searchJson.Add "using", "css selector" 947 | searchJson.Add "value", "[id=""" + value + """]" 948 | Case Strategy = By.name 949 | searchJson.Add "using", "css selector" 950 | searchJson.Add "value", "[name='" & value & "']" 951 | Case Strategy = By.LinkText 952 | searchJson.Add "using", "link text" 953 | Case Strategy = By.PartialLinkText 954 | searchJson.Add "using", "partial link text" 955 | Case Strategy = By.TagName, Strategy = By.CSS 956 | searchJson.Add "using", "css selector" 957 | searchJson.Add "value", value 958 | Case Strategy = By.XPath 959 | searchJson.Add "using", "xpath" 960 | searchJson.Add "value", value 961 | End Select 962 | 963 | Set SelectLocationStrategies = searchJson 964 | End Function 965 | 966 | Private Function Convert2WebElement(ByVal elementJson As Dictionary) As WebElement 967 | Dim element As New WebElement 968 | element.driver = Me 969 | 970 | element.elementJson = elementJson 971 | 972 | Set Convert2WebElement = element 973 | End Function 974 | 975 | Private Sub DecodeBase64ToFile(ByVal Base64String As String, ByVal savePath As String) 976 | Dim base64Byte() As Byte 977 | 978 | With CreateObject("MSXML2.DOMDocument").createElement("b64") 979 | .DataType = "bin.base64" 980 | .text = Base64String 981 | base64Byte = .nodeTypedValue 982 | End With 983 | 984 | Dim FileNumber As Long: FileNumber = FreeFile 985 | 986 | Open savePath For Binary Access Write As #FileNumber 987 | Put #FileNumber, 1, base64Byte 988 | Close #FileNumber 989 | End Sub 990 | 991 | Private Sub Log4Driver(ByVal LogFile As String, ByVal messsage As String) 992 | Dim INT_FILE As Integer 993 | INT_FILE = FreeFile() 994 | 995 | Open LogFile For Append As #INT_FILE 996 | Print #INT_FILE, format(DateTime.Now, "yyyy-MM-dd hh:mm:ss") + messsage 997 | 998 | Close #INT_FILE 999 | End Sub 1000 | -------------------------------------------------------------------------------- /src/WebDriverOptions.cls: -------------------------------------------------------------------------------- 1 | VERSION 1.0 CLASS 2 | BEGIN 3 | MultiUse = -1 'True 4 | END 5 | Attribute VB_Name = "WebDriverOptions" 6 | Attribute VB_GlobalNameSpace = False 7 | Attribute VB_Creatable = False 8 | Attribute VB_PredeclaredId = False 9 | Attribute VB_Exposed = False 10 | '------------------------------------------------------------------ 11 | ' SeleniumWrapperVBA 12 | ' 13 | ' Auther: er-ri (https://github.com/er-ri/selenium-wrapper-vba) 14 | ' Date: 2022/03 15 | ' License: MIT (https://opensource.org/licenses/MIT) 16 | '------------------------------------------------------------------ 17 | ' 18 | ' ================================================================= 19 | ' #Class WebDriverOptions 20 | ' ================================================================= 21 | Option Explicit 22 | 23 | Public Enum BrowserTypesList 24 | InternetExplorer = 1 25 | Edge = 2 26 | Chrome = 3 27 | Firefox = 4 28 | End Enum 29 | 30 | Private m_BrowserType As BrowserTypesList 31 | 32 | ' Options for Internet Explorer 33 | Private m_AttachToEdgeChrome As Boolean 34 | Private m_EdgeExecutablePath As String 35 | Private m_IntroduceFlakinessByIgnoringSecurityDomains As Boolean 36 | Private m_IgnoreZoomSetting As Boolean 37 | 38 | ' Options for Chrome 39 | Private m_ChromeArguments As Collection 40 | 41 | ' Options for Firefox 42 | Private m_FirefoxArguments As Collection 43 | 44 | ' Options for Edge 45 | Private m_EdgeArguments As Collection 46 | 47 | ' Driver Preference 48 | Private m_LogFile As String 49 | Private m_WebDriverWinStyle As VbAppWinStyle 50 | Private m_Capabilities As Dictionary 51 | 52 | '------------------------------------------------------------------ 53 | ' Encapsulation 54 | '------------------------------------------------------------------ 55 | Public Property Get BrowserType() As BrowserTypesList 56 | BrowserType = m_BrowserType 57 | End Property 58 | 59 | Public Property Let BrowserType(ByVal new_BrowserType As BrowserTypesList) 60 | m_BrowserType = new_BrowserType 61 | End Property 62 | 63 | Public Property Get AttachToEdgeChrome() As Boolean 64 | AttachToEdgeChrome = m_AttachToEdgeChrome 65 | End Property 66 | 67 | Public Property Let AttachToEdgeChrome(ByVal new_AttachToEdgeChrome As Boolean) 68 | m_AttachToEdgeChrome = new_AttachToEdgeChrome 69 | End Property 70 | 71 | Public Property Get EdgeExecutablePath() As String 72 | EdgeExecutablePath = m_EdgeExecutablePath 73 | End Property 74 | 75 | Public Property Let EdgeExecutablePath(ByVal new_EdgeExecutablePath As String) 76 | m_EdgeExecutablePath = new_EdgeExecutablePath 77 | End Property 78 | 79 | Public Property Get IntroduceFlakinessByIgnoringSecurityDomains() As Boolean 80 | IntroduceFlakinessByIgnoringSecurityDomains = m_IntroduceFlakinessByIgnoringSecurityDomains 81 | End Property 82 | 83 | Public Property Let IntroduceFlakinessByIgnoringSecurityDomains(ByVal new_IntroduceFlakinessByIgnoringSecurityDomains As Boolean) 84 | m_IntroduceFlakinessByIgnoringSecurityDomains = new_IntroduceFlakinessByIgnoringSecurityDomains 85 | End Property 86 | 87 | Public Property Get IgnoreZoomSetting() As Boolean 88 | IgnoreZoomSetting = m_IgnoreZoomSetting 89 | End Property 90 | 91 | Public Property Let IgnoreZoomSetting(ByVal new_IgnoreZoomSetting As Boolean) 92 | m_IgnoreZoomSetting = new_IgnoreZoomSetting 93 | End Property 94 | 95 | Public Property Get ChromeArguments() As Collection 96 | Set ChromeArguments = m_ChromeArguments 97 | End Property 98 | 99 | Public Property Let ChromeArguments(ByVal new_ChromeArguments As Collection) 100 | Set m_ChromeArguments = new_ChromeArguments 101 | End Property 102 | 103 | Public Property Get FirefoxArguments() As Collection 104 | Set FirefoxArguments = m_FirefoxArguments 105 | End Property 106 | 107 | Public Property Let FirefoxArguments(ByVal new_FirefoxArguments As Collection) 108 | Set m_FirefoxArguments = new_FirefoxArguments 109 | End Property 110 | 111 | Public Property Get EdgeArguments() As Collection 112 | Set EdgeArguments = m_EdgeArguments 113 | End Property 114 | 115 | Public Property Let EdgeArguments(ByVal new_EdgeArguments As Collection) 116 | Set m_EdgeArguments = new_EdgeArguments 117 | End Property 118 | 119 | Public Property Get LogFile() As String 120 | LogFile = m_LogFile 121 | End Property 122 | 123 | Public Property Let LogFile(ByVal new_LogFile As String) 124 | m_LogFile = new_LogFile 125 | End Property 126 | 127 | Public Property Get Capabilities() As Dictionary 128 | Set Capabilities = m_Capabilities 129 | End Property 130 | 131 | Public Property Let Capabilities(ByVal new_Capabilities As Dictionary) 132 | Set m_Capabilities = new_Capabilities 133 | End Property 134 | '------------------------------------------------------------------ 135 | ' Constructor 136 | '------------------------------------------------------------------ 137 | Private Sub Class_Initialize() 138 | Set m_ChromeArguments = New Collection 139 | Set m_FirefoxArguments = New Collection 140 | Set m_EdgeArguments = New Collection 141 | 142 | m_LogFile = vbNullString 143 | End Sub 144 | 145 | '------------------------------------------------------------------ 146 | ' Class Method 147 | '------------------------------------------------------------------ 148 | Public Function ConstructCapabilities() As Dictionary 149 | Dim capabilitiesRoot As New Dictionary 150 | Dim alwaysMatchRoot As New Dictionary 151 | 152 | Select Case True 153 | Case Me.BrowserType = InternetExplorer 154 | Dim ieOptionsRoot As New Dictionary 155 | ieOptionsRoot.Add "ie.edgechromium", Me.AttachToEdgeChrome 156 | ieOptionsRoot.Add "ie.edgepath", Me.EdgeExecutablePath 157 | ieOptionsRoot.Add "ignoreProtectedModeSettings", Me.IntroduceFlakinessByIgnoringSecurityDomains 158 | ieOptionsRoot.Add "ignoreZoomSetting", Me.IgnoreZoomSetting 159 | 160 | alwaysMatchRoot.Add "browserName", "internet explorer" 161 | alwaysMatchRoot.Add "se:ieOptions", ieOptionsRoot 162 | capabilitiesRoot.Add "alwaysMatch", alwaysMatchRoot 163 | Case Me.BrowserType = Edge 164 | Dim edgeOptionsRoot As New Dictionary 165 | edgeOptionsRoot.Add "args", EdgeArguments 166 | 167 | alwaysMatchRoot.Add "browserName", "MicrosoftEdge" 168 | alwaysMatchRoot.Add "ms:edgeOptions", edgeOptionsRoot 169 | capabilitiesRoot.Add "alwaysMatch", alwaysMatchRoot 170 | Case Me.BrowserType = Chrome 171 | Dim chromeOptionsRoot As New Dictionary 172 | chromeOptionsRoot.Add "args", ChromeArguments 173 | 174 | alwaysMatchRoot.Add "browserName", "chrome" 175 | alwaysMatchRoot.Add "goog:chromeOptions", chromeOptionsRoot 176 | capabilitiesRoot.Add "alwaysMatch", alwaysMatchRoot 177 | Case Me.BrowserType = Firefox 178 | Dim firefoxOptionsRoot As New Dictionary 179 | firefoxOptionsRoot.Add "args", FirefoxArguments 180 | 181 | alwaysMatchRoot.Add "browserName", "firefox" 182 | alwaysMatchRoot.Add "moz:firefoxOptions", firefoxOptionsRoot 183 | capabilitiesRoot.Add "alwaysMatch", alwaysMatchRoot 184 | End Select 185 | 186 | Set ConstructCapabilities = capabilitiesRoot 187 | End Function 188 | -------------------------------------------------------------------------------- /src/WebElement.cls: -------------------------------------------------------------------------------- 1 | VERSION 1.0 CLASS 2 | BEGIN 3 | MultiUse = -1 'True 4 | END 5 | Attribute VB_Name = "WebElement" 6 | Attribute VB_GlobalNameSpace = False 7 | Attribute VB_Creatable = False 8 | Attribute VB_PredeclaredId = False 9 | Attribute VB_Exposed = False 10 | '------------------------------------------------------------------ 11 | ' SeleniumWrapperVBA 12 | ' 13 | ' Auther: er-ri (https://github.com/er-ri/selenium-wrapper-vba) 14 | ' Date: 2022/03 15 | ' License: MIT (https://opensource.org/licenses/MIT) 16 | '------------------------------------------------------------------ 17 | ' 18 | ' ================================================================= 19 | ' #Class WebElement 20 | ' ================================================================= 21 | Option Explicit 22 | 23 | Private m_Driver As WebDriver 24 | Private m_elementJson As Dictionary 25 | 26 | '------------------------------------------------------------------ 27 | ' Encapsulation 28 | '------------------------------------------------------------------ 29 | Public Property Get driver() As WebDriver 30 | Set driver = m_Driver 31 | End Property 32 | 33 | Public Property Let driver(ByVal new_Driver As WebDriver) 34 | Set m_Driver = new_Driver 35 | End Property 36 | 37 | Public Property Get elementJson() As Dictionary 38 | Set elementJson = m_elementJson 39 | End Property 40 | 41 | Public Property Let elementJson(ByVal new_elementJson As Dictionary) 42 | Set m_elementJson = new_elementJson 43 | End Property 44 | 45 | '------------------------------------------------------------------ 46 | ' Element Command 47 | '------------------------------------------------------------------ 48 | Public Function FindElement(ByVal by_strategy As By, _ 49 | ByVal target As String) As WebElement 50 | Set FindElement = driver.FindElementFromElement(Me, by_strategy, target) 51 | End Function 52 | 53 | Public Function FindElements(ByVal element As WebElement, _ 54 | ByVal by_strategy As By, _ 55 | ByVal target As String) As Variant 56 | FindElements = driver.FindElementsFromElement(Me, by_strategy, target) 57 | End Function 58 | 59 | Public Function GetShadowRoot() 60 | Set GetShadowRoot = driver.GetElementShadowRoot(Me) 61 | End Function 62 | 63 | Public Sub SendKeys(text As String) 64 | driver.ElementSendKeys text, Me 65 | End Sub 66 | 67 | Public Sub Clear() 68 | driver.ElementClear Me 69 | End Sub 70 | 71 | Public Sub Click() 72 | driver.ElementClick Me 73 | End Sub 74 | 75 | Public Function GetAttribute(ByVal name As String) 76 | GetAttribute = driver.GetElementAttribute(Me, name) 77 | End Function 78 | 79 | Public Function GetProperty(ByVal name As String) 80 | GetProperty = driver.GetElementProperty(Me, name) 81 | End Function 82 | 83 | Public Function GetText() 84 | GetText = driver.GetElementText(Me) 85 | End Function 86 | 87 | Public Sub TakeScreenshot(ByVal savePath As String) 88 | driver.TakeElementScreenshot Me, savePath 89 | End Sub 90 | 91 | --------------------------------------------------------------------------------