title: BeautifulSoup - search by text inside a tag
body: <p>Observe the following problem:</p>
  
  <pre><code>import re
  from bs4 import BeautifulSoup as BS
  
  soup = BS("""
  &lt;a href="/customer-menu/1/accounts/1/update"&gt;
      Edit
  &lt;/a&gt;
  """)
  
  # This returns the &lt;a&gt; element
  soup.find(
      'a',
      href="/customer-menu/1/accounts/1/update",
      text=re.compile(".*Edit.*")
  )
  
  soup = BS("""
  &lt;a href="/customer-menu/1/accounts/1/update"&gt;
      &lt;i class="fa fa-edit"&gt;&lt;/i&gt; Edit
  &lt;/a&gt;
  """)
  
  # This returns None
  soup.find(
      'a',
      href="/customer-menu/1/accounts/1/update",
      text=re.compile(".*Edit.*")
  )
  </code></pre>
  
  <p>For some reason, BeautifulSoup will not match the text, when the <code>&lt;i&gt;</code> tag is there as well. Finding the tag and showing its text produces</p>
  
  <pre><code>&gt;&gt;&gt; a2 = soup.find(
          'a',
          href="/customer-menu/1/accounts/1/update"
      )
  &gt;&gt;&gt; print(repr(a2.text))
  '\n Edit\n'
  </code></pre>
  
  <p>Right. According to the <a href="http://www.crummy.com/software/BeautifulSoup/bs4/doc/#a-regular-expression" rel="noreferrer">Docs</a>, soup uses the match function of the regular expression, not the search function. So I need to provide the DOTALL flag:</p>
  
  <pre><code>pattern = re.compile('.*Edit.*')
  pattern.match('\n Edit\n')  # Returns None
  
  pattern = re.compile('.*Edit.*', flags=re.DOTALL)
  pattern.match('\n Edit\n')  # Returns MatchObject
  </code></pre>
  
  <p>Alright. Looks good. Let's try it with soup</p>
  
  <pre><code>soup = BS("""
  &lt;a href="/customer-menu/1/accounts/1/update"&gt;
      &lt;i class="fa fa-edit"&gt;&lt;/i&gt; Edit
  &lt;/a&gt;
  """)
  
  soup.find(
      'a',
      href="/customer-menu/1/accounts/1/update",
      text=re.compile(".*Edit.*", flags=re.DOTALL)
  )  # Still return None... Why?!
  </code></pre>
  
  <h3><strong>Edit</strong></h3>
  
  <p>My solution based on geckons answer: I implemented these helpers:</p>
  
  <pre><code>import re
  
  MATCH_ALL = r'.*'
  
  
  def like(string):
      """
      Return a compiled regular expression that matches the given
      string with any prefix and postfix, e.g. if string = "hello",
      the returned regex matches r".*hello.*"
      """
      string_ = string
      if not isinstance(string_, str):
          string_ = str(string_)
      regex = MATCH_ALL + re.escape(string_) + MATCH_ALL
      return re.compile(regex, flags=re.DOTALL)
  
  
  def find_by_text(soup, text, tag, **kwargs):
      """
      Find the tag in soup that matches all provided kwargs, and contains the
      text.
  
      If no match is found, return None.
      If more than one match is found, raise ValueError.
      """
      elements = soup.find_all(tag, **kwargs)
      matches = []
      for element in elements:
          if element.find(text=like(text)):
              matches.append(element)
      if len(matches) &gt; 1:
          raise ValueError("Too many matches:\n" + "\n".join(matches))
      elif len(matches) == 0:
          return None
      else:
          return matches[0]
  </code></pre>
  
  <p>Now, when I want to find the element above, I just run <code>find_by_text(soup, 'Edit', 'a', href='/customer-menu/1/accounts/1/update')</code></p>