longvh211 / Chromium-Automation-with-CDP-for-VBA

A method to directly automate Chromium-based web browsers, such as Chrome, Edge, and Firefox, using VBA for Office applications by following the Chrome DevTools Protocol framework.
MIT License
44 stars 6 forks source link

Is Sendkeys available? #2

Closed chrisdaniels closed 2 years ago

chrisdaniels commented 2 years ago

Hi, looking to replace Selenium with this code, but I need to be able to simulate key presses in order to get around some login pages.

Is Sendkeys available or possible with this solution?

longvh211 commented 2 years ago

Hi Chris, for the CDP framework, you can use the JsEval function to parse javascript code directly to the browser instance to interact with the form elements of the webpage. It works similar to SendKeys of Selenium but is more comprehensive and powerful. You can have a look with the examples in my demo file where JsEval is being used to input into form fields of a target url.

chrisdaniels commented 2 years ago

Thanks, I have got that working, but the logon page I am attempting to automate must have checking for real key presses to avoid automation. I have got the login working using selenium ide which will use simulated key presses as far as I know.

longvh211 commented 2 years ago

Chris have you tried all the JS methods for form-inputting? It is unlikely that an automation-detection mechanism is in place. The scenario I often see is that certain form field just requires you to use .setattribute instead of .value. Maybe give a try with the methods mentioned in this post and see if it works? https://stackoverflow.com/questions/7609130/set-the-value-of-an-input-field

If you still need to resort to SendKeys then SendKeys is technically inherent in VBA which you should be able to make use of already: https://docs.microsoft.com/en-us/office/vba/api/excel.application.sendkeys

chrisdaniels commented 2 years ago

Came back to this problem yesterday and managed to resolve it using VBA sendkeys as you suggested.

.setattribute and .value do the same thing on this particular logon page - the field gets populated on screen, but clicking the login button just reloads the page instead of logging in. If I pause the code just before the button click, and manually click something on the page, then login works, so I can only assume it must have some kind of crude bot detection.

It does just seem to be the logon page with this check too, so once past it, it doesn't seem to be a problem anymore.

I think Selenium's version of sendkeys is nicer as you can specify which element you want to pass the sendkeys to. Using VBA's version means that the focus needs to be in the right place at the right time which is sometimes hit and miss.

Also VBA now seems to have developed an issue where it interferes with the keyboards NumLock status which is just plain annoying if you use it.

EDIT: Method to stop Numlock turning off Dim WshShell As Object: Set WshShell = CreateObject("WScript.Shell") WshShell.SendKeys "Test", True

longvh211 commented 2 years ago

Ah so it is the click that is the issue. Sounds like an interesting case which if I had a chance I probably would give a try with firing mousedown - mouseup - onclick events on the button to see if it works.

In any case, the VBA Sendkey does have that strange drawback. This is why if I normally have to interact with an UI element and HTML COM is not able to do the job for some reason, I will resort to using UI Automation (UIA) framework in VBA to interact with that HTML element instead of Sendkeys.

Do you happen to have a snippet of the button element and its surrounds by any chance?

chrisdaniels commented 2 years ago

Yes, I thought about trying some mouse events, but didn't take it any further.

Code snippet below with both the Email inputbox and the Submit button.

<div data-v-6aa2759d="" data-v-c7aa2870="" id="callbacksPanel" class="card-body" style="">
<!---->
<div data-v-6aa2759d="" data-v-c7aa2870="" id="body-append-el">
<form data-v-6aa2759d="" data-v-c7aa2870="" id="wrapper">
<!---->
<div data-v-41d24c65="" data-v-6aa2759d="" id="callback_1" class="fr-field callback-component" callback="[object Object]" index="1" step="[object Object]">
<!---->
<span data-v-41d24c65="">
<!---->
<div data-v-4ecd1842="" data-v-eedb6682="" data-v-41d24c65="" class="w-100 floating-label-input" failedpolicies="" field="[object Object]" callback="[object Object]" index="1" step="[object Object]">
<div data-v-4ecd1842="" class="form-label-group mb-0">
<div data-v-4ecd1842="" class="form-label-group-input">

<input data-v-eedb6682="" data-v-4ecd1842="" id="floatingLabelInput17" placeholder="Email Address" data-vv-as="Email Address" name="callback_1" type="text" class="form-control">

<label data-v-4ecd1842="" for="floatingLabelInput17" class="no-pointer-events overflow-hidden text-nowrap"> Email Address </label>
</div>
<!---->
</div>
<div data-v-4ecd1842="" class="fr-validation-requirements text-left error-messages">
</div>
<!---->
</div>
</span>
<!---->
</div>

<button data-v-6aa2759d="" data-v-c7aa2870="" type="submit" class="btn mt-3 login-btn-padding btn-primary"> Next </button>

<!---->
</form>
</div>
</div>
chrisdaniels commented 2 years ago

Just to give a quick update on this conundrum, I managed to get sendkeys working via CDP and the InvokeMethod which gives much better control over where the focus is on the webpage before sending the keys.

You need to .focus() the input box first, and then send over the keys one at a time.

in the below example, I am selecting an input box called "floatingLabelInput17", and then pushing a string from sheet1.range("C15") one character at a time.

chrome.jsEval ("document.getElementById(""floatingLabelInput17"").focus()")

Dim params As New Dictionary
params("type") = "keyDown" 

Dim Letter As Variant
For Letter = 1 To Len(Sheet1.Range("C15").Value)
    params("text") = Mid$(Sheet1.Range("C15").Value, Letter, 1)
    chrome.invokeMethod "Input.dispatchKeyEvent", params
Next Letter

Mouse events are similar - I didnt end up using them, so the below may not work

    Dim params As New Dictionary
    params("type") = "mousePressed"
    params("button") = "left"
    params("x") = 1 
    params("y") = 1 
    params("modifiers") = 0
    params("clickCount") = 1

chrome.invokeMethod "Input.dispatchMouseEvent", params
longvh211 commented 2 years ago

Thanks Chris for the updates. It will be useful to have them down in record for future references.