Need to scroll down in Dynamically Generated Webpage using VBA / Selenium - vba

I am using Selenium to screen scrape a dynamically generated webpage. The trick is that the webpage does not appear to be generated until I manually scroll down the page. If I search for objects that are below the current screen when I open the page, I get an error saying the object (byClass or by XPath) don't exist. If I inspect the count of a occurrence of a multi-repeating class, it returns only a fraction of the total number. However, if I manually scroll down the page and the new content displays, then I can find the desired object using either byClass or byXPath, and the count grows, down to what has (ever) been displayed.
I have read through all the other posts with similar questions, but their solutions are not working in my case. The following has no impact on my page:
ch.ExecuteScript ("window.scrollTo(0, document.body.scrollHeight);")
Once I have (ever) scrolled down and back up the page manually, I can scroll to an object using
ch.FindElementByClass("css-w166kv-LegendLabel-LegendClickable").ScrollIntoView
But again, when I open the page, the instance of the class hasn't been generated yet, so at that time, I get the error that the class is not found.
I tried the following code (adapted from Python that I found posted in one of the other articles) that basically issues the same ExecuteScript as above, but tries to run it over and over, until everything is displayed. However, the height never changes, so it doesn't scroll.
Sub ScrollDownPage(ch As Selenium.ChromeDriver)
Dim last_height As Long
' # Get scroll height.
last_height = ch.ExecuteScript("return document.body.scrollHeight")
Do While True
' # Scroll down to the bottom.
ch.ExecuteScript ("window.scrollTo(0, document.body.scrollHeight);")
' # Wait to load the page.
Application.Wait (Now + TimeValue("0:00:02"))
' # Calculate new scroll height and compare with last scroll height.
new_height = ch.ExecuteScript("return document.body.scrollHeight")
If new_height = last_height Then
Exit Do
End If
last_height = new_height
Loop
End Sub
My current work-around is that my screenscraping code will work if I make my zoom on my browser = 50% so that everything displays on screen.
ch.ExecuteScript ("document.body.style.zoom = '0.5'")
Any suggestions other than my Zoom Out hack?

I couldn't get the scroll to work properly, but I did find a workaround. My problem was the objects were not being generated until they were displayed. Instead of scrolling to have them displayed, I simply changed the Zoom level with Selenium to have the entire page displayed. It makes it so small it is pretty much unreadable visually, but it is readable programmatically with Selenium.
szoom = "0.25"
ch.ExecuteScript ("document.body.style.zoom = '" & szoom & "'")

Related

Click events execution in VBA data extraction

I am using below mentioned code for button click events,My code for main page is working properly but in below mentioned url one additional page is loading after first page loading so click events are not working properly for add to cart,view cart and cart deletion event,I can't find out a feasible solution for resolution of trialing issue.
My urls is https://www.amazon.com/dp/B097QGD9DL/ref=olp_aod_redir_impl1/130-4397383-6777352?_encoding=UTF8&aod=1 and code is given below.
'----add to cart button click event
Set btn = html9.getElementsByClassName("a-button-input")
btn.Click
Sleep 2000
'Ie.Refresh
Set html5 = Ie.Document
'---view cart button click event
Set btn1 = html5.getElementById("a-button-input")
btn1.Click
Sleep 3000
Set html6 = Ie.Document
html7 = html6.body.innerHTML
clr = html.getElementsByClassName("sc-product-variation")(0).innerText
Size = html.getElementsByClassName("sc-product-variation")(0).innerText
'---------click on delete button to clear add to cart
Set btn3 = html6.getElementsByClassName("a-color-link")
btn3.Click
With my experience with Selenium, may i suggest you to try narrowing down your search area for button.
On second load page, there are almost 23 elements with id "a-button-input". Its a very generic id for button.
My suggestion is to first search for unique container closest to our relevant button. And then try to search for this button in the container only.
Look forward to your response. Your project is very interesting.

Wait for 1 second before starting code again - VB.NET

I desperately need help with a game I am making. For a bit of context, i am making a memory game and i have the following piece of code that is being troublesome. I have a bunch of labels on the form, 16 to be exact, with 1 randomly generated symbol placed in each. Each symbol appears in the labels twice.
------------------------------Continued----------------------------------------
'MsgBox("hello") 'used to check if the second inccorect press shows up - it does show but instantly changes colour
'''''''''''''''''NEED SOME CODE THAT PAUSES IT HERE'''''''''''''''
labels(0).ForeColor = Color.DarkRed
sender.ForeColor = Color.DarkRed
End If
flips = 1
End If
End If
tmrmemory.Enabled = True ' starts the timer after the user clicks the first label
End Sub
What's supposed to happen is that when the labels clicked don't match, it should show both the clicked labels for a short period before changing them both back to "DarkRed" which is the colour of the form's background.
I have tried using a timer but then i can't use sender.forecolor=color.darkred because it is not declared globally.
I have also tried using the command Threading.Thread.Sleep(500) but it still doesn't show the second incorrect click. I know that the code i have used works because when i use the message box, i can see both symbols and when the two clicks are correct, it stays.
Threading.Thread.Sleep(500) will actually pause your code for half a second. However during this time it won't do anything, not even refresh your controls. To get the effect you want, you need to call the YourControl.Refresh method before calling Threading.Thread.Sleep to force the control to redraw immediately.
On a side note, I would advise you not to call Threading.Thread.Sleep on UI thread. It will give a feeling of program hang. Instead do your work on a separate thread. You can either do all the work yourself right from creating a separate thread to destroying it, or use the BackgroundWorker control which has all the functionality built in.
Here is the link to an article I wrote a long time ago regarding BackgroundWorker that might be useful for you:
http://www.vbforums.com/showthread.php?680130-Correct-way-to-use-the-BackgroundWorker
Declare a variable outside the sub that stores what label should be flipped when the timer ends.
Label click sets
storedLabel = sender
Timer tick sets storedLabel.ForeColor = Color.DarkRed

ImageTools only creates PNG successfully under certain conditions

I'm trying to generate images using ImageTools, and my code works and successfully creates images...but only if I have user input before trying to create the images!
If I try to generate the images in the New sub, for example, the images are created, but they only contain the textbox control from my canvas and not the image (my control consists of text + image). So the image is being created...but it's only rendering partial content.
If I put a button on my page and generate my images from the button click even handler the images are generated correctly.
So what am I doing wrong here? And how can I get my images to generate without user input (i.e. when the app launches).
I get the exact same results using WriteableBitmap in place of ImageTools, FWIW.
I create stackpanels with a canvas and my text/image elements, then use the standard code to render the images to files in isolated storage. Since it all works perfectly after user input I don't know which parts of the code to provide...I'm basically using unmodified sample code.
Code parts (this is all in my MainPage.XAML.VB):
Public Sub New()
InitializeComponent()
' some code commented out while debugging - not relevant here
SetupHubTiles() ' this is the method that sets up the images (see below)
End Sub
The SetupHubTiles method makes several calls to the following method:
Public Sub CreateHubTile(background As StackPanel, tileImage As String, tiletoupdate As HubTile)
Dim isoStoreTileImage = String.Format("isostore:{0}", tileImage)
'Create a bitmapImage to IsolatedStorage.
Using store As IsolatedStorageFile = IsolatedStorageFile.GetUserStoreForApplication()
'Tile image's Height * Width are 173 * 173.
Dim bitmap = New WriteableBitmap(173, 173)
'Render a bitmap from StackPanel.
bitmap.Render(background, New TranslateTransform())
Dim stream = store.CreateFile(tileImage)
bitmap.Invalidate()
bitmap.SaveJpeg(stream, 173, 173, 0, 100)
stream.Close()
End Using
SetHubTileImage(tileImage, tiletoupdate) ' this is what sets a control on the MainPage to display the generated image
End Sub
And finally the button click handler (which I just implemented because the code I'm using works fine in another app, but that app always gets user input before creating images, so I figured it was the only difference between the two apps)
Private Sub StartButton_Click(sender As System.Object, e As System.Windows.RoutedEventArgs) Handles StartButton.Click
SetupHubTiles()
End Sub
As you can see the code being executed is identical, but I get a different result when I run it directly in my contructor compared to running it from a button click handler.
The goal is for these images to be generated at runtime (without any interaction from the user) to be used in the UI.
I've tried a few different methods of doing this, but I always get the same results - an image is generated with just text when there should be text + image. I am using the same method in other apps with the only difference being those other apps are not creating the images as soon as the app launches, which may be the problem.
It also doesn't seem to make a difference if I change the location/type of controls that I am using to build my images.
Based on your comment, try to invoke your method either later in the loading sequence or use:
Dispatcher.BeginInvoke(() => { GenerateImages(); });
This will queue the function up to be run on the next UI thread tick which should be after any pending layout work which is queued up.
The fact that you are calling this function right at the end of the constructor has no consequence (other than the fact that the variables representing different objects have been initialized). It's all happening in the same UI thread tick, before Layout is kicked off so there is nothing in the StackPanel for you to capture.
To fix this, add your code to the Loaded event of the Page and still wrap it in a Dispatcher.BeginInvoke call so that it is guaranteed to happen after all of your Controls have rendered (at least their first pass, not withstanding any content that is loaded after the startup sequence).

Scrollable image in userform

I have a word doc with a bunch of ActiveX Control buttons or whatever on it, and each time a button is clicked, a corresponding image needs to be displayed in a popup box.
I have a userform called ImageForm, and this is what I'm doing right now:
Sub Button_Clicked()
ImageForm.Picture = LoadPicture("appropriate_image_path")
ImageForm.Show
End Sub
Each of these images has a width of 8.5 inches, but their heights can vary anywhere from like 3 to 20 inches (they're snippets of a pdf). So I've set the width of the userform to a little more than 8.5 inches, and that looks fine. But I need to be able to scroll vertically through the image in the userform, since some of the images could be taller than a user's monitor.
I'm completely stuck on this. What I've tried so far is adding a frame to the form, then adding an image control inside the form, and setting the "ScrollBars" property of the frame to vertical. Then instead of using "ImageForm.Picture = ..." I use "ImageForm.ImageControl.Picture = ..." But it doesn't work.
Any insight here would be greatly appreciated. Hopefully this question is clear enough, I've only been using VBA for a month or so now. (I miss Java so, so much)
Thanks!
Here is a neat little trick based on one of my posts
The idea is to ensure that the image control is in frame control and the image control doesn't have a border. Also the image control's PictureSizeMode is set to fmPictureSizeModeClip so that we can scroll the image
SNAPSHOT (DESIGN TIME)
SNAPSHOT (RUN TIME)
CODE
Private Sub UserForm_Initialize()
With Frame1
'~~> This will create a vertical scrollbar
.ScrollBars = fmScrollBarsVertical
'~~> Change the values of 2 as Per your requirements
.ScrollHeight = .InsideHeight * 2
.ScrollWidth = .InsideWidth * 9
End With
With Image1
.Picture = LoadPicture("C:\Users\Public\Pictures\Sample Pictures\Desert.jpg")
.BorderStyle = fmBorderStyleNone
.PictureSizeMode = fmPictureSizeModeClip
End With
End Sub

The control or subform control is too large for this location on resize

I have a simple form in Access 2003. The form has a List control and a button. I wanted to make the list to resize with the form (only vertically) while the button remains at the bottom right of the List. I'm using the following code on the form's resize event.
list.Height = Me.InsideHeight - list.Top - 200
commandButton.Top = list.Height + list.Top + 50
This works fine as I resize the form, until the form height gets to a certain height. Then I get this error;
Run-time error '2100':
The control or subform control is too large for this location
This error is occurring at the line where I'm assigning the commandButton.Top. If I remove this line then the list height changes fine. I don't have any subforms in the form.
Does anyone know why this is happening?
Thanks
I think it is because you need to resize the detail section of the form first.
Try changing the code to
Me.Section(0).Height = Me.InsideHeight
list.Height = Me.InsideHeight - list.Top - 200
commandButton.Top = list.Height + list.Top + 50
Came by here (as many have) with the same problem and then realised my issue. Be mindful when resizing a control to a larger size with regard to the order of setting properties.
I would recommend setting the TOP and LEFT positions before HEIGHT and WIDTH.
Although my final sized control should have fitted OK once resized, I had originally tried setting the WIDTH first which attempted to enlarge the control exceeding the width of the form. My application threw the 2100 error at that point. I really hope this helps someone! Also, don't forget to set dimensions in TWIPS which is set as INCHESS x 1440 (or CM x 566.9291), ie: .Width = 10 * 566.9291 to set a control width to 10cm.
I get this same error if I set the width to greater the 31680.
With a little more research, I notice MS Access only allows a form width to be 22" wide. 22" = 31680 TWIPS.
So my workaround solutions it to add a check:
If newWidth > 31680 Then newWidth = 31680