switchTab() cannot make the tab as currentTab in WebdriverIO standalone mode - selenium-chromedriver

webdriverio with selenium-standalone server, without wdio.
Clicking a link to open a new tab.
I can see the new tab opened, but no elements on the new tab can be located, as if the new tab never existed.
The titles of the first tab and the new tab:
C:\Users\...\test>node switchtab.js
main window: CDwindow-(D77C1A0428E3E5717A1DF2473274B905)
Title is: WebdriverIO - WebDriver bindings for Node.js
new window: CDwindow-(D77C1A0428E3E5717A1DF2473274B905)
Title is: WebdriverIO - WebDriver bindings for Node.js
switchTab: CDwindow-(BE8C6F13723E588E3E39C7C21C02C290)
current window: CDwindow-(D77C1A0428E3E5717A1DF2473274B905)
Title is: WebdriverIO - WebDriver bindings for Node.js
It always gets the first tab id as you can see. Therefore no elements on new tab can be located.
What did I do wrong? How can I make the new tab as current tab? Even though I see the new tab becomes the active tab, but apprently the code doesn't think so.
Here is the sample code snippet:
var webdriverio = require('webdriverio');
var options = { desiredCapabilities: { browserName: 'chrome' } };
var client = webdriverio.remote(options);
var mainW, newW, newW1;
client
.init()
.url('http://webdriver.io/')
.getCurrentTabId().then(function (handle) {
mainW = handle;
console.log('main window: ' + mainW);
})
.getTitle().then(function(title) {
console.log('Title is: ' + title);
})
.waitForExist('a[href="http://try.learnwebdriverio.com"]')
.click('a[href="http://try.learnwebdriverio.com"]')
.pause(5000)
.getCurrentTabId().then(function (handle) {
newW = handle;
console.log('new window: ' + newW);
})
.getTitle().then(function(title) {
console.log('Title is: ' + title);
})
.getTabIds().then(function (handle) {
console.log('switchTab: ' + handle[1]);
newW1 = handle[1];
})
.switchTab(newW1)
.pause(5000)
.getCurrentTabId().then(function (handle) {
console.log('current window: ' + handle);
})
.getTitle().then(function(title) {
console.log('Title is: ' + title);
.end();
switchTab() won't help. After .switchTab(newW1), it still gets the first tab. And even worse, I see it physically switches to the first tab.

It works by using Testrunner (with mocha, and I think other frameworks would be just the same). The only difference between standalone mode and Testrunner mode I see is that:
Different than using the standalone mode all commands that get executed by the wdio test runner are synchronous.
Therefore, It should have something to do with synchronization, which the standalone mode doesn't handle correctly.
Please note that in Testrunner mode, the new tab is also not current after clicking the link. You have to manually switchTab() to set the new tab as current.

Related

Open extension popup when click on context menu

I have to make an extension that when clicked on text in the context menu, in callback opens the extension menu popup.
chrome.runtime.onInstalled.addListener(function() {
var context = "selection";
var title = "Google for Selected Text";
var id = chrome.contextMenus.create({"title": title, "contexts":["selection"],
"id": "context" + context});
});
// add click event
chrome.contextMenus.onClicked.addListener(onClickHandler);
// The onClicked callback function.
function onClickHandler(info, tab) {
var sText = info.selectionText;
var url = "https://www.google.com/search?q=" + encodeURIComponent(sText);
//what i have put here to open extension popup
};
In this case, when I click on the menu I open a new tab with this search.
There is no way of opening the default browser action popup programmatically. A work around is use content scripts to open a modal or a lightbox and show the contents of your popup.
Another way would be - within the clickhandler of your context menu item, create a new tab and make it inactive and then pass that tab to chrome.windows.create api to create a new popup window.
chrome.tabs.create({
url: chrome.extension.getURL('popup.html'),
active: false
}, function(tab) {
// After the tab has been created, open a window to inject the tab
chrome.windows.create({
tabId: tab.id,
type: 'popup',
focused: true
});
});
It is just a work around. Hope it helps.
It is now possible to open a browser action popup programmatically from inside the handler for a user action.
browser.menus.create({
id: "open-popup",
title: "open popup",
contexts: ["all"]
});
browser.menus.onClicked.addListener(() => {
browser.browserAction.openPopup();
});
You can read more about it here.
Edit:
This feature is only available as of Firefox 57. In Chrome, it is only available in the dev channel.
Sources: chrome/common/extensions/api/_api_features.json - chromium/src - Git at Google
Unfortunately, it cannot be done.
Chrome API doesn't provide a method to open extension popup programmatically. The Chromium team rejected the feature request for such an option with an explanation that:
The philosophy for browser and page action popups is that they must be
triggered by user action.
Here's the source.
You can use the chrome.window API (documentation here).
What you want is something like this :
chrome.windows.create({
url : "http://yourPopupUrl.com"
focused : true
type : "popup"});
This will open a new windows in popup mode (without the top menu bar) and load the "http://yourPopupUrl.com".

Chrome Dev Tools export Elements HTML

To debug my chromium-embedded application I am looking for a function to get the source code of the web page from withing the chrome developer tools. I basically want the HTML tree shown in the 'Elements' tab, the actual HTML DOM, as HTML text. Does this functionality exist? How can I use it?
As I use CEF I do only have the chrome dev tools available and not the full browser. I cannot use the right-click context menu because I want to see the current manipulated DOM and not the original source.
I want to use this functionality for debugging purpose so that I can diff two different HTML trees.
Select the top node and choose "Copy". You'll have to re-add the Doctype, though.
Alternatively, you could click on "edit as HTML" and copy it from there.
update: extension has been released! Named Dump Dom
chrome store
github source
I found a better way to dump the current dom tree to a html file ( to persist your changes to the dom tree in the element tab ),just paste the code below to the console, and a dom.html file would be downloaded.
filename = "dom";
var html = '',
node = document.firstChild
while (node) {
switch (node.nodeType) {
case Node.ELEMENT_NODE:
html += node.outerHTML
break
case Node.TEXT_NODE:
html += node.nodeValue
break
case Node.CDATA_SECTION_NODE:
html += '<![CDATA[' + node.nodeValue + ']]>'
break
case Node.COMMENT_NODE:
html += '<!--' + node.nodeValue + '-->'
break
case Node.DOCUMENT_TYPE_NODE:
// (X)HTML documents are identified by public identifiers
html +=
'<!DOCTYPE ' +
node.name +
(node.publicId ? ' PUBLIC "' + node.publicId + '"' : '') +
(!node.publicId && node.systemId ? ' SYSTEM' : '') +
(node.systemId ? ' "' + node.systemId + '"' : '') +
'>\n'
break
}
node = node.nextSibling
}
var file = new Blob([html], {
type: 'text/html'
});
if (window.navigator.msSaveOrOpenBlob) // IE10+
window.navigator.msSaveOrOpenBlob(file, filename);
else { // Others
var a = document.createElement("a"),
url = URL.createObjectURL(file);
a.href = url;
a.download = filename;
document.body.appendChild(a);
a.click();
setTimeout(function () {
document.body.removeChild(a);
window.URL.revokeObjectURL(url);
}, 0);
}
Inspired from this project: https://github.com/wingleung/save-page-state.
And I would develop an extention to make on-click-dump functional later.
You can try the following:
All you have to do is right click the element and copy the outerHTML.

Enabling Chrome Extension in Incognito Mode via CLI flags?

I'm using selenium to test a chrome extension and part of the extension requires the user to be in incognito mode. Currently, I've not been able to enable the extension to be allowed in incognito mode upon startup except by adding the argument user-data-dir=/path/to/directory.
The problem with this is that it loads the extension from the depths of my file system, rather than in a way I can check into git.
I've also tried navigating selenium to the chrome extensions settings page but it seems that selenium can't drive chrome:// pages.
Any ideas on to how to enable incognito on the chrome extension on boot of the chrome driver?
Here is the solution that will work with the latest version of Chrome 74.
Navigate to chrome://extensions
Click on Details button for your desired extension
Copy the url (This contains your extension id)
Now we have to navigate to the above url and then click on the allow in incognito toggle.
Java:
driver.get("chrome://extensions/?id=bhghoamapcdpbohphigoooaddinpkbai");
JavascriptExecutor js = (JavascriptExecutor) driver;
js.executeScript("document.querySelector('extensions-manager').shadowRoot.querySelector('#viewManager > extensions-detail-view.active').shadowRoot.querySelector('div#container.page-container > div.page-content > div#options-section extensions-toggle-row#allow-incognito').shadowRoot.querySelector('label#label input').click()");
Python:
driver.get("chrome://extensions/?id=bhghoamapcdpbohphigoooaddinpkbai")
driver.execute_script("return document.querySelector('extensions-manager').shadowRoot.querySelector('#viewManager > extensions-detail-view.active').shadowRoot.querySelector('div#container.page-container > div.page-content > div#options-section extensions-toggle-row#allow-incognito').shadowRoot.querySelector('label#label input').click()");
Continue Reading, if you want to know how and why
Root Cause:
As part of enhancements to the chrome browser, google moved all the chrome option in to shadow dom. So you can not access allow in incognito toggle element as selenium find_element method which will point to the original dom of the page. So we have to switch to the shadow dom and access the elements in the shadow tree.
Details:
Shadow DOM:
Note: We will be referring to the terms shown in the picture. So please go through the picture for better understanding.
Solution:
In order to work with shadow element first we have to find the shadow host to which the shadow dom is attached. Here is the simple method to get the shadow root based on the shadowHost.
private static WebElement getShadowRoot(WebDriver driver,WebElement shadowHost) {
JavascriptExecutor js = (JavascriptExecutor) driver;
return (WebElement) js.executeScript("return arguments[0].shadowRoot", shadowHost);
}
And then you can access the shadow tree element using the shadowRoot Element.
// get the shadowHost in the original dom using findElement
WebElement shadowHost = driver.findElement(By.cssSelector("shadowHost_CSS"));
// get the shadow root
WebElement shadowRoot = getShadowRoot(driver,shadowHost);
// access shadow tree element
WebElement shadowTreeElement = shadowRoot.findElement(By.cssSelector("shadow_tree_element_css"));
In order to simplify all the above steps created the below method.
public static WebElement getShadowElement(WebDriver driver,WebElement shadowHost, String cssOfShadowElement) {
WebElement shardowRoot = getShadowRoot(driver, shadowHost);
return shardowRoot.findElement(By.cssSelector(cssOfShadowElement));
}
Now you can get the shadowTree Element with single method call
WebElement shadowHost = driver.findElement(By.cssSelector("shadowHost_CSS_Goes_here));
WebElement shadowTreeElement = getShadowElement(driver,shadowHost,"shadow_tree_element_css");
And perform the operations as usual like .click(), .getText().
shadowTreeElement.click()
This Looks simple when you have only one level of shadow DOM. But here, in this case we have multiple levels of shadow doms. So we have to access the element by reaching each shadow host and root.
Below is the snippet using the methods that mentioned above (getShadowElement and getShadowRoot)
// Locate shadowHost on the current dom
WebElement shadowHostL1 = driver.findElement(By.cssSelector("extensions-manager"));
// now locate the shadowElement by traversing all shadow levels
WebElement shadowElementL1 = getShadowElement(driver, shadowHostL1, "#viewManager > extensions-detail-view.active");
WebElement shadowElementL2 = getShadowElement(driver, shadowElementL1,"div#container.page-container > div.page-content > div#options-section extensions-toggle-row#allow-incognito");
WebElement allowToggle = shadowElementL2.findElement(By.cssSelector("label#label input"));
allowToggle.click();
You can achieve all the above steps in single js call as at mentioned at the beginning of the answer (added below just to reduce the confusion).
WebElement allowToggle = (WebElement) js.executeScript("return document.querySelector('extensions-manager').shadowRoot.querySelector('#viewManager > extensions-detail-view.active').shadowRoot.querySelector('div#container.page-container > div.page-content > div#options-section extensions-toggle-row#allow-incognito').shadowRoot.querySelector('label#label input')");
In chrome version 69 this code works (Python version):
driver.get('chrome://extensions')
go_to_extension_js_code = '''
var extensionName = 'TestRevolution';
var extensionsManager = document.querySelector('extensions-manager');
var extensionsItemList = extensionsManager.shadowRoot.querySelector(
'extensions-item-list');
var extensions = extensionsItemList.shadowRoot.querySelectorAll(
'extensions-item');
for (var i = 0; i < extensions.length; i += 1) {
var extensionItem = extensions[i].shadowRoot;
if (extensionItem.textContent.indexOf(extensionName) > -1) {
extensionItem.querySelector('#detailsButton').click();
}
}
'''
enable_incognito_mode_js_code = '''
var extensionsManager = document.querySelector('extensions-manager');
var extensionsDetailView = extensionsManager.shadowRoot.querySelector(
'extensions-detail-view');
var allowIncognitoRow = extensionsDetailView.shadowRoot.querySelector(
'#allow-incognito');
allowIncognitoRow.shadowRoot.querySelector('#crToggle').click();
'''
driver.execute_script(go_to_extension_js_code)
driver.execute_script(enable_incognito_mode_js_code)
Just remember to change var extensionName = 'TestRevolution'; line to your extension name.
If you are trying to enable the already installed extension in incodnito, then try the below code . It should work with chrome.
driver.get("chrome://extensions-frame");
WebElement checkbox = driver.findElement(By.xpath("//label[#class='incognito-control']/input[#type='checkbox']"));
if (!checkbox.isSelected()) {
checkbox.click();
}
I'm still newbie in coding, but I figured another method after looking in chrome's crisper.js at chrome://extensions/ .
First you need to know the extension ID. You can do it by making the id constant here, or using pako's method on obtaining the id's. For mine it's "lmpekldgmhemmmbllpdmafmlofflampm"
Then launch chrome with --incognito and addExtension, then execute the javascript to enable in incognito.
Example:
public class test2 {
static String dir = System.getProperty("user.dir");
static WebDriver driver;
static JavascriptExecutor js;
public static void main(String[] args) throws InterruptedException, IOException{
ChromeOptions options = new ChromeOptions();
options.addArguments("--incognito");
options.addExtensions(new File(dir + "\\randua.crx"));
System.setProperty("webdriver.chrome.driver",dir + "\\chromedriver73.exe");
driver = new ChromeDriver(options);
js = (JavascriptExecutor) driver;
String extID = "lmpekldgmhemmmbllpdmafmlofflampm";
driver.get("chrome://extensions-frame/");
new WebDriverWait(driver, 60).until(webDriver -> js.executeScript("return document.readyState").equals("complete"));
js.executeScript("chrome.developerPrivate.updateExtensionConfiguration({extensionId: \"" + extID + "\",incognitoAccess: true})");
Thread.sleep(1000);
}
}
Hope it helps :)

Google Chrome - download attribute of anchor tags

I've an extension which saves some files to the downloads folder. The code below is just for testing
//This lies in the background page of my extension
function fileTest(name) {
var a = document.createElement('a');
a.href = 'data:text/plain;base64,SGVsbG8gV29ybGQh'; //Hello World!
a.download = name + '.txt';
a.onclick = function (e) {console.log('[TEST] ' + name);return true;};
a.click();
}
window.onload = function() {
fileTest('test1');
fileTest('test12');
fileTest('test123');
}
only the first file "test1.txt" is saved to the disk, although the output of the console shows that there was 3 clicks
[TEST] test1
[TEST] test12
[TEST] test123
Is this an intentional limitation by the browser ? or there's something wrong with the code ?
When I run your code in a regular browsing session, I get a slide out notification (at the top of the window) that says
This site is attempting to download multiple files. Do you want to allow this?
So, yes, it is a security limitation of the browser to restrict downloads that are not user-initiated. You probably don't see the notification because the action is being performed by your background page.
The limitation seems to be one download per user action as demonstrated in this variant of your code:
window.onclick = function() {
fileTest('test1');
}
This will allow unlimited downloads, but only one download per click event.

Chrome extensions: redirect in context menu

i have following code in my background.html.
var child1 = chrome.contextMenus.create( {"title": "test '%s'", "onclick": callLocalhost, "contexts":"selection"]});
function callLocalhost(obj){
window.location.href="http://localhost/"+obj.selectionText;
}
function is called but redirect doesn't work.
window.location.href contains the extension id and is not editable.
Is there a workaround?
Your code executes in the background window of your extension, the window object is the background window. To change tab location you need to execute code in the tab. You do it with the chrome.tabs.executeScript method. Something like this (untested):
function callLocalhost(obj, tab){
chrome.tabs.executeScript(tab.id, {
code: "var obj = " + JSON.stringify(obj) + ";" +
"window.location.href='http://localhost/'+obj.selectionText;"
});
}
Note that the content script (the script injected into the tab) cannot access variables of the background window directly. This is why I put obj directly into the code to be executed in the tab.
You can use the chrome.tabs.update API to change the location:
function callLocalhost(info, tab) {
chrome.tabs.update(
info.tab,
{"url": obj."http://localhost/" + info.selectionText});
}
Unlike the chrome.tabs.executeScript approach proposed by Wladimir, this doesn't require script execution permissions in the tab.
Use the chrome.tabs.create API.