Skip to content

allow using shadow dom for table row element #764

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Merged
merged 1 commit into from
Jul 7, 2020
Merged
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
15 changes: 10 additions & 5 deletions webdriver-ts/src/common.ts
Original file line number Diff line number Diff line change
Expand Up @@ -63,6 +63,7 @@ export interface FrameworkData {
uri: string;
keyed: boolean;
useShadowRoot: boolean;
useRowShadowRoot: boolean;
issues: number[];
}

Expand All @@ -86,7 +87,7 @@ export interface FrameworkId {

abstract class FrameworkVersionInformationValid implements FrameworkId {
public url: string;
constructor(public keyedType: KeyedType, public directory: string, customURL: string|undefined, public useShadowRoot: boolean, public issues: number[]) {
constructor(public keyedType: KeyedType, public directory: string, customURL: string|undefined, public useShadowRoot: boolean, public useRowShadowRoot: boolean, public issues: number[]) {
this.keyedType = keyedType;
this.directory = directory;
this.url = 'frameworks/'+keyedType+'/'+directory + (customURL ? customURL : '');
Expand All @@ -95,21 +96,22 @@ abstract class FrameworkVersionInformationValid implements FrameworkId {

export class FrameworkVersionInformationDynamic extends FrameworkVersionInformationValid {
constructor(keyedType: KeyedType, directory: string, public packageNames: string[],
customURL: string|undefined, useShadowRoot: boolean = false, issues: number[]) {
super(keyedType, directory, customURL, useShadowRoot, issues);
customURL: string|undefined, useShadowRoot: boolean = false, useRowShadowRoot: boolean = false, issues: number[]) {
super(keyedType, directory, customURL, useShadowRoot, useRowShadowRoot, issues);
}
}

export class FrameworkVersionInformationStatic extends FrameworkVersionInformationValid {
constructor(keyedType: KeyedType, directory: string, public frameworkVersion: string, customURL: string|undefined, useShadowRoot: boolean = false, issues: number[]) {
super(keyedType, directory, customURL, useShadowRoot, issues);
constructor(keyedType: KeyedType, directory: string, public frameworkVersion: string, customURL: string|undefined, useShadowRoot: boolean = false, useRowShadowRoot: boolean = false, issues: number[]) {
super(keyedType, directory, customURL, useShadowRoot, useRowShadowRoot, issues);
}
getFrameworkData(): FrameworkData {
return {name: this.directory,
fullNameWithKeyedAndVersion: this.directory+(this.frameworkVersion ? '-v'+this.frameworkVersion : '')+'-'+this.keyedType,
uri: this.url,
keyed: this.keyedType === 'keyed',
useShadowRoot: this.useShadowRoot,
useRowShadowRoot: this.useRowShadowRoot,
issues: this.issues
}
}
Expand Down Expand Up @@ -164,13 +166,15 @@ async function loadFrameworkInfo(pathInFrameworksDir: string): Promise<Framework
packageJSON['js-framework-benchmark']['frameworkVersionFromPackage'].split(':'),
packageJSON['js-framework-benchmark']['customURL'],
packageJSON['js-framework-benchmark']['useShadowRoot'],
packageJSON['js-framework-benchmark']['useRowShadowRoot'],
packageJSON['js-framework-benchmark']['issues']
);
} else if (typeof packageJSON['js-framework-benchmark']['frameworkVersion'] === 'string') {
return new FrameworkVersionInformationStatic(keyedType, directory,
packageJSON['js-framework-benchmark']['frameworkVersion'],
packageJSON['js-framework-benchmark']['customURL'],
packageJSON['js-framework-benchmark']['useShadowRoot'],
packageJSON['js-framework-benchmark']['useRowShadowRoot'],
packageJSON['js-framework-benchmark']['issues']
);
} else {
Expand Down Expand Up @@ -219,6 +223,7 @@ export class PackageVersionInformationResult {
uri: this.framework.url,
keyed: this.framework.keyedType === 'keyed',
useShadowRoot: this.framework.useShadowRoot,
useRowShadowRoot: this.framework.useRowShadowRoot,
issues: this.framework.issues
}
}
Expand Down
4 changes: 3 additions & 1 deletion webdriver-ts/src/forkedBenchmarkRunner.ts
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
import {WebDriver, logging} from 'selenium-webdriver'
import {BenchmarkType, Benchmark, benchmarks, fileName, LighthouseData} from './benchmarks'
import {setUseShadowRoot, buildDriver} from './webdriverAccess'
import {setUseShadowRoot, buildDriver, setUseRowShadowRoot} from './webdriverAccess'

const lighthouse = require('lighthouse');
const chromeLauncher = require('chrome-launcher');
Expand Down Expand Up @@ -361,6 +361,7 @@ async function runCPUBenchmark(framework: FrameworkData, benchmark: Benchmark, b
driver = buildDriver(benchmarkOptions);
for (let i = 0; i <benchmarkOptions.batchSize; i++) {
setUseShadowRoot(framework.useShadowRoot);
setUseRowShadowRoot(framework.useRowShadowRoot);
await driver.get(`http://localhost:${benchmarkOptions.port}/${framework.uri}/index.html`);

// await (driver as any).sendDevToolsCommand('Network.enable');
Expand Down Expand Up @@ -418,6 +419,7 @@ async function runMemBenchmark(framework: FrameworkData, benchmark: Benchmark, b
try {
driver = buildDriver(benchmarkOptions);
setUseShadowRoot(framework.useShadowRoot);
setUseRowShadowRoot(framework.useRowShadowRoot);
await driver.get(`http://localhost:${benchmarkOptions.port}/${framework.uri}/index.html`);

await driver.executeScript("console.timeStamp('initBenchmark')");
Expand Down
21 changes: 10 additions & 11 deletions webdriver-ts/src/isKeyed.ts
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
import * as yargs from 'yargs';
import {buildDriver, setUseShadowRoot, testTextContains, testTextNotContained, testClassContains, testElementLocatedByXpath, testElementNotLocatedByXPath, testElementLocatedById, clickElementById, clickElementByXPath, getTextByXPath, shadowRoot, findByXPath} from './webdriverAccess'
import {buildDriver, setUseShadowRoot, testTextContains, testTextNotContained, testClassContains, testElementLocatedByXpath, testElementNotLocatedByXPath, testElementLocatedById, clickElementById, clickElementByXPath, getTextByXPath, mainRoot, findByXPath, setUseRowShadowRoot} from './webdriverAccess'
import {config, FrameworkData, initializeFrameworks, BenchmarkOptions} from './common'
import { WebDriver, By, WebElement } from 'selenium-webdriver';
import * as R from 'ramda';
Expand Down Expand Up @@ -156,33 +156,32 @@ async function assertClassesContained(elem: WebElement, expectedClassNames: stri
}

export async function checkTRcorrect(driver: WebDriver, timeout = config.TIMEOUT): Promise<boolean> {
let elem = await shadowRoot(driver);
let tr = await findByXPath(elem, '//tbody/tr[1000]');
let tr = await findByXPath(driver, '//tbody/tr[1000]');
if (!await assertChildNodes(tr, [ 'td', 'td', 'a', 'td', 'a', 'span', 'td' ], "tr")) {
return false;
}

// first td
let td1 = await findByXPath(elem, '//tbody/tr[1000]/td[1]');
let td1 = await findByXPath(driver, '//tbody/tr[1000]/td[1]');
if (!await assertClassesContained(td1, ["col-md-1"], "first td")) {
return false;
}


// second td
let td2 = await findByXPath(elem, '//tbody/tr[1000]/td[2]');
let td2 = await findByXPath(driver, '//tbody/tr[1000]/td[2]');
if (!await assertClassesContained(td2, ["col-md-4"], "second td")) {
return false;
}

// third td
let td3 = await findByXPath(elem, '//tbody/tr[1000]/td[3]');
let td3 = await findByXPath(driver, '//tbody/tr[1000]/td[3]');
if (!await assertClassesContained(td3, ["col-md-1"], "third td")) {
return false;
}

// span in third td
let span = await findByXPath(elem, '//tbody/tr[1000]/td[3]/a/span');
let span = await findByXPath(driver, '//tbody/tr[1000]/td[3]/a/span');
if (!await assertClassesContained(span, ["glyphicon","glyphicon-remove"], "span in a in third td")) {
return false;
}
Expand All @@ -194,7 +193,7 @@ export async function checkTRcorrect(driver: WebDriver, timeout = config.TIMEOUT


// fourth td
let td4 = await findByXPath(elem, '//tbody/tr[1000]/td[4]');
let td4 = await findByXPath(driver, '//tbody/tr[1000]/td[4]');
if (!await assertClassesContained(td4, ["col-md-6"], "fourth td")) {
return false;
}
Expand All @@ -204,8 +203,7 @@ export async function checkTRcorrect(driver: WebDriver, timeout = config.TIMEOUT
}

export async function getInnerHTML(driver: WebDriver, xpath: string, timeout = config.TIMEOUT): Promise<string> {
let elem = await shadowRoot(driver);
elem = await findByXPath(elem, xpath);
let elem = await findByXPath(driver, xpath);
return elem.getAttribute("innerHTML");
}

Expand All @@ -227,8 +225,9 @@ async function runBench(frameworkNames: string[]) {
for (let i=0;i<runFrameworks.length;i++) {
let driver = await buildDriver(benchmarkOptions);
try {
let framework = runFrameworks[i];
let framework: FrameworkData = runFrameworks[i];
setUseShadowRoot(framework.useShadowRoot);
setUseRowShadowRoot(framework.useRowShadowRoot);
await driver.get(`http://localhost:${config.PORT}/${framework.uri}/index.html`);
await testElementLocatedById(driver, "add");
await clickElementById(driver,'run');
Expand Down
69 changes: 43 additions & 26 deletions webdriver-ts/src/webdriverAccess.ts
Original file line number Diff line number Diff line change
Expand Up @@ -9,11 +9,16 @@ interface PathPart {
}

let useShadowRoot = false;
let useRowShadowRoot = false;

export function setUseShadowRoot(val: boolean) {
useShadowRoot = val;
}

export function setUseRowShadowRoot(val: boolean) {
useRowShadowRoot = val;
}

function convertPath(path: string): Array<PathPart> {
let parts = path.split(/\//).filter(v => !!v);
let res: Array<PathPart> = [];
Expand All @@ -35,16 +40,36 @@ function convertPath(path: string): Array<PathPart> {
return res;
}

async function shadowRoot(driver: WebDriver, selector: string): Promise<WebElement> {
const el = await driver.findElement(By.css(selector));
return driver.executeScript(`return arguments[0].shadowRoot`, el);
}

// Fake findByXPath for simple XPath expressions to allow usage with shadow dom
export async function findByXPath(node: WebElement, path: string): Promise<WebElement> {
export async function findByXPath(driver: WebDriver, path: string): Promise<WebElement> {
let root = useShadowRoot ? await shadowRoot(driver, 'main-element') : await driver.findElement(By.tagName("body"));
let paths = convertPath(path);
let n = node;
let n = root;
try {
for (let p of paths) {
// n = n.then(nd => nd.findElements(By.tagName(p.tagName))).then(elems => { // costly since it fetches all elements
let elems = await n.findElements(By.css(p.tagName+":nth-child("+(p.index)+")"));
if (elems==null || elems.length==0) { return null};
n = elems[0];
for (let p of paths) {
let elem;
if (useRowShadowRoot && p.tagName === 'tr') {
try {
const shadowHost = await shadowRoot(driver, `benchmark-row:nth-child(${p.index})`);
elem = await shadowHost.findElement(By.tagName('tr'));
if (elem === null) {
return null;
}
} catch {
return null;
}
} else {
let elems = await n.findElements(By.css(p.tagName+":nth-child("+(p.index)+")"));
if (elems==null || elems.length==0) { return null};
elem = elems[0];
}

n = elem;
}
} catch (e) {
//can happen for StaleElementReferenceError
Expand All @@ -70,8 +95,7 @@ export async function testTextContains(driver: WebDriver, xpath: string, text: s
return waitForCondition(driver)(`testTextContains ${xpath} ${text}`,
async function(driver) {
try {
let elem = await shadowRoot(driver);
elem = await findByXPath(elem, xpath);
let elem = await findByXPath(driver, xpath);
if (elem==null) return false;
let v = await elem.getText();
return v && v.indexOf(text)>-1;
Expand All @@ -85,8 +109,7 @@ export function testTextNotContained(driver: WebDriver, xpath: string, text: str
return waitForCondition(driver)(`testTextNotContained ${xpath} ${text}`,
async function(driver) {
try {
let elem = await shadowRoot(driver);
elem = await findByXPath(elem, xpath);
let elem = await findByXPath(driver, xpath);
if (elem==null) return false;
let v = await elem.getText();
return v && v.indexOf(text)==-1;
Expand All @@ -100,8 +123,7 @@ export function testClassContains(driver: WebDriver, xpath: string, text: string
return waitForCondition(driver)(`testClassContains ${xpath} ${text}`,
async function(driver) {
try {
let elem = await shadowRoot(driver);
elem = await findByXPath(elem, xpath);
let elem = await findByXPath(driver, xpath);
if (elem==null) return false;
let v = await elem.getAttribute("class");
return v && v.indexOf(text)>-1;
Expand All @@ -115,8 +137,7 @@ export function testElementLocatedByXpath(driver: WebDriver, xpath: string, time
return waitForCondition(driver)(`testElementLocatedByXpath ${xpath}`,
async function(driver) {
try {
let elem = await shadowRoot(driver);
elem = await findByXPath(elem, xpath);
let elem = await findByXPath(driver, xpath);
return elem ? true : false;
} catch(err) {
console.log("ignoring error in testElementLocatedByXpath for xpath = "+xpath,err.toString())
Expand All @@ -128,8 +149,7 @@ export function testElementNotLocatedByXPath(driver: WebDriver, xpath: string, t
return waitForCondition(driver)(`testElementNotLocatedByXPath ${xpath}`,
async function(driver) {
try {
let elem = await shadowRoot(driver);
elem = await findByXPath(elem, xpath);
let elem = await findByXPath(driver, xpath);
return elem ? false : true;
} catch(err) {
console.log("ignoring error in testElementNotLocatedByXPath for xpath = "+xpath,err.toString().split("\n")[0]);
Expand All @@ -141,7 +161,7 @@ export function testElementLocatedById(driver: WebDriver, id: string, timeout =
return waitForCondition(driver)(`testElementLocatedById ${id}`,
async function(driver) {
try {
let elem = await shadowRoot(driver);
let elem = await mainRoot(driver);
elem = await elem.findElement(By.id(id));
return true;
} catch(err) {
Expand All @@ -164,7 +184,7 @@ async function retry<T>(retryCount: number, driver: WebDriver, fun : (driver: W
// No idea how that can be explained
export function clickElementById(driver: WebDriver, id: string) {
return retry(5, driver, async function (driver) {
let elem = await shadowRoot(driver);
let elem = await mainRoot(driver);
elem = await elem.findElement(By.id(id));
await elem.click();
});
Expand All @@ -173,8 +193,7 @@ export function clickElementById(driver: WebDriver, id: string) {
export function clickElementByXPath(driver: WebDriver, xpath: string) {
return retry(5, driver, async function(driver, count) {
if (count>1 && config.LOG_DETAILS) console.log("clickElementByXPath ",xpath," attempt #",count);
let elem = await shadowRoot(driver);
elem = await findByXPath(elem, xpath);
let elem = await findByXPath(driver, xpath);
await elem.click();
});
// Stale element possible:
Expand All @@ -184,15 +203,13 @@ export function clickElementByXPath(driver: WebDriver, xpath: string) {
export async function getTextByXPath(driver: WebDriver, xpath: string): Promise<string> {
return await retry(5, driver, async function(driver, count) {
if (count>1 && config.LOG_DETAILS) console.log("getTextByXPath ",xpath," attempt #",count);
let elem = await shadowRoot(driver);
elem = await findByXPath(elem, xpath);
let elem = await findByXPath(driver, xpath);
return await elem.getText();
});
}

export async function shadowRoot(driver: WebDriver) : Promise<WebElement> {
return useShadowRoot ? await driver.executeScript('return document.querySelector("main-element").shadowRoot') as WebElement
: await driver.findElement(By.tagName("body"));
export async function mainRoot(driver: WebDriver) : Promise<WebElement> {
return useShadowRoot ? shadowRoot(driver, 'main-element') : driver.findElement(By.tagName("body"));
}

// node_modules\.bin\chromedriver.cmd --verbose --port=9998 --log-path=chromedriver.log
Expand Down