0

I'm automating with Selenium. I'm dealing with 2 variants (mobile, desktop) of a page. Both have a "Login" button, but the HTML is different.

First variant:

 <div role="button"><div ...><span><span>Foo Bar MaybeQuux</span></span></div></div>

Second variant:

<a href="p/q/r" data="loginButton"><div><span><span>Foo Bar MaybeQuux</span></span></div></a>

I've written MaybeQuux as for small screens it turns into Mayb.... However Bar is guaranteed.

Here's my code so far:

def waitClickable(by, text, timeout=60):
    return WebDriverWait(driver, timeout).until(
        EC.element_to_be_clickable(
            (by, text)
        )
    )

waitClickable(By.XPATH, "???").click()

So my question is, would it possible to write a single xpath that can locate both variants?

If so I can avoid the awkwardness of having to wait on an object of unknown form.

ref: Python selenium : Explicitly wait for one of two elements to be loaded

undetected Selenium
  • 183,867
  • 41
  • 278
  • 352
P i
  • 29,020
  • 36
  • 159
  • 267

3 Answers3

1

This XPath should get the inner span in both cases

'//div[@role="button"]/descendant::span[starts-with(.,"Foo Bar")] | //a[@data="loginButton"]/descendant::span[starts-with(.,"Foo Bar")]'

Alternative expressions

//*[(name()="div" and @role="button") or (name()="a" and @data="loginButton")]/descendant::span[starts-with(.,"Foo Bar")]

//*[(self::div and @role="button") or (self::a and @data="loginButton")]/descendant::span[starts-with(.,"Foo Bar")]

LMC
  • 10,453
  • 2
  • 27
  • 52
  • 1
    Thanks! I wonder if this would serve as an answer to the referenced question also. Nobody seems to have come up with it. – P i Aug 03 '22 at 20:25
1

You can use below XPATH

//*[self::div[@role='button'] or self::a[@data='loginButton'] and starts-with(.//span, 'Foo Bar Maybe')]

but the easiest way is

//span[starts-with(., 'Foo Bar Mayb')]

or

//span[contains(., 'Bar Mayb')]/ancestor::*[self::a[@data='loginButton'] or self::div[@role='button']]
Curious koala
  • 309
  • 1
  • 9
  • Could you break the syntax down a little? Usually I can figure syntax out if I know what the thing does, but I'm struggling with XPATHs. Those last examples... they aren't reaching back to their containing `a`/`div`s. Can I get selenium to click the span and it will click the button? – P i Aug 03 '22 at 20:23
  • @Pi if click on `span` doesn't work and you want XPath to return `a` or `div` tru `//span[contains(., 'Bar Mayb')]/ancestor::*[self::a[@data='loginButton'] or self::div[@role='button']]` – Curious koala Aug 04 '22 at 07:23
1

Considering both the HTMLs:

  • First:

    <div role="button"><div ...><span><span>Foo Bar MaybeQuux</span></span></div></div>
    
  • Second:

    <a href="p/q/r" data="loginButton"><div><span><span>Foo Bar MaybeQuux</span></span></div></a>
    

Both have the following identical features:

  • Bar is the guaranteed text
  • Mayb is the guaranteed text
  • The above texts are within a <span>
  • The grandchild <span> is within it's immediate parent <span>
  • The parent <span> is within it's immediate parent <div>

Solution

A common xpath to both the elements would be:

//div/span/span[contains(., 'Bar') and contains(., 'Mayb')]

POC

Maybe

undetected Selenium
  • 183,867
  • 41
  • 278
  • 352