Disabling Lightning Experience w/ Python and Selenium in Salesforce
12 Sep 2019
You saw me be too lazy to click buttons in No-Permissions Salesforce Profile w/ Python and Selenium: watch me do it again turning Lightning Experience off in all my Custom Profiles.
(And watch me get shunned from the Salesforce community for making it easier to neuter the Winter ‘20 release feature that flips Lightning Experience on?)
Background
Where I work, we’re going to roll out Lightning Experience to our users by Permission Set, not by Profile.
We don’t have any entire profiles trained up, so we want to make sure they see nothing about Lightning Experience when it goes on.
(Our help desk already got calls from confused end-users when our testing sandbox asked people if they wanted to try Lightning Experience after this weekend’s upgrade.)
The Task
I want to loop over all my profiles, which I can find in Salesforce at https://MyURL.com/00e
, check if there’s an editable System Permission called “Lightning Experience User,” and make sure it’s un-checked.
In Setup, this is under Profiles, in the “System Permissions” area of a given Profile’s configuration page.
It turns out that editable versions of this page for every profile are available at:
https://MyURL.com/00eSOMETHING/e?s=SystemPermissions
…where 00eSOMETHING
is the ID of the profile (which can be revealed in the profile-by-profile links in https://MyURL.com/00e
take you, handily enough).
The Code
As before, I’m going to “screen-scrape” Salesforce web pages and “hijack” my own browser to save myself a few clicks.
- Yes, of course this took me longer to code than to click manually.
- That said, now I can reuse my code against any org.
- Until Salesforce changes the HTML behind their setup pages and breaks all my code, of course.
(That’s the danger of screen-scraping.)
- Until Salesforce changes the HTML behind their setup pages and breaks all my code, of course.
Note that this works against Setup in the “classic” user interface – the HTML’s probably all different in Lightning Experience.
(I wouldn’t know – I have it turned off!)
Step 1: Log In
First, I ran the following Python code
from selenium.common.exceptions import NoSuchElementException
from selenium.webdriver import Chrome
browser = Chrome(executable_path='C:\\exampleprograms\\ChromeDriver\\chromedriver.exe')
browser.get('https://test.salesforce.com')
This caused a special instance of Chrome to pop up on my computer.
I logged into Salesforce with my username and password as usual.
Step 2: Get My Object Links
Then I commented out the code above and, without letting my Python IDE clear out the value of the browser
variable (which is normal behavior of my IDE from one “run” button click to the next), I ran the following code:
editableLinksToVisit = []
browser.get('https://cs99.salesforce.com/00e')
profilerows = browser.find_elements_by_xpath("//table[contains(@class, 'x-grid3-row-table')]")
for profilerow in profilerows:
profileLink = profilerow.find_element_by_css_selector("div[id$='ProfileName']").find_element_by_css_selector("a[href*='/00e']")
profileBaseURL = profileLink.get_attribute('href')
profileEditURL = profileBaseURL + '/e?s=SystemPermissions'
editableLinksToVisit.append({'text':profileLink.text, 'editLink':profileEditURL})
Here’s what the code above does:
- The
browser.get()
command made me visit a new web site in Chrome – as if I myself had typed that URL into my browser bar and hit “enter.”- It’s pretty trippy – you literally watch your computer do things without you, like someone else has taken over control.
- The URL I told
browser.get()
about is the URL of the “profiles” screen.- Note that this script won’t run so well if you’re using a List View of profiles that doesn’t, say, show all of them – or at least all the custom ones.
- When I run
browser.get()
, the contents stored in my program’sbrowser
variable change. - Then I use various methods built into the data type I’ve stored in
browser
(I forget exactly what it is), such as.find_element_by_css_selector()
, to scrape the contents of this particular web page.- Technically, I’m scraping the DOM, or the thing you see when you use the Firefox or Chrome developer console “inspect element” tool, not scraping the HTML source. Their contents are ever-so-slightly different. It used to be that you could tell what HTML was making up a page just by looking at its source. These days, JavaScript can “inject” HTML into the “DOM” at the last minute, and your browser actually renders a web page based on the DOM, so you have to know how to check what’s really in the DOM if you want to parse the code behind a web page.
- My
for
loop looks for links on the page within a<div>...</div>
tagset whoseid
property ends with “ProfileName
” that point to a URL containing the text “/00e
.” - Then I grab the name of the object (per the link’s text) and its URL.
- Finally, I edit the link by sticking an
/e?s=SystemPermissions
at the end of it – now instead of visiting a Profile overview page, I’ll go straight to an editable “System Permissions” configuration page when I visit my link.
My big list of object names and their links is what I store as editableLinksToVisit
.
Step 3: Click lots of buttons
I again commented out the code above and, without letting my Python IDE clear out the value any variables (which is normal behavior of my IDE from one “run” button click to the next), took a stretch and ran the following code (it took about 1-2 minutes to run 30 profiles):
for linkDict in editableLinksToVisit:
print()
print(linkDict.get('text'))
browser.get(linkDict.get('editLink'))
try:
submitbutton = browser.find_element_by_css_selector("input[type='submit'][value='Save'][id$='button_pc_save']")
except NoSuchElementException:
submitbutton = None
try:
checkedCheckbox = browser.find_element_by_css_selector("input:enabled:checked[type='checkbox'][title='LightningExperienceUser']")
except NoSuchElementException:
checkedCheckbox = None
if submitbutton is not None and checkedCheckbox is not None:
print('submit ' + str(submitbutton is not None))
print('checkbox ' + str(checkedCheckbox is not None))
checkedCheckbox.send_keys(" ")
submitbutton.send_keys("\n")
print('done')
else:
print('There is nothing to save.')
This code is a loop over all the description-URL link pairs (dictionaries) I’d saved into editableLinksToVisit
.
Here’s what it does for each link in the list:
- It writes down a note I can read telling me what object it’s supposed to be about to hijack my browser to visit the URL of
- It hijacks my browser and visits that URL.
- It reads the code of the page’s DOM to find the Save button.
- If there isn’t one, it makes a note of that.
- It reads the code of the page’s DOM to find the first checkbox on the page titled
LightningExperienceUser
that is currently checked and un-checkable.- If there isn’t one, it makes a note of that.
- If it found anything that “could use un-checking,” it does so.
- I use an imitation of hitting the spacebar after “tabbing” to a checkbox with the keyboard, rather than Selenium’s
.click()
command, because.click()
wasn’t working reliably and someone on StackOverflow said this might and they were right. 🤷
- I use an imitation of hitting the spacebar after “tabbing” to a checkbox with the keyboard, rather than Selenium’s
- It clicks the Save button (again, I simulate this by virtually hitting “enter” on the keyboard rather than by clicking because for some reason that worked more reliably).
You can watch your browser “drive” without you. It’s kind of fun.
There’re also a lot of print()
notes so you can watch what’s going on from your “standard output” terminal of the software in which you’re running your Python code and anticipate how far through the list it is.
Conclusion
So … there you go. A new party trick for lazy adminelopers.
Have fun, and don’t forget to give people you do want to be able to see Lightning Experience that permission in a Permission Set after you disable it in all custom Profiles.
You do that by:
- Creating a permission set
- Going into its “System Permissions” area and clicking “Edit”
- Checking the “Lightning Experience User” checkbox and clicking “Save”
- Assigning a user to the permission set in question