InvisionApp doesn't allow bulk exporting of prototypes or restoring them from archives. This means that you would have to export or restore them one-by-one! These scripts will help you automate the tedious manual jobs in case you have lots of prototypes.
- Python3 (If you don't have Python 3 installed, I recommend installing it using pyenv)
- ChromeDriver
- Mac OS brew:
brew install chromedriver
- Mac OS brew:
- webdriver_manager
- selenium
These libraries are managed locally using pipenv.
You can also install them globally
pip3 install webdriver_manager selenium
.
- $
pipenv install
- Make a copy of example-private.py and rename it to
private.py
- Enter all the fields required by the scripts that you are planning to run.
$ pipenv run python3 export.py
- Start exporting all the prototypes from the space you specified one-by-one! The exported prototypes will be archived and can be restored later from the "Archived" tab
$ pipenv run python3 restore.py
- Restore archived projects one-by-one from a space
There are 3 export options for Invision prototypes. You can specify one format each time you run export.py
. Uncomment the option that you choose in export.py
, e.g. To export as offline prototype (HTML):
##images zip
# downloadOption = wait.until(EC.element_to_be_clickable((By.XPATH, '//*[@id="app-shell:feature-root:prototype-overview"]/div/div/div[1]/div/div[1]/div/section/div[2]/div[1]/div/div/div/ul/li[5]/div/ul/li[3]/div/button')))
##HTML
downloadOption = wait.until(EC.element_to_be_clickable((By.XPATH, '//*[@id="app-shell:feature-root:prototype-overview"]/div/div/div[1]/div/div[1]/div/section/div[2]/div[1]/div/div/div/ul/li[5]/div/ul/li[2]/div/button')))
##PDF
# downloadOption = wait.until(EC.element_to_be_clickable((By.XPATH, '//*[@id="app-shell:feature-root:prototype-overview"]/div/div/div[1]/div/div[1]/div/section/div[2]/div[1]/div/div/div/ul/li[5]/div/ul/li[1]/div/button')))
If you like to export with more than one options, for example - export all prototypes as HTML and images:
- Set
downloadDir = '/Users/[user]/Downloads/invision/html/'
inprivate.py
and uncomment the HTML download option inexport.py
- $
pipenv run python3 export.py
- $
pipenv run python3 restore.py
- Set
downloadDir = '/Users/[user]/Downloads/invision/png/'
inprivate.py
and uncomment the images zip download option inexport.py
- $
pipenv run python3 export.py
- $
pipenv run python3 list.py
- If you have a reference list of all the project names, this can help you check what are missing in your download directory from that list. - $
pipenv run python3 cleanup.py
- This helps you clean up duplicated downloads of the same files in a specified local directory
Because the InvisionApp is built with React with asynchronous loading - the projects load and unload as the page scrolls, there's no straight-forward way to get the total count of the projects, nor the total length of the page content as new content loads automatically when the scroll bar reaches the bottom (aka infinite scroll). Using selenium to simulate page scrolls in order to get to the entire list of projects keeps having issues of web element staleness. This makes it particularly hard if you have to scroll down multiple times to keep making manual exports (infinite scroll controversy in terms of usability and accessibility - see this nngroup article)
In export.py
and restore.py
, you should set an upper bound of the for loop range based on the estimated count of your prototypes in the space. Don't worry if you overshoot or undershoot for the estimation - if you overshoot, the script will throw an timeout exception once all prototypes are exported or restored; or if you undershoot, you can always run it again and it will pick up from where it left off. To get an accurate estimation, this is what I did to count them using JS in the browser console:
let list =[]
function getProjects() {
const p = document.querySelectorAll('div[class^="grid__grid__"] > div > div')
p.forEach((e, i) => {
const name = e.innerText.split('\n')[0]
console.log(i, name)
list.push(name)
})
return p
}
function scroll() {
const projects = getProjects()
const p = projects[projects.length -1]
console.log('!!! scroll to: '+ p.innerText.split('\n')[0])
p.scrollIntoView()
}
// estimated scrolls
totalScrolls = 20
for (i = 1; i < (totalScrolls + 1); i++) {
//window.scroll(356 * 3 *(i-1));
getProjects()
scroll()
}
console.log(list)
Copyright (c) 2021 Minghua Sun