From e193d19853b55c83b88caaa17207da5e447c536c Mon Sep 17 00:00:00 2001 From: Priyansh Garg Date: Wed, 24 Jan 2024 15:15:02 +0530 Subject: [PATCH] =?UTF-8?q?Fixed=20#3989=20=E2=80=93=20Unable=20to=20fetch?= =?UTF-8?q?=20elements=20with=20reference=20to=20a=20ShadowRoot?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- lib/api/web-element/scoped-element.js | 14 +- lib/api/web-element/scoped-elements.js | 11 +- .../commands/web-element/testShadowFind.js | 190 +++++++++++ .../commands/web-element/testShadowFindAll.js | 316 ++++++++++++++++++ 4 files changed, 524 insertions(+), 7 deletions(-) create mode 100644 test/src/api/commands/web-element/testShadowFind.js create mode 100644 test/src/api/commands/web-element/testShadowFindAll.js diff --git a/lib/api/web-element/scoped-element.js b/lib/api/web-element/scoped-element.js index b127f11041..69cd42123f 100644 --- a/lib/api/web-element/scoped-element.js +++ b/lib/api/web-element/scoped-element.js @@ -89,9 +89,15 @@ class ScopedWebElement { const createLocateElement = () => { return parentElement && parentElement.webElement ? function(locator) { return new Condition('for at least one element to be located ' + locator, function (driver) { - return parentElement.webElement.findElements(locator).then(function (elements) { - return elements.length > 0 ? elements : null; - }); + return parentElement.webElement + .then(function (webElement) { + // also takes into consideration if `webElement` + // resolves to a shadow root. + return webElement.findElements(locator); + }) + .then(function (elements) { + return elements.length > 0 ? elements : null; + }); }); } : until.elementsLocated; }; @@ -111,7 +117,7 @@ class ScopedWebElement { while (allElements.length > 0) { const nextElement = allElements.shift(); if (result) { - parentElement = {webElement: result}; + parentElement = {webElement: Promise.resolve(result)}; } const {condition, index} = ScopedElementLocator.create(nextElement, this.nightwatchInstance); diff --git a/lib/api/web-element/scoped-elements.js b/lib/api/web-element/scoped-elements.js index 615725ae46..c5e0cc492b 100644 --- a/lib/api/web-element/scoped-elements.js +++ b/lib/api/web-element/scoped-elements.js @@ -56,9 +56,14 @@ class ScopedElements { } async findElements({parentElement, selector, timeout, retryInterval}) { - const webElements = parentElement && parentElement.webElement - ? await parentElement.webElement.findElements(selector) - : await this.nightwatchInstance.transport.driver.wait(until.elementsLocated(selector), timeout, null, retryInterval); + let webElements; + + if (parentElement && parentElement.webElement) { + const parentWebElement = await parentElement.webElement; + webElements = await parentWebElement.findElements(selector); + } else { + webElements = await this.nightwatchInstance.transport.driver.wait(until.elementsLocated(selector), timeout, null, retryInterval); + } return webElements; } diff --git a/test/src/api/commands/web-element/testShadowFind.js b/test/src/api/commands/web-element/testShadowFind.js new file mode 100644 index 0000000000..403387faa0 --- /dev/null +++ b/test/src/api/commands/web-element/testShadowFind.js @@ -0,0 +1,190 @@ +const assert = require('assert'); +const {WebElement} = require('selenium-webdriver'); +const {ShadowRoot} = require('selenium-webdriver/lib/webdriver.js'); +const MockServer = require('../../../../lib/mockserver.js'); +const CommandGlobals = require('../../../../lib/globals/commands-w3c.js'); +const common = require('../../../../common.js'); +const Element = common.require('element/index.js'); + +describe('element().shadow().find() commands', function () { + before(function (done) { + CommandGlobals.beforeEach.call(this, done); + }); + + after(function (done) { + CommandGlobals.afterEach.call(this, done); + }); + + it('test .element().shadow().find()', async function() { + MockServer + .addMock({ + url: '/session/13521-10219-202/element/0/shadow', + method: 'GET', + response: JSON.stringify({ + value: {'shadow-6066-11e4-a52e-4f735466cecf': '9'} + }) + }, true) + .addMock({ + url: '/session/13521-10219-202/shadow/9/elements', + postdata: { + using: 'css selector', + value: '#helpBtn' + }, + method: 'POST', + response: JSON.stringify({ + value: [{'element-6066-11e4-a52e-4f735466cecf': '5'}] + }) + }, true); + + const shadowRootEl = this.client.api.element('#signupSection').getShadowRoot(); + assert.strictEqual(await shadowRootEl instanceof ShadowRoot, true); + assert.strictEqual(await shadowRootEl.getId(), '9'); + + const helpBtnElement = shadowRootEl.find('#helpBtn'); + assert.strictEqual(helpBtnElement instanceof Element, true); + assert.strictEqual(await helpBtnElement.getId(), '5'); + + const helpBtnWebElement = await helpBtnElement; + assert.strictEqual(helpBtnWebElement instanceof WebElement, true); + assert.strictEqual(await helpBtnWebElement.getId(), '5'); + }); + + it('test .element().shadow().get()', async function() { + MockServer + .addMock({ + url: '/session/13521-10219-202/element/0/shadow', + method: 'GET', + response: JSON.stringify({ + value: {'shadow-6066-11e4-a52e-4f735466cecf': '9'} + }) + }, true) + .addMock({ + url: '/session/13521-10219-202/shadow/9/elements', + postdata: { + using: 'css selector', + value: '#helpBtn' + }, + method: 'POST', + response: JSON.stringify({ + value: [{'element-6066-11e4-a52e-4f735466cecf': '5'}] + }) + }, true); + + const shadowRootEl = this.client.api.element('#signupSection').getShadowRoot(); + assert.strictEqual(await shadowRootEl instanceof ShadowRoot, true); + assert.strictEqual(await shadowRootEl.getId(), '9'); + + const helpBtnElement = shadowRootEl.get('#helpBtn'); + assert.strictEqual(helpBtnElement instanceof Element, true); + assert.strictEqual(await helpBtnElement.getId(), '5'); + + const helpBtnWebElement = await helpBtnElement; + assert.strictEqual(helpBtnWebElement instanceof WebElement, true); + assert.strictEqual(await helpBtnWebElement.getId(), '5'); + }); + + it('test .element().shadow().findElement()', async function() { + MockServer + .addMock({ + url: '/session/13521-10219-202/element/0/shadow', + method: 'GET', + response: JSON.stringify({ + value: {'shadow-6066-11e4-a52e-4f735466cecf': '9'} + }) + }, true) + .addMock({ + url: '/session/13521-10219-202/shadow/9/elements', + postdata: { + using: 'css selector', + value: '#helpBtn' + }, + method: 'POST', + response: JSON.stringify({ + value: [{'element-6066-11e4-a52e-4f735466cecf': '5'}] + }) + }, true); + + const shadowRootEl = this.client.api.element('#signupSection').getShadowRoot(); + assert.strictEqual(await shadowRootEl instanceof ShadowRoot, true); + assert.strictEqual(await shadowRootEl.getId(), '9'); + + const helpBtnElement = shadowRootEl.findElement('#helpBtn'); + assert.strictEqual(helpBtnElement instanceof Element, true); + assert.strictEqual(await helpBtnElement.getId(), '5'); + + const helpBtnWebElement = await helpBtnElement; + assert.strictEqual(helpBtnWebElement instanceof WebElement, true); + assert.strictEqual(await helpBtnWebElement.getId(), '5'); + }); + + it('test .element().shadow().find(selectorObject)', async function() { + MockServer + .addMock({ + url: '/session/13521-10219-202/element/0/shadow', + method: 'GET', + response: JSON.stringify({ + value: {'shadow-6066-11e4-a52e-4f735466cecf': '9'} + }) + }, true) + .addMock({ + url: '/session/13521-10219-202/shadow/9/elements', + postdata: { + using: 'xpath', + value: '//*[id="helpBtn"]' + }, + method: 'POST', + response: JSON.stringify({ + value: [{'element-6066-11e4-a52e-4f735466cecf': '2'}] + }) + }, true); + + const shadowRootEl = this.client.api.element('#signupSection').getShadowRoot(); + assert.strictEqual(await shadowRootEl instanceof ShadowRoot, true); + assert.strictEqual(await shadowRootEl.getId(), '9'); + + const helpBtnElement = shadowRootEl.find({ + locateStrategy: 'xpath', selector: '//*[id="helpBtn"]'}); + assert.strictEqual(helpBtnElement instanceof Element, true); + assert.strictEqual(await helpBtnElement.getId(), '2'); + + const helpBtnWebElement = await helpBtnElement; + assert.strictEqual(helpBtnWebElement instanceof WebElement, true); + assert.strictEqual(await helpBtnWebElement.getId(), '2'); + }); + + it('test .element().shadow().find().getText()', async function() { + MockServer + .addMock({ + url: '/session/13521-10219-202/element/0/shadow', + method: 'GET', + response: JSON.stringify({ + value: {'shadow-6066-11e4-a52e-4f735466cecf': '9'} + }) + }, true) + .addMock({ + url: '/session/13521-10219-202/shadow/9/elements', + postdata: { + using: 'css selector', + value: '#helpBtn' + }, + method: 'POST', + response: JSON.stringify({ + value: [{'element-6066-11e4-a52e-4f735466cecf': '55'}] + }) + }, true) + .addMock({ + url: '/session/13521-10219-202/element/55/text', + method: 'GET', + response: JSON.stringify({ + value: 'Help' + }) + }, true); + + const shadowRootEl = this.client.api.element('#signupSection').getShadowRoot(); + const helpBtnElement = shadowRootEl.find('#helpBtn'); + const helpBtnTextPromise = helpBtnElement.getText(); + + assert.strictEqual(typeof helpBtnTextPromise.then, 'function'); + assert.strictEqual(await helpBtnTextPromise, 'Help'); + }); +}); diff --git a/test/src/api/commands/web-element/testShadowFindAll.js b/test/src/api/commands/web-element/testShadowFindAll.js new file mode 100644 index 0000000000..f115106e4d --- /dev/null +++ b/test/src/api/commands/web-element/testShadowFindAll.js @@ -0,0 +1,316 @@ +const assert = require('assert'); +const {WebElement} = require('selenium-webdriver'); +const {ShadowRoot} = require('selenium-webdriver/lib/webdriver.js'); +const MockServer = require('../../../../lib/mockserver.js'); +const CommandGlobals = require('../../../../lib/globals/commands-w3c.js'); +const common = require('../../../../common.js'); +const Element = common.require('element/index.js'); + +describe('element().shadow().findAll() commands', function() { + before(function(done) { + CommandGlobals.beforeEach.call(this, done); + }); + + after(function(done) { + CommandGlobals.afterEach.call(this, done); + }); + + it('test .element().shadow().findAll()', async function() { + MockServer + .addMock({ + url: '/session/13521-10219-202/element/0/shadow', + method: 'GET', + response: JSON.stringify({ + value: {'shadow-6066-11e4-a52e-4f735466cecf': '9'} + }) + }, true) + .addMock({ + url: '/session/13521-10219-202/shadow/9/elements', + postdata: { + using: 'css selector', + value: '.btn' + }, + method: 'POST', + response: JSON.stringify({ + value: [ + {'element-6066-11e4-a52e-4f735466cecf': '5'}, + {'element-6066-11e4-a52e-4f735466cecf': '6'}, + {'element-6066-11e4-a52e-4f735466cecf': '7'} + ] + }) + }, true); + + const shadowRootEl = this.client.api.element('#signupSection').getShadowRoot(); + assert.strictEqual(await shadowRootEl instanceof ShadowRoot, true); + assert.strictEqual(await shadowRootEl.getId(), '9'); + + const btnElements = shadowRootEl.findAll('.btn'); + assert.strictEqual(btnElements instanceof Element, false); + assert.strictEqual(typeof btnElements.find, 'undefined'); + assert.strictEqual(typeof btnElements.click, 'undefined'); + + assert.strictEqual(btnElements instanceof Promise, false); + assert.strictEqual(typeof btnElements.then, 'function'); + assert.strictEqual(typeof btnElements.nth, 'function'); + assert.strictEqual(typeof btnElements.count, 'function'); + + const btnWEbElements = await btnElements; + assert.strictEqual(btnWEbElements.length, 3); + assert.strictEqual(btnWEbElements[0] instanceof WebElement, true); + assert.strictEqual(await btnWEbElements[0].getId(), '5'); + assert.strictEqual(await btnElements.nth(0).getId(), '5'); + }); + + it('test .element().shadow().getAll()', async function() { + MockServer + .addMock({ + url: '/session/13521-10219-202/element/0/shadow', + method: 'GET', + response: JSON.stringify({ + value: {'shadow-6066-11e4-a52e-4f735466cecf': '9'} + }) + }, true) + .addMock({ + url: '/session/13521-10219-202/shadow/9/elements', + postdata: { + using: 'css selector', + value: '.btn' + }, + method: 'POST', + response: JSON.stringify({ + value: [ + {'element-6066-11e4-a52e-4f735466cecf': '5'}, + {'element-6066-11e4-a52e-4f735466cecf': '6'}, + {'element-6066-11e4-a52e-4f735466cecf': '7'} + ] + }) + }, true); + + const shadowRootEl = this.client.api.element('#signupSection').getShadowRoot(); + assert.strictEqual(await shadowRootEl instanceof ShadowRoot, true); + assert.strictEqual(await shadowRootEl.getId(), '9'); + + const btnElements = shadowRootEl.getAll('.btn'); + assert.strictEqual(btnElements instanceof Element, false); + assert.strictEqual(typeof btnElements.find, 'undefined'); + assert.strictEqual(typeof btnElements.click, 'undefined'); + + assert.strictEqual(btnElements instanceof Promise, false); + assert.strictEqual(typeof btnElements.then, 'function'); + assert.strictEqual(typeof btnElements.nth, 'function'); + assert.strictEqual(typeof btnElements.count, 'function'); + + const btnWEbElements = await btnElements; + assert.strictEqual(btnWEbElements.length, 3); + assert.strictEqual(btnWEbElements[0] instanceof WebElement, true); + assert.strictEqual(await btnWEbElements[0].getId(), '5'); + assert.strictEqual(await btnElements.nth(0).getId(), '5'); + }); + + it('test .element().shadow().findElements()', async function() { + MockServer + .addMock({ + url: '/session/13521-10219-202/element/0/shadow', + method: 'GET', + response: JSON.stringify({ + value: {'shadow-6066-11e4-a52e-4f735466cecf': '9'} + }) + }, true) + .addMock({ + url: '/session/13521-10219-202/shadow/9/elements', + postdata: { + using: 'css selector', + value: '.btn' + }, + method: 'POST', + response: JSON.stringify({ + value: [ + {'element-6066-11e4-a52e-4f735466cecf': '5'}, + {'element-6066-11e4-a52e-4f735466cecf': '6'}, + {'element-6066-11e4-a52e-4f735466cecf': '7'} + ] + }) + }, true); + + const shadowRootEl = this.client.api.element('#signupSection').getShadowRoot(); + assert.strictEqual(await shadowRootEl instanceof ShadowRoot, true); + assert.strictEqual(await shadowRootEl.getId(), '9'); + + const btnElements = shadowRootEl.findElements('.btn'); + assert.strictEqual(btnElements instanceof Element, false); + assert.strictEqual(typeof btnElements.find, 'undefined'); + assert.strictEqual(typeof btnElements.click, 'undefined'); + + assert.strictEqual(btnElements instanceof Promise, false); + assert.strictEqual(typeof btnElements.then, 'function'); + assert.strictEqual(typeof btnElements.nth, 'function'); + assert.strictEqual(typeof btnElements.count, 'function'); + + const btnWEbElements = await btnElements; + assert.strictEqual(btnWEbElements.length, 3); + assert.strictEqual(btnWEbElements[0] instanceof WebElement, true); + assert.strictEqual(await btnWEbElements[0].getId(), '5'); + assert.strictEqual(await btnElements.nth(0).getId(), '5'); + }); + + it('test .element().shadow().findAll(selectorObject)', async function() { + MockServer + .addMock({ + url: '/session/13521-10219-202/element/0/shadow', + method: 'GET', + response: JSON.stringify({ + value: {'shadow-6066-11e4-a52e-4f735466cecf': '9'} + }) + }, true) + .addMock({ + url: '/session/13521-10219-202/shadow/9/elements', + postdata: { + using: 'xpath', + value: '//*[id="helpBtn"]' + }, + method: 'POST', + response: JSON.stringify({ + value: [ + {'element-6066-11e4-a52e-4f735466cecf': '4'}, + {'element-6066-11e4-a52e-4f735466cecf': '5'} + ] + }) + }, true); + + const shadowRootEl = this.client.api.element('#signupSection').getShadowRoot(); + assert.strictEqual(await shadowRootEl instanceof ShadowRoot, true); + assert.strictEqual(await shadowRootEl.getId(), '9'); + + const helpBtnElements = shadowRootEl.findAll({ + locateStrategy: 'xpath', selector: '//*[id="helpBtn"]' + }); + assert.strictEqual(helpBtnElements instanceof Element, false); + assert.strictEqual(typeof helpBtnElements.find, 'undefined'); + assert.strictEqual(typeof helpBtnElements.click, 'undefined'); + + assert.strictEqual(helpBtnElements instanceof Promise, false); + assert.strictEqual(typeof helpBtnElements.then, 'function'); + assert.strictEqual(typeof helpBtnElements.nth, 'function'); + assert.strictEqual(typeof helpBtnElements.count, 'function'); + + const btnWEbElements = await helpBtnElements; + assert.strictEqual(btnWEbElements.length, 2); + assert.strictEqual(btnWEbElements[0] instanceof WebElement, true); + assert.strictEqual(await btnWEbElements[0].getId(), '4'); + assert.strictEqual(await helpBtnElements.nth(0).getId(), '4'); + }); + + it('test .element().shadow().findAll().count()', async function() { + MockServer + .addMock({ + url: '/session/13521-10219-202/element/0/shadow', + method: 'GET', + response: JSON.stringify({ + value: {'shadow-6066-11e4-a52e-4f735466cecf': '9'} + }) + }, true) + .addMock({ + url: '/session/13521-10219-202/shadow/9/elements', + postdata: { + using: 'css selector', + value: '.btn' + }, + method: 'POST', + response: JSON.stringify({ + value: [ + {'element-6066-11e4-a52e-4f735466cecf': '5'}, + {'element-6066-11e4-a52e-4f735466cecf': '6'}, + {'element-6066-11e4-a52e-4f735466cecf': '7'} + ] + }) + }, true); + + const shadowRootEl = this.client.api.element('#signupSection').getShadowRoot(); + assert.strictEqual(await shadowRootEl instanceof ShadowRoot, true); + assert.strictEqual(await shadowRootEl.getId(), '9'); + + const resultPromise = shadowRootEl.findAll('.btn').count(); + assert.strictEqual(resultPromise instanceof Element, false); + assert.strictEqual(typeof resultPromise.find, 'undefined'); + + assert.strictEqual(resultPromise instanceof Promise, false); + assert.strictEqual(typeof resultPromise.then, 'function'); + assert.strictEqual(typeof resultPromise.value, 'object'); + assert.strictEqual(typeof resultPromise.assert, 'object'); + + const result = await resultPromise; + assert.strictEqual(result instanceof WebElement, false); + assert.strictEqual(result, 3); + + assert.strictEqual(await resultPromise.value, 3); + assert.strictEqual(await resultPromise.assert.equals(3), 3); + assert.strictEqual(await resultPromise.assert.not.equals(4), 3); + }); + + it('test .element().findAll().nth()', async function() { + MockServer + .addMock({ + url: '/session/13521-10219-202/element/0/shadow', + method: 'GET', + response: JSON.stringify({ + value: {'shadow-6066-11e4-a52e-4f735466cecf': '9'} + }) + }, true) + .addMock({ + url: '/session/13521-10219-202/shadow/9/elements', + postdata: { + using: 'css selector', + value: '.btn' + }, + method: 'POST', + response: JSON.stringify({ + value: [ + {'element-6066-11e4-a52e-4f735466cecf': '5'}, + {'element-6066-11e4-a52e-4f735466cecf': '6'}, + {'element-6066-11e4-a52e-4f735466cecf': '7'} + ] + }) + }, true) + .addMock({ + url: '/session/13521-10219-202/element/6/elements', + postdata: { + using: 'css selector', + value: 'span' + }, + method: 'POST', + response: JSON.stringify({ + value: [ + {'element-6066-11e4-a52e-4f735466cecf': '9'} + ] + }) + }, true) + .addMock({ + url: '/session/13521-10219-202/element/6/property/role', + method: 'GET', + response: JSON.stringify({ + value: 'button' + }) + }, true); + + const shadowRootEl = this.client.api.element('#signupSection').getShadowRoot(); + assert.strictEqual(await shadowRootEl instanceof ShadowRoot, true); + assert.strictEqual(await shadowRootEl.getId(), '9'); + + const btnElement = shadowRootEl.findAll('.btn').nth(1); + assert.strictEqual(btnElement instanceof Element, false); + assert.strictEqual(btnElement instanceof Promise, true); + assert.strictEqual(typeof btnElement.find, 'function'); + assert.strictEqual(typeof btnElement.getValue, 'function'); + + const btnWebElement = await btnElement; + assert.strictEqual(btnWebElement instanceof WebElement, true); + assert.strictEqual(await btnWebElement.getId(), '6'); + + const btnSpanElement = btnElement.find('span'); + assert.strictEqual(await btnSpanElement.getId(), '9'); + + const btnElementProperty = await btnElement.getProperty('role'); + assert.strictEqual(btnElementProperty, 'button'); + }); +}); +