ui5-community / wdi5

official UI5 end-to-end test framework for UI5 web-apps. wdi5 = Webdriver.IO + UI5 Test API
https://ui5-community.github.io/wdi5/
Apache License 2.0
102 stars 43 forks source link

WDI5 does not work with chromium or without chromedriver #428

Closed QA-ler closed 1 year ago

QA-ler commented 1 year ago

Describe the bug WDI5 is unable to receive the webelement in following conditions:

  1. Remove chromedriver from services
  2. Use chromedriver service with chromium installed using browserName: "chromium" in wdio conf and set Environment Variables see chromedirver documentation DETECT_CHROMEDRIVER_VERSION=true INCLUDE_CHROMIUM=true
  3. As in step 2, but with Dockerfile and ENV DETECT_CHROMEDRIVER_VERSION true ENV INCLUDE_CHROMIUM true We use RUN apt-get update && \ apt-get install -y chromium to install chromium in docker

To Reproduce

  1. Checkout https://github.com/ui5-community/wdi5-workshop
  2. Start Webservice in Folder \ui5con2022\ui5-js-app npm run serve
  3. In \ui5con2022\ui5-js-app\wdio.conf.js comment out the Chromedriver service image
  4. In new Terminal in Folder ui5con2022\ui5-js-app run npm run wdi5
  5. Error in console occurs image

Error seems to appear in this line: image

_webelement is undefined image Any further interaction with this control fails as there is no webelement set

Expected behavior In order to use WDI5 with Chromium Either: WDI5 works completely without chromedriver Or: At least works with chromium and chromedriver service combined

dominikfeininger commented 1 year ago

Do other pure wdio tests which not use wdi5 like

it("should verify the browser title", async function () {
      const title = await browser.getTitle();
      expect(title).toEqual("xxx");
});

or

      const button = await this.browser.$('#some button id')
      await button.click()

work?

QA-ler commented 1 year ago

Yes, this works see image

Also getting Puppeteer or other expects on browser are working.

vobu commented 1 year ago

wdi5 definitely works with safari(driver), edge(driver), firefox (gecko driver) and chrome(driver). chromium should be useable via the devtools protocol. but isn‘t, see #404 and #168 - we seem to have a bug in wdi5 when run with the „plain“ devtools- instead of the webdriver-protocol. we‘ll try to tackle this as soon as time permits…but given that you‘re obviously really deep into debugging wdi5 already @QA-ler, any chance that you could go down the rabbit hole further and help hunt down the culprit 🐛 ?!? 😸

QA-ler commented 1 year ago

wdi5 definitely works with safari(driver), edge(driver), firefox (gecko driver) and chrome(driver). chromium should be useable via the devtools protocol. but isn‘t, see #404 and #168 - we seem to have a bug in wdi5 when run with the „plain“ devtools- instead of the webdriver-protocol. we‘ll try to tackle this as soon as time permits…but given that you‘re obviously really deep into debugging wdi5 already @QA-ler, any chance that you could go down the rabbit hole further and help hunt down the culprit 🐛 ?!? 😸

@vobu Challange accepted ^^

If I find anything useful, I will share it here.

QA-ler commented 1 year ago

@vobu @dominikfeininger What i found out yet is that when chromedriver service is not used it uses the puppeteer evaluate function which behaves differently. See image

Like mentioned in webdriver documentation https://webdriver.io/docs/api/browser/execute it is intended to return a webelement if possible. While puppeteer(devtools protocol) seems to have problems returning complex data in general.

Did not yet find a solution how to archive the same behaviour with devtools protocol.

Do one of you have an idea?

QA-ler commented 1 year ago

@dominikfeininger @vobu I'v now tried to use browser.execute function directly in getControl function as fallback. image

But as you see the element id looks completely different. (This happens when chromedriver service is inactive)

At the moment further interactions still fail. Maybe because of different element reference but I'm not sure about this.

Any ideas from your side?

QA-ler commented 1 year ago

@dominikfeininger @vobu

The problem I had was problem with internal code. Futher interactions now work. The above mentioned code fixes the problem.

I'v tried to also fix this in your typescript project but was unable to push with 403(Forbidden). Can I somehow commit my changes? Otherwise, you need to review this code manually. (Haven't worked with TypeScript in a while, so I apologize for any stupid code that was added^^)

Code:

 private async _getControl(controlSelector = this._controlSelector) {
      // check whether we have a "by id regex" locator request
      if (controlSelector.selector.id && typeof controlSelector.selector.id === "object") {
          // make it a string for serializing into browser-scope and
          // further processing there
          controlSelector.selector.id = controlSelector.selector.id.toString()
      }

      // check whether we have a (partial) text matcher
      // that should match:
      // properties: {
      //     text: new RegExp(/.*ersi.*/gm)
      // }
      // ...but not:
      // properties: {
      //     text: {
      //         regex: {
      //             source: '.*ersi.*',
      //             flags: 'gm'
      //         }
      //     }
      // }
      if (
          typeof controlSelector.selector.properties?.text === "object" &&
          controlSelector.selector.properties?.text instanceof RegExp
      ) {
          // make it a string for serializing into browser-scope and
          // further processing there
          controlSelector.selector.properties.text = controlSelector.selector.properties.text.toString()
      }

      const _result = (await clientSide_getControl(controlSelector, this._browserInstance)) as clientSide_ui5Response

      // When chromedriver service is not used the domElement is not set accordingly via devtool protocol
      // Therefore we get element reference by calling browser execute function manually
      if (Object.keys(_result.domElement).length === 0) {
          const elementReference = (await this._browserInstance.execute((id) => {
              const webElement = document.evaluate(
                  `//*[@id='${id}']`,
                  document,
                  null,
                  XPathResult.FIRST_ORDERED_NODE_TYPE,
                  null
              ).singleNodeValue
              return webElement
          }, _result.id)) as unknown as WebdriverIO.Element
          _result.domElement = elementReference
      }

      const { status, domElement, id, aProtoFunctions, className } = _result

      if (status === 0 && id) {
          // only if the result is valid
          this._generatedWdioMethods = wdioApi

          // add metadata
          this._metadata.className = className
          this._domId = id

          // set the succesful init param
          this._initialisation = true
      }
      if (this._logging) {
          this._writeObjectResultLog(_result, "_getControl()")
      }

      return { status: status, domElement: domElement, aProtoFunctions: aProtoFunctions }
  }
vobu commented 1 year ago

w00t, thanks for digging deep and getting working code done! The easiest way to directly contribute is to

Then CI runs, we can review the PR and work together on getting your changes in as fast as possible!

QA-ler commented 1 year ago

430

Done. Hope I did this right ^^

vobu commented 1 year ago

closed via #430