Saving full page content using Selenium - selenium

I was wondering what's the best way to save all the files that are retrieved when Selenium visits a site. In other words, when Selenium visits http://www.google.com I want to save the HTML, JavaScript (including scripts referenced in src tags), images, and potentially content contained in iframes. How can this be done?
I know getHTMLSource() will return the HTML content in the body of the main frame, but how can this be extended to download the complete set of files necessary to render that page again.
Thanks in advance!

Selenium isn't the designed for this, you could either:
Use getHtmlSource and parse the resulting HTML for references to external files, which you can then download and store outside of Selenium.
Use something other than Selenium to download and store an offline version of a website - I'm sure there are plenty of tools that could do this if you do a search. For example WGet can perform a recursive download (http://en.wikipedia.org/wiki/Wget#Recursive_download)
Is there any reason you want to use Selenium? Is this part of your testing strategy or are you just wanting to find a tool that will create an offline copy of a page?

The only built in method Selenium has for downloading source content is
driver = webdriver.Chrome()
driver.get('www.someurl.com')
page_source = driver.page_source
But that doesn't download all the images, css, and js scripts like you would get if you used ctrl+s on a webpage. So you'll need to emulate the ctr+s keys after you navigate to a webpage like Algorithmatic has stated.
I made a gist to show how thats done. https://gist.github.com/GrilledChickenThighs/211c307edf8f828806c4bb4e4707b106
# Download entire webpage including all javascript, html, css of webpage. Replicates ctrl+s when on a webpage.
from selenium.webdriver.common.action_chains import ActionChains
from selenium.webdriver.common.keys import Keys
def save_current_page():
ActionChains(browser).send_keys(Keys.CONTROL, "s").perform()

A good tool for that is http://www.httrack.com/, Selenium doesn't provide any API for that. In case you need to save the full content of a page from your test case in selenium, perhaps you can execute httrack as a command line tool.
Thanks

If you really want to use Selenium then what you can do is emulate Ctrl+S for saving the page, but then it's more work/difficult (also OS dependent) to emulate pressing Enter or changing the location of where you want to save the webpage and its content.
I wanted to do the same thing with Selenium but realized that I could just use tools like wget, and I really didn't need to only use Selenium.. So I ended up using wget, it's really powerful and it does exactly what I need.
This is how you would do it using wget from a Python script:
import os
# Save HTML
directory = 'directory_to_save_webpage_content/'
url = 'http://www.google.com'
wget = "wget -p -k -P {} {}".format(directory, url)
os.system(wget)
The args passed are just to make it possible to view the page offline as if you're still online.
--page-requisites -p -- get all images needed to display page
--convert-links -k -- convert links to be relative
--directory-prefix -P -- specify prefix to save files to

I made this by downloading external sources (images) and replacing their src attribute.
Let's assume I want to save all images from <img> tags to the ../images path relative to the current page.
~/site
~/site/pages/
~/site/pages/page1.html
~/site/pages/page2.html
~/site/images/
~/site/images/img_for_page1.png
~/site/images/img_for_page2.png
I download the images with requests module.
# save_full_page.py
from selenium import webdriver
import requests
... # open page you want to save
with open("replace_img_srcs.js", 'r') as file:
replace_img_srcs_js = file.read()
save_dir = "/home/user/site"
save_to_file = "/home/user/site/pages/page1.html"
img_tags = driver.find_elements(By.TAG_NAME, "img")
for img_tag in img_tags:
img_src = img_tag.get_attribute("src")
r = requests.get(img_src, allow_redirects=True)
img_filename = img_src.rsplit('/', 1)[1]
open(save_dir + "/images/" + img_filename, 'wb').write(r.content)
driver.execute_script(replace_img_srcs_js) # see below
with open(save_to_file, 'w') as f:
f.write(driver.page_source)
This code edits the src attribute. I placed it to separate file to be able to see syntax highlighting. You can place the contents of it directly to the driver.execute_script(...) if you wish.
// replace_img_srcs.js
Array.prototype.slice.call(document.getElementsByTagName('img')).forEach(
function(item) {
var img_src = item.src;
var img_filename = img_src.replace(/^.*[\\\/]/, '');
var img_filename_urlencoded = encodeURIComponent(img_filename) // because images may be named with encoded symbols
item.src = item.src.replace(img_src, "../images/" + img_filename_urlencoded);
}
)
Now we have page saved for autonomous use.

Related

Can vscode's markdown preview scripts trigger actions directly in an extension?

I'm writing a vscode extension where I'm hoping to squeeze more dynamic functionality out of markdown preview. Effectively the problem I'm trying to solve is:
In markdown preview, there's a checkbox
When user clicks the checkbox in markdown preview, send a message/event to the vscode extension runtime
Vscode extension can listen for this message/event and store the action in local storage
Checkbox state is saved - and subsequent renders of the markdown preview can use this action
Ideally, I'd like to do this while keeping the default markdown preview security (https://code.visualstudio.com/Docs/languages/markdown#_strict). After all, I don't need the extension to or markdown preview script to talk to a remote server - I just want them to be able to talk to one another.
Problem as code
To write the problem as sudo code, I want my markdown preview script to contain something like:
const button = ... // get button element
button.addEventListener('click', () => {
... /*
* Send a message to the vscode extension. Something like:
* `vscode.postMessage('vscode.my-extension.preview-action' + value)`
* (which I can't get to work, I'll discuss why)
*/
});
where then my extension can listen for messages like 'vscode.my-extension.preview-action'.
What I've Tried Already
I have tried acquireVsCodeApi() but because the markdown extension already does that, I can't do it again in the subsequent loaded script. I've also tried registering a uri handler but as far as I can try out the preview script still needs to fetch to that uri, which is still blocked by the default markdown security settings.
Perhaps markdown preview scripts are not the place to do this kind of thing, but I just wanted to leverage as much as possible that's already there with the vscode markdown extension. I want to supplement markdown but not replace it, the functionality I want to add is just icing on markdown documentation.
I've read https://code.visualstudio.com/api/extension-guides/markdown-extension#adding-advanced-functionality-with-scripts and it doesn't tell me much about markdown extension scripts capabilities and limitations.
Thanks to #LexLi I looked at some of the source code in the markdown extension and was able to come up with an ugly hack to make this work in preview scripts. Markdown allows normal clicks. And vscode extensions can handle normal clicks. I've paraphrased the code so there could be small syntax errors.
In the extension I did this:
vscode.window.registerUriHandler({
handleUri(uri: vscode.Uri): vscode.ProviderResult<void> {
console.log(`EXTENSION GOT URL: ${uri.toString()}`);
},
});
Then I made sure my extension/preview script put this in the document
<!-- in the preview script I place a button like this -->
<!-- it even works with hidden :) so I can do more app customization -->
<a
hidden
id="my-extension-messager"
href="vscode://publisher-id.my-extension"
>
cant see me but I'm there
</a>
Then my preview script I can even set href before faking a click:
const aMessager = document.querySelector("#my-extension-messager");
console.log('client is setting attribute and clicking...')
aMessager.setAttribute('href', 'vscode://publisher-id.my-extension?action=do-something');
aMessager.click();
console.log('client clicked');
Logs I saw (trimmed/tweaked from my particular extension to match the contrived example):
client is setting attribute and clicking...
client clicked
[Extension Host] EXTENSION GOT URL: vscode://publisher-id.my-extension?action%3Ddo-something
It's a hack but I can do a lot with this. Within the URL I can encode data back to the extension and kind of pass whatever I want (as long as data is relatively small).

How to click on devtools console tab with Selenium and python

I have this code:
async def acess_all():
def acess_localhost():
option = Options()
option.add_argument("--no-sandbox")
option.debugger_Address="127.0.0.1:8081"
driver = webdriver.Chrome(options=option)
driver.get("http://127.0.0.1:8081")
wait = WebDriverWait(driver, 5)
wait.until(EC.element_to_be_clickable((By.CSS_SELECTOR, 'a.item'))).click()
try:
t = threading.Thread(target=get_infos)
t.start()
os.system("chromium --remote-debugging-port=8081 https://google.com")
except:
print("Error!")
What I need:
Guys, as you can see, this code opens the Chrome browser on the Google page, and my Selenium code opens the browser on localhost because it's accessing where the remote-debbuging address points to, but I can't access the console tab of devtools, I need to access this tab using Selenium and run a javascript command to copy the cookie in json format, but I can't, how can I access the devtools console tab?
I believe I've figured out a way to run a javascript command in the console and get a return value (using selenium, as in the question).
Note: I'm running this on a Windows computer, but the idea should remain the same on a different operating system. You might have to tweak a few things in this code. Also, all Chrome sessions have to be closed beforehand to get this to work.
Ok, unless I interpreted your question wrong (tell me if I did), this is the basic order of the things you want you want to happen when:
Open the regular chrome browser (by regular, I mean not selenium, but regular chrome.exe or Google Chrome.app) to google.com and set the debugging port (which I assume is what you're doing when you run this command: chromium --remote-debugging-port=8081 https://google.com)
Open Chromedriver (Selenium) and go to the locally-hosted debugger window at 127.0.0.1:8081
Select the "Google" window option in the list of available windows at 127.0.0.1:8081
Once the devtools window opens, move to the Console tab
Finally, run some Javascript in the Console's input box and somehow get a return value for a cookie
You already did the first few items (1-3) in your code but you needed help figuring out the rest. I think I've found a way to do it.
So assuming that you already opened the google.com window on the Chrome browser and the 127.0.0.1:8081 window on localhost, all you need to do now is access the Console.
Here's what my chromedriver (selenium) browser screen looks like at this point, just for reference.
We'll start by waiting until a specific element (devtools-elements-breadcrumbs) has loaded on the page. We wait for this so we are sure that the page is loaded enough to navigate. I found this element by looking at the driver page_source. Here's how we wait for it:
wait.until(EC.presence_of_element_located((By.TAG_NAME, "devtools-elements-breadcrumbs")))
Once the breadcrumb element is located we can move to the console window by sending a shortcut to the chromedriver browser telling it to move right one tab in the Inspector window (to Console). The command is Control + ] (Windows/Linux) or Command + ] (Mac), to go to the next panel. This is how we send that shortcut to the selenium window (once again, using Windows):
click_console = ActionChains(driver)
click_console.key_down(Keys.CONTROL).send_keys(']').key_up(Keys.CONTROL).perform()
or on a Mac,
click_console = ActionChains(driver)
click_console.key_down(Keys.COMMAND).send_keys(']').key_up(Keys.COMMAND).perform()
After moving to the Console, we now have to wait for the console area to load, so we wait for all the CodeMirror class elements (once again, found using driver.page_source)
# wait for console area to open
wait.until(EC.presence_of_all_elements_located((By.CLASS_NAME, "CodeMirror")))
Now the last thing to do is to send the javascript command to the console. In this example, I'm searching for the APISID cookie.
cookie_name = "SEARCH_SAMESITE"
# javascript find the cookie
# https://stackoverflow.com/a/59603055/11073064
js_command = '(\"; \" + document.cookie).split(\"; {}\").pop().split(\';\').shift();'.format(cookie_name)
# send the command to the selenium webbrowser
send_js_command = ActionChains(driver)
send_js_command.send_keys(js_command, Keys.ENTER).perform()
and to get the value outputted in the console after you run that command:
# wait for return value span tag to be found
element = wait.until(EC.presence_of_element_located((By.CSS_SELECTOR, 'span.object-value-string.source-code')))
value = element.text
driver.close()
Here's the full code I used (on Windows).
from selenium import webdriver
from selenium.webdriver.support.ui import WebDriverWait
from selenium.webdriver.support import expected_conditions as EC
from selenium.webdriver.common.by import By
import threading
from selenium.webdriver.chrome.options import Options
from selenium.webdriver import ActionChains
from selenium.webdriver.common.keys import Keys
import os
def start_chrome_browser_with_debug():
# this is the path to your regular google chrome browser, not selenium chromedriver
# note the chrome.exe, not chromedriver.exe.
# point the path to your regular chrome browser
os.system('"C:\Program Files (x86)\Google\Chrome\Application\chrome.exe" --remote-debugging-port=8081 https://google.com')
cookie_name = "SEARCH_SAMESITE"
js_command = '(\"; \" + document.cookie).split(\"; {}\").pop().split(\';\').shift();'.format(cookie_name)
chrome_browser = threading.Thread(target=start_chrome_browser_with_debug)
chrome_browser.start()
option = Options()
option.add_argument("--no-sandbox")
option.debugger_Address = "127.0.0.1:8081"
driver = webdriver.Chrome(options=option)
driver.get("http://127.0.0.1:8081")
wait = WebDriverWait(driver, 5)
wait.until(EC.element_to_be_clickable((By.CSS_SELECTOR, 'a.item'))).click()
wait.until(EC.presence_of_element_located((By.TAG_NAME, "devtools-elements-breadcrumbs")))
click_console = ActionChains(driver)
click_console.key_down(Keys.CONTROL).send_keys(']').key_up(Keys.CONTROL).perform()
wait.until(EC.presence_of_all_elements_located((By.CLASS_NAME, "CodeMirror")))
send_js_command = ActionChains(driver)
send_js_command.send_keys(js_command, Keys.ENTER).perform()
element = wait.until(EC.presence_of_element_located((By.CSS_SELECTOR, 'span.object-value-string.source-code')))
value = element.text
driver.close()
os.system('taskkill /F /IM chrome.exe /T')
print(value)
Actually, you can execute the javascript command by selenium itself :
driver.execute_script("some javascript code here");
You can't use Selenium to interact with the dev tools console, however you don't need it to get the cookies, just use get_cookies() from the webdriver and convert it to json using json.dumps()
import json
cookies = driver.get_cookies()
js = json.dumps(cookies)
I couldn't make this work on a Mac using only Python and Selenium. However, I found a solution that works if I use pyautogui in addition.
I'm working on a file, duals7.svg, saved locally. I want to be able to open it, play with it and watch what happens at the Javascript developer console in Chrome.
The script below does exactly what I want:
# chrome.py
# https://www.browserstack.com/guide/python-selenium-to-run-web-automation-test
# https://www.tutorialspoint.com/how-do-i-pass-options-to-the-selenium-chrome-driver-using-python
# https://stackoverflow.com/questions/36311191/how-to-open-chrome-developer-console-in-selenium-webdriver-using-java
# https://towardsdatascience.com/controlling-the-web-with-python-6fceb22c5f08
from selenium import webdriver
from selenium.webdriver.chrome.options import Options as ChromeOptions
import pyautogui
op = webdriver.ChromeOptions ()
op.add_argument ('--allow-file-access-from-files')
op.add_argument ('--auto-open-devtools-for-tabs')
driver = webdriver.Chrome (options=op)
driver.get ('file:///Users/murray/projects/warp/dual7.svg')
console = (850, 175)
pyautogui.moveTo (console [0], console [1], duration = 0.5)
pyautogui.click (console [0], console [1])
(I don't think the pyautogui.moveTo command is needed. Including it allows me to watch the mouse move, which I like.)
When I run it (python chrome.py), I see my svg "app" in Chrome with the Javascript dev console open:
The coordinates of the console button were determined by trial and error. Please forgive the aesthetics of the app: it'll look nice when it's done.
When I interact with my "app" I see my console.log () debugging messages.
If you're interested, the app is being modified from
/* elliptic-mesh.js
*
* generates a structured mesh bounded by four splines by
* solving the elliptic Laplace equation
*
* For more info see:
* http://www.particleincell.com/2012/online-meshing-example/
* http://www.particleincell.com/2012/bezier-splines/
*
* Lubos Brieda, Particle In Cell Consulting LLC, 2012
* you may freely use this algorithm in your codes but whenever possible
* please include a link/reference to the source article
*/
My version generalizes Dr. Brieda's code a bit, adds some debugging statements, and will add save and load options.

How to create a better screenshot during the selenium automation in JAVA?

I am creating automated test cases by using selenium 3 & testng. Everything looks good, except the screenshots that are generated. Here is my piece of code to create screenshots PNG files:
file = ((TakesScreenshot) driver).getScreenshotAs(OutputType.FILE);
FileUtils.copyFile(file, new File(pngfile));
which is pretty standard way to do it, but the quality of the created PNG file is not so good.
As you can see from following PNG file. In the picture, the email value ( "....#yahoo.com"), which should be at the upper-right corner of the web-page and should be as high as the other navigation bar elements on the left side. But in the created PNG file, this item has been squeezed to the lower level, which is not what I am looking for. Any ideas ? Thanks for the help !
Make sure your window is the right size when you're opening the browser. You can do this via visual inspection or using Selenium's getSize method. I assume you're using Java, but here it is in Python as well.
Then, if the window is not of the correct size in order to guarantee that your webpage's CSS doesn't break, use setSize. Here is that method in Python as well.
Afterwards, your screenshot should look like the window does.
Please try this,
public void calltakeScreenShot(String SSName) throws Exception
{
File screen = ((TakesScreenshot) driver).getScreenshotAs(OutputType.FILE);
BufferedImage img = ImageIO.read(screen);
File filetest = Paths.get(".").toAbsolutePath().normalize().toFile();
ImageIO.write(img, "png", new File(filetest + "\\Screenshots\\" + SSName + ".png"));
}
Additionally Here you just need to pre-created Screenshots folder in your Project directory. It will store it by getting absolute path of your project. Also you can manage screenshot name by passing argument.
After several day's research, here is my latest summary.
A). If I was executing the scripts, that is not in the "headless" mode. When the selenium test case is being executed, I will see a new browser session is being popped up and get to that URL, click some buttons, etc, ... till the execution is finished. In this execution, the screenshot page will be saved in good quality.
B). For the same selenium test script, if I am including one extra ChromeOptions setting, which is "--headless", I will not see any browser being brought up during the execution. And once execution is finished, I will get the screenshot with such squeezed web elements.
Comments ?

Automate a button click on chrome://extensions page using selenium webdriver

I'm trying to write an automated test that will automate the process of updating a google chrome extension. I'm not aware of another method of doing this automatically so here is what I'm currently trying to do:
Open the chrome extensions page (as far as I'm aware this is just an html page unless I'm missing something).
Click on the "Update extensions" button
Here is what I have tried having opened the chrome extensions page:
IwebElement UpdateButton = driver.findelement(By.Id("update-extensions-now"));
UpdateButton.Click();
For some reason the button click is not registering. I have tried some other locators such as CSS path and Xpath but they don't work either. Also, when I debug this test, it passes fine so I know it's not an issue with any of my locators. I have (as a test) tried to automate clicks on the other elements on this page and it's the same issue. I can't get a handle on any elements on the chrome://extensions page at all.
Has anyone encountered this or have any ideas as to what's going on?
You can use the Chrome extensions API to auto-update required extension.
Find the file "manifest.json" in the default Google Chrome
C:\Users\*UserName*\AppData\Local\Google\Chrome\User Data\Default\Extensions
There find the update URL of your extension:
{
"name": "My extension",
...
"update_url": "http://myhost.com/mytestextension/updates.xml",
...
}
The returned XML by the Google server looks like:
<?xml version='1.0' encoding='UTF-8'?>
<gupdate xmlns='http://www.google.com/update2/response' protocol='2.0'>
<app appid='yourAppID'>
<updatecheck codebase='http://myhost.com/mytestextension/mte_v2.crx' version='2.0' />
</app>
</gupdate>
appid
The extension or app ID, generated based on a hash of the public key, as described in Packaging. You can find the ID of an extension or Chrome App by going to the Extensions page (chrome://extensions).
codebase
A URL to the .crx file.
version
Used by the client to determine whether it should download the .crx file specified by codebase. It should match the value of "version" in the .crx file's manifest.json file.
The update manifest XML file may contain information about multiple extensions by including multiple elements.
Another option is to use the --extensions-update-frequency command-line flag to set a more frequent interval in seconds. For example, to make checks run every 45 seconds, run Google Chrome like this:
chrome.exe --extensions-update-frequency=45
Note that this affects checks for all installed extensions and apps, so consider the bandwidth and server load implications of this. You may want to temporarily uninstall all but the one you are testing with, and should not run with this option turned on during normal browser usage.
The request to update each individual extension would be:
http://test.com/extension_updates.php?x=id%3DyourAppID%26v%3D1.1
You can find even more detailed information on exntesions developers site: https://developer.chrome.com/extensions
If you look at the HTML of the "chrome://extensions" page you will notice that the "Update extensions now" button is contained within an iframe. You need to switch to the iframe before trying to register a button click. i.e:
(This is in c#. Note that this code is written from memory so it may not be 100% accurate. Also, you will want to write more robust method. This code just quickly demonstrates that by switching to the iframe, it will work ok)
String ChromeExtensionsPage = "chrome://extensions";
driver.Navigate().GoToUrl(ChromeExtensionsPage);
driver.Switchto().Frame("DesiredFrame");
IwebElement UpdateButton = driver.findelement(By.Id("DesiredButtonID"));
UpdateButton.Click();

File Upload Testing in Nightwatch.js

I'd like to reopen the question posed here and here about testing file uploading within Nightwatch.js which uses selenium.
Both links have the recommended solution of setting the value of the file input element as the url. In my use case, I've been unable to get this to work. Even setting the value tag manually, outside of nightwatch, of the input where type="file", does not change the url. I've tried this on Chrome, Firefox, and IE10, within the dev tools.
An alternative solution I've looked at was trying to emulate the entire file upload process keystrokes. This would follow the path of clicking the file upload button, typing the path, and typing enter. This would be done through the .click and .key methods. However, you lose focus of the actual file upload window, which delays the keystrokes until that window is closed. Other developers have seemed to be able to fix this solution directly in selenium using the .findElement and .sendKeys methods in java, but I could not figure out how to do this within javascript and nightwatch itself.
Any ideas?
// My test
module.exports = {
"Standard File Upload" : function (browser) {
browser
.url("http://localhost:3000")
.waitForElementVisible('body', 1000)
.waitForElementVisible('input[type="file"]', 1000)
.setValue('input[type="file"]','http://localhost:3000/testfile.txt')
.click('#submit')
.pause(1000)
.assert.containsText('h3', 'File Uploaded Successfully')
.end();
}
};
// http://localhost:3000/testfile.txt was tested manually in the file upload window and worked successfully
<!-- My input tag -->
<input id="fileUpload" type="file" name="textfile"/>
There were two seperate issues with my setValue() method implementation.
Using the --verbose tag in the nightwatch command led me to an issue
where it was not actually finding the input tag during the
setValue(), however it was during the waitForElementVisible().
Changing input[type="file"] to input#fileUpload solved this
issue.
Secondly, the following ways of describing the path were not working...
'textfile.txt'
'http://localhost:3000/testfile.txt' (Will work if typed manually into file upload window)
What did work was using require('path').resolve(__dirname + '/testfile.txt')
Take a look here to see the discussion that led to the fix. Thanks goes out to richard-flosi.
The working code:
module.exports = {
"Standard File Upload" : function (browser) {
browser
.url("http://localhost:3000")
.waitForElementVisible('body', 1000)
.waitForElementVisible('input#fileUpload', 1000)
.pause(1000)
.setValue('input#fileUpload', require('path').resolve(__dirname + '/testfile.txt')) // Works
// .setValue('input#fileUpload', "testfile.txt") // Will not work
// .setValue('input#fileUpload', "http://localhost:3000/testfile.txt") // Will not work
// .setValue('input[type="file"]', require('path').resolve(__dirname + '/testfile.txt')) // Will not work
.click('#submit')
.pause(1000)
.assert.containsText('h3', 'File Uploaded Successfully')
.end();
}
};
I'm not sure why you're having these issues, maybe check to see if you are using the latest version of selenium server and nightwatch. This code works for me 100% in Chrome, Safari, Firefox, IE7/8/9/10/11 (not tested in IE6 but assume it as well).
driver.setValue('input#fileUpload', __dirname + '\\testfile.txt')
In my case, I had an additional problem because the file I was trying to upload was too high up in my directory structure.
As soon as I moved the file to the same level (or in a subdirectory of) the actual test files, things worked.
From a script living in my page-objects folder:
// No dice:
var fullPath = require('path').resolve(__dirname + '/../../somefile.pdf');
// Works:
var fullPath = require('path').resolve(__dirname + '/../somefile.pdf');
this.setValue('input#fileUpload', fullPath);