Skip to content
This repository was archived by the owner on Jul 29, 2024. It is now read-only.
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
54 changes: 54 additions & 0 deletions lib/clientsidescripts.js
Original file line number Diff line number Diff line change
Expand Up @@ -704,6 +704,60 @@ functions.findByCssContainingText = function(cssSelector, searchText, using) {
return matches;
};

/**
* Find an element throughout the Shadow DOM by extended css selector.
* In order to go into the shadow tree of the element, a special selector `::sr` is used.
* As compatible with both Shadow DOM v1 and prior specification, `::sr` will be ignored
* for the browser with no support of Shadow DOM, so the result will be same.
*
* Usage:
* element(by.shadowRoot('#outer #inner')) <=> $('#outer #inner')
* element(by.shadowRoot('#outer::sr #inner')) <=> $('#outer').shadowRoot.$('#inner')
* element.all(by.shadowRoot('#outer .inner')) <=> $$('#outer .inner')
* element.all(by.shadowRoot('#outer::sr .inner')) <=> $$('#outer').shadowRoot.$$('.inner')
* outer.element(by.shadowRoot('#inner')) <=> outer.$('#inner')
* outer.element(by.shadowRoot('::sr #inner')) <=> outer.shadowRoot.$('#inner')
* outer.all(by.shadowRoot('.inner')) <=> outer.$$('.inner')
* outer.all(by.shadowRoot('::sr .inner')) <=> outer.shadowRoot.$$('.inner')
*
* @param {string} selector An extended css selector, each `::sr` in which
* will go into the shadow root of the element.
* @param {Element} using The scope of the search.
*
* @return {Array.<Element>} The matching elements.
*/
functions.findByShadowRoot = function(selector, using) {
var selectors = selector.split('::sr');
if (selectors.length === 0) {
return [];
}

var shadowDomInUse = (document.head.createShadowRoot || document.head.attachShadow);
var getShadowRoot = function (el) {
return ((el && shadowDomInUse) ? el.shadowRoot : el);
};
var findAllMatches = function (selector /*string*/, targets /*array*/, firstTry /*boolean*/) {
var scope, i, matches = [];
for (i = 0; i < targets.length; ++i) {
scope = (firstTry) ? targets[i] : getShadowRoot(targets[i]);
if (scope) {
if (selector === '') {
matches.push(scope);
} else {
Array.prototype.push.apply(matches, scope.querySelectorAll(selector));
}
}
}
return matches;
};

var matches = findAllMatches(selectors.shift().trim(), [using || document], true);
while (selectors.length > 0 && matches.length > 0) {
matches = findAllMatches(selectors.shift().trim(), matches, false);
}
return matches;
};

/**
* Tests whether the angular global variable is present on a page. Retries
* in case the page is just loading slowly.
Expand Down
37 changes: 37 additions & 0 deletions lib/locators.ts
Original file line number Diff line number Diff line change
Expand Up @@ -491,4 +491,41 @@ export class ProtractorBy extends WebdriverBy {
// When that is supported, switch it here.
return By.css('* /deep/ ' + selector);
};

/**
* Find an element throughout the Shadow DOM by extended css selector.
* In order to go into the shadow tree of the element, use a special selector `::sr`.
* As compatible with both Shadow DOM v1 and prior specification, `::sr` will be ignored
* for the browser with no support of Shadow DOM, so the result will be same.
*
* @alias by.shadowRoot(selector)
* @view
* <div>
* <span id="outerspan">
* <"shadow tree">
* <span id="span1"></span>
* <"shadow tree">
* <span id="span2"></span>
* </>
* </>
* </div>
* @example
* var span1 = element(by.shadowRoot('#outerspan::sr #span1'));
* var span2 = element(by.shadowRoot('#outerspan::sr #span1::sr #span2'));
*
* @param {string} selector an extended css selector, each `::sr` in which will go into the shadow root of the element
* @returns {Locator} location strategy
*/
shadowRoot(selector: string): Locator {
return {
findElementsOverride: (driver: WebDriver, using: WebElement, rootSelector: string):
wdpromise.Promise<WebElement[]> => {
return driver.findElements(
By.js(clientSideScripts.findByShadowRoot, selector, using, rootSelector));
},
toString: (): string => {
return 'by.shadowRoot("' + selector + '")';
}
};
};
}
21 changes: 21 additions & 0 deletions spec/basic/locators_spec.js
Original file line number Diff line number Diff line change
Expand Up @@ -414,6 +414,27 @@ describe('locators', function() {
});
});

describe('by shadowRoot', function() {
beforeEach(function() {
browser.get('index.html#/shadow');
});

it('should find items inside the shadow DOM', function() {
var parent = element(by.shadowRoot('#innerDiv::sr #parentDiv'));
var parentHeading = parent.element(by.shadowRoot('.parentshadowheading'));
var youngerChildHeading = parent.element(by.shadowRoot('::sr .youngershadowheading'));

expect(parentHeading.isPresent()).toBe(true);
expect(youngerChildHeading.isPresent()).toBe(true);

expect(parentHeading.getText()).toEqual('Parent');
expect(youngerChildHeading.getText()).toEqual('Younger Child');

expect(element(by.shadowRoot('.originalcontent')).getText())
.toEqual('original content');
});
});

it('should determine if an element is present', function() {
expect(browser.isElementPresent(by.binding('greet'))).toBe(true);
expect(browser.isElementPresent(by.binding('nopenopenope'))).toBe(false);
Expand Down