Full Code of mherrmann/helium for AI

master ac354ad19f71 cached
104 files
258.7 KB
69.2k tokens
916 symbols
1 requests
Download .txt
Showing preview only (283K chars total). Download the full file or copy to clipboard to get everything.
Repository: mherrmann/helium
Branch: master
Commit: ac354ad19f71
Files: 104
Total size: 258.7 KB

Directory structure:
gitextract_83h9li4j/

├── .gitattributes
├── .gitignore
├── .readthedocs.yaml
├── LICENSE.txt
├── NOTICE.txt
├── README.md
├── docs/
│   ├── Makefile
│   ├── README.md
│   ├── api.rst
│   ├── cheatsheet.md
│   ├── conf.py
│   ├── contributors.rst
│   ├── index.rst
│   ├── installation.rst
│   └── make.bat
├── helium/
│   ├── __init__.py
│   └── _impl/
│       ├── __init__.py
│       ├── match_type.py
│       ├── selenium_wrappers.py
│       └── util/
│           ├── __init__.py
│           ├── dictionary.py
│           ├── geom.py
│           ├── html.py
│           ├── inspect_.py
│           ├── lang.py
│           ├── path.py
│           ├── system.py
│           └── xpath.py
├── requirements/
│   ├── base.txt
│   ├── docs.txt
│   └── test.txt
├── setup.py
└── tests/
    ├── __init__.py
    ├── api/
    │   ├── __init__.py
    │   ├── data/
    │   │   ├── default.css
    │   │   ├── js/
    │   │   │   ├── jquery.ui-contextmenu.js
    │   │   │   └── util.js
    │   │   ├── test_alert.html
    │   │   ├── test_aria.html
    │   │   ├── test_click.html
    │   │   ├── test_doubleclick.html
    │   │   ├── test_drag/
    │   │   │   ├── default.html
    │   │   │   ├── html5.html
    │   │   │   └── test_drag.css
    │   │   ├── test_file_upload/
    │   │   │   └── test_file_upload.html
    │   │   ├── test_gui_elements.html
    │   │   ├── test_gui_elements_iframe.html
    │   │   ├── test_hover.html
    │   │   ├── test_iframe/
    │   │   │   ├── iframe.html
    │   │   │   ├── main.html
    │   │   │   └── nested_iframe.html
    │   │   ├── test_implicit_wait.html
    │   │   ├── test_leaked_password.html
    │   │   ├── test_point.html
    │   │   ├── test_rightclick.html
    │   │   ├── test_scroll.html
    │   │   ├── test_start_go_to.html
    │   │   ├── test_tables.html
    │   │   ├── test_text_impl.html
    │   │   ├── test_wait_until.html
    │   │   ├── test_window/
    │   │   │   ├── popup.html
    │   │   │   └── test_window.html
    │   │   ├── test_window_handling/
    │   │   │   ├── main.html
    │   │   │   ├── main_immediate_popup.html
    │   │   │   └── popup.html
    │   │   └── test_write.html
    │   ├── test_alert.py
    │   ├── test_aria.py
    │   ├── test_chrome_options.py
    │   ├── test_click.py
    │   ├── test_doubleclick.py
    │   ├── test_drag.py
    │   ├── test_file_upload.py
    │   ├── test_find_all.py
    │   ├── test_gui_elements.py
    │   ├── test_highlight.py
    │   ├── test_hover.py
    │   ├── test_iframe.py
    │   ├── test_implicit_wait.py
    │   ├── test_kill_service_at_exit.py
    │   ├── test_kill_service_at_exit_chrome.py
    │   ├── test_leaked_password.py
    │   ├── test_no_driver.py
    │   ├── test_point.py
    │   ├── test_press.py
    │   ├── test_repr.py
    │   ├── test_rightclick.py
    │   ├── test_s.py
    │   ├── test_scroll.py
    │   ├── test_start_go_to.py
    │   ├── test_tables.py
    │   ├── test_text_impl.py
    │   ├── test_wait_until.py
    │   ├── test_window.py
    │   ├── test_window_handling.py
    │   ├── test_write.py
    │   └── util.py
    └── unit/
        ├── __init__.py
        └── test__impl/
            ├── __init__.py
            ├── test_selenium_wrappers.py
            └── test_util/
                ├── __init__.py
                ├── test_dictionary.py
                ├── test_html.py
                └── test_xpath.py

================================================
FILE CONTENTS
================================================

================================================
FILE: .gitattributes
================================================
* text=auto
*.sh text eol=lf

================================================
FILE: .gitignore
================================================
# Byte-compiled / optimized / DLL files
__pycache__/
*.py[cod]
*$py.class

# C extensions
*.so

# Distribution / packaging
.Python
build/
develop-eggs/
dist/
downloads/
eggs/
.eggs/
lib/
lib64/
parts/
sdist/
var/
wheels/
pip-wheel-metadata/
share/python-wheels/
*.egg-info/
.installed.cfg
*.egg
MANIFEST

# Installer logs
pip-log.txt
pip-delete-this-directory.txt

# Translations
*.mo
*.pot

# Sphinx documentation
docs/_build/
docs/modules/

# Environments
.env
.venv
env/
venv/
ENV/
env.bak/
venv.bak/

# Personal Files
personal/
sample-data/
pyproject.toml
poetry.lock

# IDE / Editor
.idea
.vscode

geckodriver.log


================================================
FILE: .readthedocs.yaml
================================================
version: 2

python:
   install:
     - requirements: requirements/docs.txt

build:
  os: ubuntu-22.04
  tools:
    python: "3.11"

sphinx:
   configuration: docs/conf.py

================================================
FILE: LICENSE.txt
================================================
MIT License

Copyright (c) 2020 Michael Herrmann
Copyright (c) 2013 - 2019 Michael Herrmann & Tytus Dobrzynski

Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files (the "Software"), to deal
in the Software without restriction, including without limitation the rights
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
copies of the Software, and to permit persons to whom the Software is
furnished to do so, subject to the following conditions:

The above copyright notice and this permission notice shall be included in all
copies or substantial portions of the Software.

THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
SOFTWARE.

================================================
FILE: NOTICE.txt
================================================
LICENSES OF THIRD-PARTY LIBRARIES
=================================

Helium is based on several free and open source software libraries and
could not have been developed without them. Here we describle the licensing
terms under which we are using them.

Selenium Java & Python Bindings
===============================
URL: https://selenium.dev
License: Apache License, Version 2.0

 Apache License
                           Version 2.0, January 2004
                        http://www.apache.org/licenses/

   TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION

   1. Definitions.

      "License" shall mean the terms and conditions for use, reproduction,
      and distribution as defined by Sections 1 through 9 of this document.

      "Licensor" shall mean the copyright owner or entity authorized by
      the copyright owner that is granting the License.

      "Legal Entity" shall mean the union of the acting entity and all
      other entities that control, are controlled by, or are under common
      control with that entity. For the purposes of this definition,
      "control" means (i) the power, direct or indirect, to cause the
      direction or management of such entity, whether by contract or
      otherwise, or (ii) ownership of fifty percent (50%) or more of the
      outstanding shares, or (iii) beneficial ownership of such entity.

      "You" (or "Your") shall mean an individual or Legal Entity
      exercising permissions granted by this License.

      "Source" form shall mean the preferred form for making modifications,
      including but not limited to software source code, documentation
      source, and configuration files.

      "Object" form shall mean any form resulting from mechanical
      transformation or translation of a Source form, including but
      not limited to compiled object code, generated documentation,
      and conversions to other media types.

      "Work" shall mean the work of authorship, whether in Source or
      Object form, made available under the License, as indicated by a
      copyright notice that is included in or attached to the work
      (an example is provided in the Appendix below).

      "Derivative Works" shall mean any work, whether in Source or Object
      form, that is based on (or derived from) the Work and for which the
      editorial revisions, annotations, elaborations, or other
      modifications represent, as a whole, an original work of authorship.
      For the purposes of this License, Derivative Works shall not include
      works that remain separable from, or merely link (or bind by name) to
      the interfaces of, the Work and Derivative Works thereof.

      "Contribution" shall mean any work of authorship, including
      the original version of the Work and any modifications or additions
      to that Work or Derivative Works thereof, that is intentionally
      submitted to Licensor for inclusion in the Work by the copyright
      owner or by an individual or Legal Entity authorized to submit on
      behalf of the copyright owner. For the purposes of this definition,
      "submitted" means any form of electronic, verbal, or written
      communication sent to the Licensor or its representatives, including
      but not limited to communication on electronic mailing lists, source
      code control systems, and issue tracking systems that are managed by,
      or on behalf of, the Licensor for the purpose of discussing and
      improving the Work, but excluding communication that is conspicuously
      marked or otherwise designated in writing by the copyright owner as
      "Not a Contribution."

      "Contributor" shall mean Licensor and any individual or Legal Entity
      on behalf of whom a Contribution has been received by Licensor and
      subsequently incorporated within the Work.

   2. Grant of Copyright License. Subject to the terms and conditions of
      this License, each Contributor hereby grants to You a perpetual,
      worldwide, non-exclusive, no-charge, royalty-free, irrevocable
      copyright license to reproduce, prepare Derivative Works of,
      publicly display, publicly perform, sublicense, and distribute the
      Work and such Derivative Works in Source or Object form.

   3. Grant of Patent License. Subject to the terms and conditions of
      this License, each Contributor hereby grants to You a perpetual,
      worldwide, non-exclusive, no-charge, royalty-free, irrevocable
      (except as stated in this section) patent license to make, have made,
      use, offer to sell, sell, import, and otherwise transfer the Work,
      where such license applies only to those patent claims licensable
      by such Contributor that are necessarily infringed by their
      Contribution(s) alone or by combination of their Contribution(s)
      with the Work to which such Contribution(s) was submitted. If You
      institute patent litigation against any entity (including a
      cross-claim or counterclaim in a lawsuit) alleging that the Work
      or a Contribution incorporated within the Work constitutes direct
      or contributory patent infringement, then any patent licenses
      granted to You under this License for that Work shall terminate
      as of the date such litigation is filed.

   4. Redistribution. You may reproduce and distribute copies of the
      Work or Derivative Works thereof in any medium, with or without
      modifications, and in Source or Object form, provided that You
      meet the following conditions:

      (a) You must give any other recipients of the Work or
          Derivative Works a copy of this License; and

      (b) You must cause any modified files to carry prominent notices
          stating that You changed the files; and

      (c) You must retain, in the Source form of any Derivative Works
          that You distribute, all copyright, patent, trademark, and
          attribution notices from the Source form of the Work,
          excluding those notices that do not pertain to any part of
          the Derivative Works; and

      (d) If the Work includes a "NOTICE" text file as part of its
          distribution, then any Derivative Works that You distribute must
          include a readable copy of the attribution notices contained
          within such NOTICE file, excluding those notices that do not
          pertain to any part of the Derivative Works, in at least one
          of the following places: within a NOTICE text file distributed
          as part of the Derivative Works; within the Source form or
          documentation, if provided along with the Derivative Works; or,
          within a display generated by the Derivative Works, if and
          wherever such third-party notices normally appear. The contents
          of the NOTICE file are for informational purposes only and
          do not modify the License. You may add Your own attribution
          notices within Derivative Works that You distribute, alongside
          or as an addendum to the NOTICE text from the Work, provided
          that such additional attribution notices cannot be construed
          as modifying the License.

      You may add Your own copyright statement to Your modifications and
      may provide additional or different license terms and conditions
      for use, reproduction, or distribution of Your modifications, or
      for any such Derivative Works as a whole, provided Your use,
      reproduction, and distribution of the Work otherwise complies with
      the conditions stated in this License.

   5. Submission of Contributions. Unless You explicitly state otherwise,
      any Contribution intentionally submitted for inclusion in the Work
      by You to the Licensor shall be under the terms and conditions of
      this License, without any additional terms or conditions.
      Notwithstanding the above, nothing herein shall supersede or modify
      the terms of any separate license agreement you may have executed
      with Licensor regarding such Contributions.

   6. Trademarks. This License does not grant permission to use the trade
      names, trademarks, service marks, or product names of the Licensor,
      except as required for reasonable and customary use in describing the
      origin of the Work and reproducing the content of the NOTICE file.

   7. Disclaimer of Warranty. Unless required by applicable law or
      agreed to in writing, Licensor provides the Work (and each
      Contributor provides its Contributions) on an "AS IS" BASIS,
      WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or
      implied, including, without limitation, any warranties or conditions
      of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A
      PARTICULAR PURPOSE. You are solely responsible for determining the
      appropriateness of using or redistributing the Work and assume any
      risks associated with Your exercise of permissions under this
      License.

   8. Limitation of Liability. In no event and under no legal theory,
      whether in tort (including negligence), contract, or otherwise,
      unless required by applicable law (such as deliberate and grossly
      negligent acts) or agreed to in writing, shall any Contributor be
      liable to You for damages, including any direct, indirect, special,
      incidental, or consequential damages of any character arising as a
      result of this License or out of the use or inability to use the
      Work (including but not limited to damages for loss of goodwill,
      work stoppage, computer failure or malfunction, or any and all
      other commercial damages or losses), even if such Contributor
      has been advised of the possibility of such damages.

   9. Accepting Warranty or Additional Liability. While redistributing
      the Work or Derivative Works thereof, You may choose to offer,
      and charge a fee for, acceptance of support, warranty, indemnity,
      or other liability obligations and/or rights consistent with this
      License. However, in accepting such obligations, You may act only
      on Your own behalf and on Your sole responsibility, not on behalf
      of any other Contributor, and only if You agree to indemnify,
      defend, and hold each Contributor harmless for any liability
      incurred by, or claims asserted against, such Contributor by reason
      of your accepting any such warranty or additional liability.

   END OF TERMS AND CONDITIONS

   APPENDIX: How to apply the Apache License to your work.

      To apply the Apache License to your work, attach the following
      boilerplate notice, with the fields enclosed by brackets "[]"
      replaced with your own identifying information. (Don't include
      the brackets!)  The text should be enclosed in the appropriate
      comment syntax for the file format. We also recommend that a
      file or class name and description of purpose be included on the
      same "printed page" as the copyright notice for easier
      identification within third-party archives.

   Copyright [yyyy] [name of copyright owner]

   Licensed under the Apache License, Version 2.0 (the "License");
   you may not use this file except in compliance with the License.
   You may obtain a copy of the License at

       http://www.apache.org/licenses/LICENSE-2.0

   Unless required by applicable law or agreed to in writing, software
   distributed under the License is distributed on an "AS IS" BASIS,
   WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
   See the License for the specific language governing permissions and
   limitations under the License.

InternetExplorerDriver
======================
URL: https://selenium.dev
License: Apache License, Version 2.0 (see above)

ChromeDriver
============
URL: https://code.google.com/p/chromedriver/
License: New BSD License

Copyright 2015 The Chromium Authors. All rights reserved.

Redistribution and use in source and binary forms, with or without
modification, are permitted provided that the following conditions are
met:
 * Redistributions of source code must retain the above copyright
   notice, this list of conditions and the following disclaimer.
 * Redistributions in binary form must reproduce the above
   copyright notice, this list of conditions and the following disclaimer
   in the documentation and/or other materials provided with the
   distribution.
 * Neither the name of Google Inc. nor the names of its contributors may
   be used to endorse or promote products derived from this software
   without specific prior written permission.

  THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
  "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
  LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
  A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
  OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
  SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
  LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
  DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
  THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
  (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
  OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.

Apache Commons Exec
===================
URL: http://commons.apache.org/proper/commons-exec/
License: Apache License, Version 2.0 (see above).

JTwig HtmlUtils
===============
URL: https://github.com/lyncode/jtwig/blob/d540f2160467146a7dde0bdf9ac979687f78f194/jtwig-functions/src/main/java/com/lyncode/jtwig/functions/util/HtmlUtils.java
Version: d540f2160467146a7dde0bdf9ac979687f78f194
License: Apache License, Version 2.0 (see above).

geckodriver
===========
URL: https://github.com/mozilla/geckodriver

License:

Mozilla Public License Version 2.0
----------------------------------

1. Definitions
--------------

1.1. "Contributor"
    means each individual or legal entity that creates, contributes to
    the creation of, or owns Covered Software.

1.2. "Contributor Version"
    means the combination of the Contributions of others (if any) used
    by a Contributor and that particular Contributor's Contribution.

1.3. "Contribution"
    means Covered Software of a particular Contributor.

1.4. "Covered Software"
    means Source Code Form to which the initial Contributor has attached
    the notice in Exhibit A, the Executable Form of such Source Code
    Form, and Modifications of such Source Code Form, in each case
    including portions thereof.

1.5. "Incompatible With Secondary Licenses"
    means

    (a) that the initial Contributor has attached the notice described
        in Exhibit B to the Covered Software; or

    (b) that the Covered Software was made available under the terms of
        version 1.1 or earlier of the License, but not also under the
        terms of a Secondary License.

1.6. "Executable Form"
    means any form of the work other than Source Code Form.

1.7. "Larger Work"
    means a work that combines Covered Software with other material, in
    a separate file or files, that is not Covered Software.

1.8. "License"
    means this document.

1.9. "Licensable"
    means having the right to grant, to the maximum extent possible,
    whether at the time of the initial grant or subsequently, any and
    all of the rights conveyed by this License.

1.10. "Modifications"
    means any of the following:

    (a) any file in Source Code Form that results from an addition to,
        deletion from, or modification of the contents of Covered
        Software; or

    (b) any new file in Source Code Form that contains any Covered
        Software.

1.11. "Patent Claims" of a Contributor
    means any patent claim(s), including without limitation, method,
    process, and apparatus claims, in any patent Licensable by such
    Contributor that would be infringed, but for the grant of the
    License, by the making, using, selling, offering for sale, having
    made, import, or transfer of either its Contributions or its
    Contributor Version.

1.12. "Secondary License"
    means either the GNU General Public License, Version 2.0, the GNU
    Lesser General Public License, Version 2.1, the GNU Affero General
    Public License, Version 3.0, or any later versions of those
    licenses.

1.13. "Source Code Form"
    means the form of the work preferred for making modifications.

1.14. "You" (or "Your")
    means an individual or a legal entity exercising rights under this
    License. For legal entities, "You" includes any entity that
    controls, is controlled by, or is under common control with You. For
    purposes of this definition, "control" means (a) the power, direct
    or indirect, to cause the direction or management of such entity,
    whether by contract or otherwise, or (b) ownership of more than
    fifty percent (50%) of the outstanding shares or beneficial
    ownership of such entity.

2. License Grants and Conditions
--------------------------------

2.1. Grants

Each Contributor hereby grants You a world-wide, royalty-free,
non-exclusive license:

(a) under intellectual property rights (other than patent or trademark)
    Licensable by such Contributor to use, reproduce, make available,
    modify, display, perform, distribute, and otherwise exploit its
    Contributions, either on an unmodified basis, with Modifications, or
    as part of a Larger Work; and

(b) under Patent Claims of such Contributor to make, use, sell, offer
    for sale, have made, import, and otherwise transfer either its
    Contributions or its Contributor Version.

2.2. Effective Date

The licenses granted in Section 2.1 with respect to any Contribution
become effective for each Contribution on the date the Contributor first
distributes such Contribution.

2.3. Limitations on Grant Scope

The licenses granted in this Section 2 are the only rights granted under
this License. No additional rights or licenses will be implied from the
distribution or licensing of Covered Software under this License.
Notwithstanding Section 2.1(b) above, no patent license is granted by a
Contributor:

(a) for any code that a Contributor has removed from Covered Software;
    or

(b) for infringements caused by: (i) Your and any other third party's
    modifications of Covered Software, or (ii) the combination of its
    Contributions with other software (except as part of its Contributor
    Version); or

(c) under Patent Claims infringed by Covered Software in the absence of
    its Contributions.

This License does not grant any rights in the trademarks, service marks,
or logos of any Contributor (except as may be necessary to comply with
the notice requirements in Section 3.4).

2.4. Subsequent Licenses

No Contributor makes additional grants as a result of Your choice to
distribute the Covered Software under a subsequent version of this
License (see Section 10.2) or under the terms of a Secondary License (if
permitted under the terms of Section 3.3).

2.5. Representation

Each Contributor represents that the Contributor believes its
Contributions are its original creation(s) or it has sufficient rights
to grant the rights to its Contributions conveyed by this License.

2.6. Fair Use

This License is not intended to limit any rights You have under
applicable copyright doctrines of fair use, fair dealing, or other
equivalents.

2.7. Conditions

Sections 3.1, 3.2, 3.3, and 3.4 are conditions of the licenses granted
in Section 2.1.

3. Responsibilities
-------------------

3.1. Distribution of Source Form

All distribution of Covered Software in Source Code Form, including any
Modifications that You create or to which You contribute, must be under
the terms of this License. You must inform recipients that the Source
Code Form of the Covered Software is governed by the terms of this
License, and how they can obtain a copy of this License. You may not
attempt to alter or restrict the recipients' rights in the Source Code
Form.

3.2. Distribution of Executable Form

If You distribute Covered Software in Executable Form then:

(a) such Covered Software must also be made available in Source Code
    Form, as described in Section 3.1, and You must inform recipients of
    the Executable Form how they can obtain a copy of such Source Code
    Form by reasonable means in a timely manner, at a charge no more
    than the cost of distribution to the recipient; and

(b) You may distribute such Executable Form under the terms of this
    License, or sublicense it under different terms, provided that the
    license for the Executable Form does not attempt to limit or alter
    the recipients' rights in the Source Code Form under this License.

3.3. Distribution of a Larger Work

You may create and distribute a Larger Work under terms of Your choice,
provided that You also comply with the requirements of this License for
the Covered Software. If the Larger Work is a combination of Covered
Software with a work governed by one or more Secondary Licenses, and the
Covered Software is not Incompatible With Secondary Licenses, this
License permits You to additionally distribute such Covered Software
under the terms of such Secondary License(s), so that the recipient of
the Larger Work may, at their option, further distribute the Covered
Software under the terms of either this License or such Secondary
License(s).

3.4. Notices

You may not remove or alter the substance of any license notices
(including copyright notices, patent notices, disclaimers of warranty,
or limitations of liability) contained within the Source Code Form of
the Covered Software, except that You may alter any license notices to
the extent required to remedy known factual inaccuracies.

3.5. Application of Additional Terms

You may choose to offer, and to charge a fee for, warranty, support,
indemnity or liability obligations to one or more recipients of Covered
Software. However, You may do so only on Your own behalf, and not on
behalf of any Contributor. You must make it absolutely clear that any
such warranty, support, indemnity, or liability obligation is offered by
You alone, and You hereby agree to indemnify every Contributor for any
liability incurred by such Contributor as a result of warranty, support,
indemnity or liability terms You offer. You may include additional
disclaimers of warranty and limitations of liability specific to any
jurisdiction.

4. Inability to Comply Due to Statute or Regulation
---------------------------------------------------

If it is impossible for You to comply with any of the terms of this
License with respect to some or all of the Covered Software due to
statute, judicial order, or regulation then You must: (a) comply with
the terms of this License to the maximum extent possible; and (b)
describe the limitations and the code they affect. Such description must
be placed in a text file included with all distributions of the Covered
Software under this License. Except to the extent prohibited by statute
or regulation, such description must be sufficiently detailed for a
recipient of ordinary skill to be able to understand it.

5. Termination
--------------

5.1. The rights granted under this License will terminate automatically
if You fail to comply with any of its terms. However, if You become
compliant, then the rights granted under this License from a particular
Contributor are reinstated (a) provisionally, unless and until such
Contributor explicitly and finally terminates Your grants, and (b) on an
ongoing basis, if such Contributor fails to notify You of the
non-compliance by some reasonable means prior to 60 days after You have
come back into compliance. Moreover, Your grants from a particular
Contributor are reinstated on an ongoing basis if such Contributor
notifies You of the non-compliance by some reasonable means, this is the
first time You have received notice of non-compliance with this License
from such Contributor, and You become compliant prior to 30 days after
Your receipt of the notice.

5.2. If You initiate litigation against any entity by asserting a patent
infringement claim (excluding declaratory judgment actions,
counter-claims, and cross-claims) alleging that a Contributor Version
directly or indirectly infringes any patent, then the rights granted to
You by any and all Contributors for the Covered Software under Section
2.1 of this License shall terminate.

5.3. In the event of termination under Sections 5.1 or 5.2 above, all
end user license agreements (excluding distributors and resellers) which
have been validly granted by You or Your distributors under this License
prior to termination shall survive termination.

************************************************************************
*                                                                      *
*  6. Disclaimer of Warranty                                           *
*  -------------------------                                           *
*                                                                      *
*  Covered Software is provided under this License on an "as is"       *
*  basis, without warranty of any kind, either expressed, implied, or  *
*  statutory, including, without limitation, warranties that the       *
*  Covered Software is free of defects, merchantable, fit for a        *
*  particular purpose or non-infringing. The entire risk as to the     *
*  quality and performance of the Covered Software is with You.        *
*  Should any Covered Software prove defective in any respect, You     *
*  (not any Contributor) assume the cost of any necessary servicing,   *
*  repair, or correction. This disclaimer of warranty constitutes an   *
*  essential part of this License. No use of any Covered Software is   *
*  authorized under this License except under this disclaimer.         *
*                                                                      *
************************************************************************

************************************************************************
*                                                                      *
*  7. Limitation of Liability                                          *
*  --------------------------                                          *
*                                                                      *
*  Under no circumstances and under no legal theory, whether tort      *
*  (including negligence), contract, or otherwise, shall any           *
*  Contributor, or anyone who distributes Covered Software as          *
*  permitted above, be liable to You for any direct, indirect,         *
*  special, incidental, or consequential damages of any character      *
*  including, without limitation, damages for lost profits, loss of    *
*  goodwill, work stoppage, computer failure or malfunction, or any    *
*  and all other commercial damages or losses, even if such party      *
*  shall have been informed of the possibility of such damages. This   *
*  limitation of liability shall not apply to liability for death or   *
*  personal injury resulting from such party's negligence to the       *
*  extent applicable law prohibits such limitation. Some               *
*  jurisdictions do not allow the exclusion or limitation of           *
*  incidental or consequential damages, so this exclusion and          *
*  limitation may not apply to You.                                    *
*                                                                      *
************************************************************************

8. Litigation
-------------

Any litigation relating to this License may be brought only in the
courts of a jurisdiction where the defendant maintains its principal
place of business and such litigation shall be governed by laws of that
jurisdiction, without reference to its conflict-of-law provisions.
Nothing in this Section shall prevent a party's ability to bring
cross-claims or counter-claims.

9. Miscellaneous
----------------

This License represents the complete agreement concerning the subject
matter hereof. If any provision of this License is held to be
unenforceable, such provision shall be reformed only to the extent
necessary to make it enforceable. Any law or regulation which provides
that the language of a contract shall be construed against the drafter
shall not be used to construe this License against a Contributor.

10. Versions of the License
---------------------------

10.1. New Versions

Mozilla Foundation is the license steward. Except as provided in Section
10.3, no one other than the license steward has the right to modify or
publish new versions of this License. Each version will be given a
distinguishing version number.

10.2. Effect of New Versions

You may distribute the Covered Software under the terms of the version
of the License under which You originally received the Covered Software,
or under the terms of any subsequent version published by the license
steward.

10.3. Modified Versions

If you create software not governed by this License, and you want to
create a new license for such software, you may create and use a
modified version of this License if you rename the license and remove
any references to the name of the license steward (except to note that
such modified license differs from this License).

10.4. Distributing Source Code Form that is Incompatible With Secondary
Licenses

If You choose to distribute Source Code Form that is Incompatible With
Secondary Licenses under the terms of this version of the License, the
notice described in Exhibit B of this License must be attached.

Exhibit A - Source Code Form License Notice
-------------------------------------------

  This Source Code Form is subject to the terms of the Mozilla Public
  License, v. 2.0. If a copy of the MPL was not distributed with this
  file, You can obtain one at http://mozilla.org/MPL/2.0/.

If it is not possible or desirable to put the notice in a particular
file, then You may include the notice in a location (such as a LICENSE
file in a relevant directory) where a recipient would be likely to look
for such a notice.

You may add additional accurate notices of copyright ownership.

Exhibit B - "Incompatible With Secondary Licenses" Notice
---------------------------------------------------------

  This Source Code Form is "Incompatible With Secondary Licenses", as
  defined by the Mozilla Public License, v. 2.0.

================================================
FILE: README.md
================================================
# Lighter web automation with Python

Helium is a Python library for automating browsers such as Chrome and Firefox.
For example:

![Helium Demo](docs/helium-demo.gif)

## Installation

To get started with Helium, you need Python 3 and Chrome or Firefox.

I would recommend creating a virtual environment. This lets you install Helium
for just your current project, instead of globally on your whole computer.

To create and activate a virtual environment, type the following commands into
a command prompt window:

```bash
python3 -m venv venv
# On Mac/Linux:
source venv/bin/activate
# On Windows:
call venv\scripts\activate.bat
```
Then, you can install Helium with `pip`:

```bash
python -m pip install helium
```

Now enter `python` into the command prompt and (for instance) the commands in
the animation at the top of this page (`from helium import *`, ...).

## Your first script

I've compiled a [cheatsheet](docs/cheatsheet.md) that quickly teaches you all
you need to know to be productive with Helium. For a more complete reference of
Helium's features, please see the
[documentation](https://helium.readthedocs.io/en/latest/).

## Connection to Selenium

Under the hood, Helium forwards each call to Selenium. The difference is that
Helium's API is much more high-level. In Selenium, you need to use HTML IDs,
XPaths and CSS selectors to identify web page elements. Helium on the other hand
lets you refer to elements by user-visible labels. As a result, Helium scripts
are typically 30-50% shorter than similar Selenium scripts. What's more, they
are easier to read and more stable with respect to changes in the underlying web
page.

Because Helium is simply a wrapper around Selenium, you can freely mix the two
libraries. For example:

```python
# A Helium function:
driver = start_chrome()
# A Selenium API:
driver.execute_script("alert('Hi!');")
```

So in other words, you don't lose anything by using Helium over pure Selenium.

In addition to its more high-level API, Helium simplifies further tasks that are
traditionally painful in Selenium:

- **iFrames:** Unlike Selenium, Helium lets you interact with elements inside
  nested iFrames, without having to first "switch to" the iFrame.
- **Window management.** Helium notices when popups open or close and focuses /
  defocuses them like a user would. You can also easily switch to a window by
  (parts of) its title. No more having to iterate over Selenium window handles.
- **Implicit waits.** By default, if you try click on an element with Selenium
  and that element is not yet present on the page, your script fails. Helium by
  default waits up to 10 seconds for the element to appear.
- **Explicit waits.** Helium gives you a much nicer API for waiting for a
  condition on the web page to become true. For example: To wait for an element
  to appear in Selenium, you would write:
  ```python
  element = WebDriverWait(driver, 10).until(
      EC.presence_of_element_located((By.ID, "myDynamicElement"))
  )
  ```
  With Helium, you can write:
  ```python
  wait_until(Button('Download').exists)
  ```

## Status of this project

I have too little spare time to maintain this project for free. If you'd like
my help, please go to my [web site](http://herrmann.io) to ask about my
consulting rates. Otherwise, unless it is very easy for me, I will usually not
respond to emails or issues on the issue tracker. I will however accept and
merge PRs. So if you add some functionality to Helium that may be useful for
others, do share it with us by creating a Pull Request. For instructions, please
see [Contributing](#Contributing) below.

## How you can help

I find Helium extremely useful in my own projects and feel it should be more
widely known. Here's how you can help with this:

- Star this project on GitHub.
- Tell your friends and colleagues about it.
- [Share it on Twitter with one click](https://twitter.com/intent/tweet?text=I%20find%20Helium%20very%20useful%20for%20web%20automation%20with%20Python%3A%20https%3A//github.com/mherrmann/helium)
- Share it on other social media
- Write a blog post about Helium.

With this, I think we can eventually make Helium the de-facto standard for web
automation in Python.

## Contributing

Pull Requests are very welcome. Please follow the same coding conventions as the
rest of the code, in particular the use of tabs over spaces. Also, read through my
[PR guidelines](https://gist.github.com/mherrmann/5ce21814789152c17abd91c0b3eaadca).
Doing this will save you (and me) unnecessary effort.

Before you submit a PR, ensure that the tests still work:

```bash
pip install -Ur requirements/test.txt
python setup.py test
```

This runs the tests against Chrome. To run them against Firefox, set the
environment variable `TEST_BROWSER` to `firefox`. Eg. on Mac/Linux:

```bash
TEST_BROWSER=firefox python setup.py test
```

On Windows:

```bash
set TEST_BROWSER=firefox
python setup.py test
```

If you do add new functionality, you should also add tests for it. Please see
the [`tests/`](tests) directory for what this might look like.

## History

I (Michael Herrmann) originally developed Helium in 2013 for a Polish IT startup
called BugFree software. (It could be that you have seen Helium before at
https://heliumhq.com.) We shut down the company at the end of 2019 and I felt it
would be a shame if Helium simply disappeared from the face of the earth. So I
invested some time to modernize it and bring it into a state suitable for open
source.

Helium used to be available for both Java and Python. But because I now only
use it from Python, I didn't have time to bring the Java implementation up to
speed as well. Similarly for Internet Explorer: Helium used to support it, but
since I have no need for it, I removed the (probably broken) old implementation.

The name Helium was chosen because it is also a chemical element like Selenium,
but it is lighter.


================================================
FILE: docs/Makefile
================================================
# Minimal makefile for Sphinx documentation
#

# You can set these variables from the command line, and also
# from the environment for the first two.
SPHINXOPTS    ?=
SPHINXBUILD   ?= sphinx-build
SOURCEDIR     = .
BUILDDIR      = _build

# Put it first so that "make" without argument is like "make help".
help:
	@$(SPHINXBUILD) -M help "$(SOURCEDIR)" "$(BUILDDIR)" $(SPHINXOPTS) $(O)

.PHONY: help Makefile

# Catch-all target: route all unknown targets to Sphinx using the new
# "make mode" option.  $(O) is meant as a shortcut for $(SPHINXOPTS).
%: Makefile
	@$(SPHINXBUILD) -M $@ "$(SOURCEDIR)" "$(BUILDDIR)" $(SPHINXOPTS) $(O)


================================================
FILE: docs/README.md
================================================
# Welcome to Helium's documentation

The documentation is built using
[sphinx](https://www.sphinx-doc.org/en/master/index.html) and the theme used is
[sphinx-rtd-theme](https://sphinx-rtd-theme.readthedocs.io/en/stable/).

## Setting up documentation locally

Ensure you have `python` and `pip` installed on your
system and then run this command in the project root:

```bash
pip install -Ur requirements/docs.txt
make -C docs/ html
```

This will install all development dependencies for the project and then build
the documentation in HTML format in `docs/_build/` directory. Open
`docs/_build/index.html` in your browser to see the documentation.


================================================
FILE: docs/api.rst
================================================
API
===

.. automodule:: helium
    :members:


================================================
FILE: docs/cheatsheet.md
================================================
# Helium cheatsheet

This page very quickly teaches you the most important parts of Helium's API.

## Importing

All of Helium's public functions lie directly in the module `helium`.
You can for instance import them as follows:

```python
from helium import *
```

## Starting a browser

Helium currently supports Chrome and Firefox. You can start them with the
following functions:

```python
start_chrome()
start_firefox()
```

You can optionally pass a URL to open (eg. `start_chrome('google.com')`)

## Headless browser

When you type the above commands, you will actually see a browser window open.
This is useful for developing your scripts. However, once you run them, you may
not want this window to appear. You can achieve this by adding `headless=True`:

```python
start_chrome(headless=True)
start_chrome('google.com', headless=True)
```

(Similarly for `start_firefox(...)` of course.)

## Interacting with a web site

The following example shows the most typical statements in a Helium script:

```python
from helium import *
start_chrome('google.com')
write('helium selenium github')
press(ENTER)
click('mherrmann/helium')
go_to('github.com/login')
write('username', into='Username')
write('password', into='Password')
click('Sign in')
kill_browser()
```

Most of your own code will (hopefully) be as simple as the above.

## Element types

The above example used pure strings such as `Sign in` to identify elements on
the web page. But Helium also lets you target elements more specifically.
For instance:

 * [`Link('Sign in')`](https://github.com/mherrmann/helium/blob/0667ddb9be531367a0d707ad8f5fcfb75c528521/helium/__init__.py#L643)
 * [`Button('Sign in')`](https://github.com/mherrmann/helium/blob/0667ddb9be531367a0d707ad8f5fcfb75c528521/helium/__init__.py#L706)
 * [`TextField('First name')`](https://github.com/mherrmann/helium/blob/0667ddb9be531367a0d707ad8f5fcfb75c528521/helium/__init__.py#L768)
 * [`CheckBox('I accept')`](https://github.com/mherrmann/helium/blob/0667ddb9be531367a0d707ad8f5fcfb75c528521/helium/__init__.py#L867)
 * [`RadioButton('Windows')`](https://github.com/mherrmann/helium/blob/0667ddb9be531367a0d707ad8f5fcfb75c528521/helium/__init__.py#L907)
 * [`Image(alt='Helium logo')`](https://github.com/mherrmann/helium/blob/0667ddb9be531367a0d707ad8f5fcfb75c528521/helium/__init__.py#L739)

You can pass them into other functions such as `click(Link('Sign in'))`.
But you can also use them to _read_ data from the web site. For instance:

```python
print(TextField('First name').value)
```

A common use case is to use `.exists()` to check for the existence of an
element. For example:

```python
if Text('Accept cookies?').exists():
    click('I accept')
```

I also often find `Text(...).value` useful for reading out data:

```python
name = Text(to_right_of='Name:', below=Image(alt='Profile picture')).value
```

For a full list of element types and their properties, please see
[the source code](https://github.com/mherrmann/helium/blob/0667ddb9be531367a0d707ad8f5fcfb75c528521/helium/__init__.py#L470-L1008).

## Finding elements relative to others

You already saw in the previous section how `above=...` and `to_right_of=...`
let you find elements relative to other elements. You can similarly use
`below=...` and `to_left_of`. Here are some more examples.

```python
Text(above='Balance', below='Transactions').value
Link(to_right_of='Invoice:')
Image(to_right_of=Link('Sign in', below=Text('Navigation')))
```

## Waiting for elements to appear (or other conditions)

Use
[`wait_until(...)`](https://github.com/mherrmann/helium/blob/0667ddb9be531367a0d707ad8f5fcfb75c528521/helium/__init__.py#L410)
to wait for a condition to become true. For example:

```python
wait_until(Button('Download').exists)
```

But you can also use this to wait for an arbitrary condition:

```python
wait_until(lambda: TextField('Balance').value == '$2M')
```

## jQuery-style selectors

Sometimes, you do need to fall back to using HTML IDs, CSS Selectors or XPaths
to identify an element on the web page. Helium's
[`S(...)`](https://github.com/mherrmann/helium/blob/0667ddb9be531367a0d707ad8f5fcfb75c528521/helium/__init__.py#L568)
predicate lets you do this. The parameter you pass to it is interpreted as
follows:

* If it starts with an ``@``, then it identifies elements by HTML ``name``.
  Eg. ``S("@btnName")`` identifies an element with ``name="btnName"``.
* If it starts with ``//``, then Helium interprets it as an XPath.
* Otherwise, Helium interprets it as a CSS selector. This in particular
  lets you write ``S("#myId")`` to identify an element with ``id="myId"``,
  or ``S(".myClass")`` to identify elements with ``class="myClass"``.

As before, you can combine `S(...)` with other functions such as
`click(S(...))`, or use it to extract data. For an example of this, see
[below](#finding-all-elements).

## Combining Helium and Selenium's APIs

All Helium does is translate your high-level commands into low-level Selenium
function calls. Because of this, you can freely mix Selenium and Helium. For
example:

```python
# A Helium function:
driver = start_chrome()
# A Selenium API:
driver.execute_script("alert('Hi!');")
```

You can also get / set the Selenium WebDriver which Helium uses via
[`get_driver()`](https://github.com/mherrmann/helium/blob/0667ddb9be531367a0d707ad8f5fcfb75c528521/helium/__init__.py#L104)
and
[`set_driver(...)`](https://github.com/mherrmann/helium/blob/0667ddb9be531367a0d707ad8f5fcfb75c528521/helium/__init__.py#L97).

With the WebDriver instance, you can execute any Selenium commands you want.

To use Helium's API's to obtain Selenium `WebElement`s, use the `.web_element`
property of Helium's various GUI elements. For instance:

```python
# Get the CSS class of the "Helium" link:
Link('Helium').web_element.get_attribute('class')
```

Here, `.get_attribute(...)` is a Selenium API.


## Finding all elements

The `.web_element` property and the `S(...)` predicate are particularly useful
for extracting multiple pieces of data from a web page. To do this, you can use
Helium's [`find_all(...)`](https://github.com/mherrmann/helium/blob/0667ddb9be531367a0d707ad8f5fcfb75c528521/helium/__init__.py#L281) function.
As its name implies, it lets you find all occurrences of an element on a page.
For example:

```python
email_cells = find_all(S("table > tr > td", below="Email"))
emails = [cell.web_element.text for cell in email_cells]
```

## Implicit waits

When you issue a command such as `click('Download')`, Helium by default waits
up to 10 seconds for the respective element to appear. This feature is called
"implicit waiting". You can change the 10 second default to a different value
via the
[`Config` class](https://github.com/mherrmann/helium/blob/0667ddb9be531367a0d707ad8f5fcfb75c528521/helium/__init__.py#L437):

```python
Config.implicit_wait_secs = 30
```

However, before you do this, it may be better to add explicit waits to your
code, such as `wait_until(Button('Download').exists)`.

## Alerts

The
[`Alert` class](https://github.com/mherrmann/helium/blob/0667ddb9be531367a0d707ad8f5fcfb75c528521/helium/__init__.py#L970)
lets you interface with JavaScript popup boxes. Use `Alert().accept()`,
`Alert().dismiss()` to click "Ok" or "Cancel", `Alert().text` to read the
message shown, or `write(..., into=Alert())` to enter a value.

## File uploads, drag and drop, combo boxes, popups

Use
[`attach_file(...)`](https://github.com/mherrmann/helium/blob/0667ddb9be531367a0d707ad8f5fcfb75c528521/helium/__init__.py#L388),
[`drag_file(...)`](https://github.com/mherrmann/helium/blob/0667ddb9be531367a0d707ad8f5fcfb75c528521/helium/__init__.py#L375),
[`drag(...)`](https://github.com/mherrmann/helium/blob/0667ddb9be531367a0d707ad8f5fcfb75c528521/helium/__init__.py#L252),
[`select(...)`](https://github.com/mherrmann/helium/blob/0667ddb9be531367a0d707ad8f5fcfb75c528521/helium/__init__.py#L362),
[`switch_to(...)`](https://github.com/mherrmann/helium/blob/0667ddb9be531367a0d707ad8f5fcfb75c528521/helium/__init__.py#L1057).

## Clicking at x, y coordinates

Sometimes, you may want to click at a specific `(x, y)` coordinate, or at an
offset of an element. 

### Create a `Point` to specify the coordinates and use it to click:

```python
from helium import click
point = Point(x=100, y=200)
click(point)  # Clicks at (100, 200)
```

### Adjusting a Point by a offset
You can modify a point's position using addition or subtraction by a delta:

```python
delta = (20, -10)
click(Point(100, 200) + delta)  # Clicks at (120, 190)
```

See the
[`Point` class](https://github.com/mherrmann/helium/blob/0667ddb9be531367a0d707ad8f5fcfb75c528521/helium/__init__.py#L1010)
for more.

## Taking a screenshot

Use Selenium's API:

```python
get_driver().save_screenshot(r'C:\screenshot.png')
```

Note the leading `r`. This is required because the string contains a backslash
`\`.


================================================
FILE: docs/conf.py
================================================
import os
import sys
from datetime import date

sys.path.insert(0, os.path.abspath('..'))


# -- Project information -----------------------------------------------------

project = 'helium'
copyright = '%s, Michael Herrmann' % date.today().year
author = 'Michael Herrmann'

# Also update ../setup.py when you change this:
release = '7.0.0'


# -- General configuration ---------------------------------------------------

# Add any Sphinx extension module names here, as strings. They can be
# extensions coming with Sphinx (named 'sphinx.ext.*') or your custom
# ones.
extensions = ['sphinx.ext.autodoc', 'sphinx.ext.githubpages', 'sphinx_rtd_theme']

html_theme = "sphinx_rtd_theme"

# Add any paths that contain templates here, relative to this directory.
templates_path = ['_templates']

# List of patterns, relative to source directory, that match files and
# directories to ignore when looking for source files.
# This pattern also affects html_static_path and html_extra_path.
exclude_patterns = ['_build', 'Thumbs.db', '.DS_Store']

autodoc_member_order = 'bysource'

================================================
FILE: docs/contributors.rst
================================================
Contributors to this project
============================
..
    Please use this format to add your contributions to this file
    `SocialUsernameName <Profile-Url>`_ (**Your Name**) - *Description of your contribution in a few words*

- `mherrmann <https://github.com/mherrmann>`_ (**Michael Herrmann**) - *Project creator and maintainer*
- `IgnisDa <https://github.com/IgnisDa>`_ (**Diptesh Choudhuri**) - *Documentation maintainer*



================================================
FILE: docs/index.rst
================================================
Welcome to Helium's documentation!
==================================

Helium is a Python library for automating web sites. It is based on
`Selenium-python <https://selenium-python.readthedocs.io/>`_.
Selenium is great, but difficult to use. Helium wraps around Selenium to give
you a simpler API. Helium's name comes from being a lighter chemical element
than Selenium.

For a quick overview of Helium's features, please see
`the project home page <https://github.com/mherrmann/helium>`_.
Here, in the documentation, you will find a more comprehensive reference.


.. toctree::
   :maxdepth: 2
   :caption: Contents:

   installation.rst
   api.rst
   contributors.rst



Indices and tables
==================

* :ref:`genindex`
* :ref:`search`


================================================
FILE: docs/installation.rst
================================================
Installation
============

To install Helium, you need Python 3 and Chrome or Firefox.

If you already know Python, then the following command should be all you need:


.. code-block:: bash

    pip install helium

Otherwise - Hi! I would recommend you create a virtual environment in the
current directory. Any libraries you download (such as Helium) will be placed
there. Enter the following into a command prompt:

.. code-block:: bash

    python3 -m venv venv

This creates a virtual environment in the `venv/` directory. To activate it:

.. code-block:: bash

    # On Mac/Linux, bash shell:
    source venv/bin/activate
    # On Windows:
    call venv\Scripts\activate.bat

Then, install Helium using `pip`:

.. code-block:: bash

    python -m pip install helium

Now enter :code:`python` into the command prompt and the command :code:`from
helium import *` and you are ready to get started!


================================================
FILE: docs/make.bat
================================================
@ECHO OFF

pushd %~dp0

REM Command file for Sphinx documentation

if "%SPHINXBUILD%" == "" (
	set SPHINXBUILD=sphinx-build
)
set SOURCEDIR=.
set BUILDDIR=_build

if "%1" == "" goto help

%SPHINXBUILD% >NUL 2>NUL
if errorlevel 9009 (
	echo.
	echo.The 'sphinx-build' command was not found. Make sure you have Sphinx
	echo.installed, then set the SPHINXBUILD environment variable to point
	echo.to the full path of the 'sphinx-build' executable. Alternatively you
	echo.may add the Sphinx directory to PATH.
	echo.
	echo.If you don't have Sphinx installed, grab it from
	echo.http://sphinx-doc.org/
	exit /b 1
)

%SPHINXBUILD% -M %1 %SOURCEDIR% %BUILDDIR% %SPHINXOPTS% %O%
goto end

:help
%SPHINXBUILD% -M help %SOURCEDIR% %BUILDDIR% %SPHINXOPTS% %O%

:end
popd


================================================
FILE: helium/__init__.py
================================================
"""
Helium's API is contained in module ``helium``. It is a simple Python API that
makes specifying web automation cases as simple as describing them to someone
looking over their shoulder at a screen.

The public functions and classes of Helium are listed below. If you wish to use
Helium functions in your Python scripts you can import them from the
``helium`` module::

	from helium import *
"""
from collections import namedtuple, OrderedDict
from copy import copy
from helium._impl import APIImpl
from helium._impl.util.html import get_easily_readable_snippet
from helium._impl.util.inspect_ import repr_args
from selenium.common import NoSuchElementException
from selenium.webdriver.common.keys import Keys

import helium._impl

def start_chrome(url=None, headless=False, maximize=False, options=None):
	"""
	:param url: URL to open.
	:type url: str
	:param headless: Whether to start Chrome in headless mode.
	:type headless: bool
	:param maximize: Whether to maximize the Chrome window.
	                 Ignored when `headless` is set to `True`.
	:type maximize: bool
	:param options: ChromeOptions to use for starting the browser
	:type options: :py:class:`selenium.webdriver.ChromeOptions`

	Starts an instance of Google Chrome::

		start_chrome()

	You can optionally open a URL::

		start_chrome("google.com")

	The `headless` switch lets you prevent the browser window from appearing on
	your screen::

		start_chrome(headless=True)
		start_chrome("google.com", headless=True)

	For more advanced configuration, use the `options` parameter::

		from selenium.webdriver import ChromeOptions
		options = ChromeOptions()
		options.add_argument('--proxy-server=1.2.3.4:5678')
		options.set_capability('goog:loggingPrefs', {'performance': 'ALL'})
		start_chrome(options=options)

	When no compatible ChromeDriver is found on your `PATH`, then `start_chrome`
	automatically downloads it using Selenium Manager.

	On shutdown of the Python interpreter, Helium terminates the ChromeDriver
	process but does not close the browser itself. If you want to close the
	browser at the end of your script, use the following command::

		kill_browser()
	"""
	return _get_api_impl().start_chrome_impl(url, headless, maximize, options)

def start_firefox(url=None, headless=False, options=None, profile=None):
	"""
	:param url: URL to open.
	:type url: str
	:param headless: Whether to start Firefox in headless mode.
	:type headless: bool
	:param options: FirefoxOptions to use for starting the browser.
	:type options: :py:class:`selenium.webdriver.FirefoxOptions`
	:param profile: FirefoxProfile to use for starting the browser.
	:type profile: :py:class:`selenium.webdriver.FirefoxProfile`

	Starts an instance of Firefox::

		start_firefox()

	If this doesn't work for you, then it may be that Helium's copy of
	geckodriver is not compatible with your version of Firefox. To fix this,
	place a copy of geckodriver on your `PATH`.

	You can optionally open a URL::

		start_firefox("google.com")

	The `headless` switch lets you prevent the browser window from appearing on
	your screen::

		start_firefox(headless=True)
		start_firefox("google.com", headless=True)

	For more advanced configuration, use the `options` parameter::

		from selenium.webdriver import FirefoxOptions
		options = FirefoxOptions()
		options.add_argument("--width=2560")
		options.add_argument("--height=1440")
		start_firefox(options=options)

	To set proxy, useragent, etc. (ie. things you find in about:config), use the
	`profile` parameter::

		from selenium.webdriver import FirefoxProfile
		profile = FirefoxProfile()
		SOCKS5_PROXY_HOST = "0.0.0.0"
		PROXY_PORT = 0
		profile.set_preference("network.proxy.type", 1)
		profile.set_preference("network.proxy.socks", SOCKS5_PROXY_HOST)
		profile.set_preference("network.proxy.socks_port", PROXY_PORT)
		profile.set_preference("network.proxy.socks_remote_dns", True)
		profile.set_preference("network.proxy.socks_version", 5)
		profile.set_preference("network.proxy.no_proxies_on", "localhost,
		                       10.20.30.40")
		USER_AGENT = "Mozilla/5.0 ..."
		profile.set_preference("general.useragent.override", USER_AGENT)
		start_firefox(profile=profile)

	On shutdown of the Python interpreter, Helium cleans up all resources used
	for controlling the browser (such as the geckodriver process), but does
	not close the browser itself. If you want to terminate the browser at the
	end of your script, use the following command::

		kill_browser()
	"""
	return _get_api_impl().start_firefox_impl(url, headless, options, profile)

def go_to(url):
	"""
	:param url: URL to open.
	:type url: str

	Opens the specified URL in the current web browser window. For instance::

		go_to("google.com")
	"""
	_get_api_impl().go_to_impl(url)

def set_driver(driver):
	"""
	Sets the Selenium WebDriver used to execute Helium commands. See also
	:py:func:`get_driver`.
	"""
	_get_api_impl().set_driver_impl(driver)

def get_driver():
	"""
	Returns the Selenium WebDriver currently used by Helium to execute all
	commands. Each Helium command such as ``click("Login")`` is translated to a
	sequence of Selenium commands that are issued to this driver.
	"""
	return _get_api_impl().get_driver_impl()

def write(text, into=None):
	"""
	:param text: The text to be written.
	:type text: one of str, unicode
	:param into: The element to write into.
	:type into: one of str, unicode, :py:class:`HTMLElement`, \
:py:class:`selenium.webdriver.remote.webelement.WebElement`, :py:class:`Alert`

	Types the given text into the active window. If parameter 'into' is given,
	writes the text into the text field or element identified by that parameter.
	Common examples of 'write' are::

		write("Hello World!")
		write("user12345", into="Username:")
		write("Michael", into=Alert("Please enter your name"))
	"""
	_get_api_impl().write_impl(text, into)

def press(key):
	"""
	:param key: Key or combination of keys to be pressed.

	Presses the given key or key combination. To press a normal letter key such
	as 'a' simply call `press` for it::

		press('a')

	You can also simulate the pressing of upper case characters that way::

		press('A')

	The special keys you can press are those given by Selenium's class
	:py:class:`selenium.webdriver.common.keys.Keys`. Helium makes all those keys
	available through its namespace, so you can just use them without having to
	refer to :py:class:`selenium.webdriver.common.keys.Keys`. For instance, to
	press the Enter key::

		press(ENTER)

	To press multiple keys at the same time, concatenate them with `+`. For
	example, to press Control + a, call::

		press(CONTROL + 'a')
	"""
	_get_api_impl().press_impl(key)

NULL         = Keys.NULL
CANCEL       = Keys.CANCEL
HELP         = Keys.HELP
BACK_SPACE   = Keys.BACK_SPACE
TAB          = Keys.TAB
CLEAR        = Keys.CLEAR
RETURN       = Keys.RETURN
ENTER        = Keys.ENTER
SHIFT        = Keys.SHIFT
LEFT_SHIFT   = Keys.LEFT_SHIFT
CONTROL      = Keys.CONTROL
LEFT_CONTROL = Keys.LEFT_CONTROL
ALT          = Keys.ALT
LEFT_ALT     = Keys.LEFT_ALT
PAUSE        = Keys.PAUSE
ESCAPE       = Keys.ESCAPE
SPACE        = Keys.SPACE
PAGE_UP      = Keys.PAGE_UP
PAGE_DOWN    = Keys.PAGE_DOWN
END          = Keys.END
HOME         = Keys.HOME
LEFT         = Keys.LEFT
ARROW_LEFT   = Keys.ARROW_LEFT
UP           = Keys.UP
ARROW_UP     = Keys.ARROW_UP
RIGHT        = Keys.RIGHT
ARROW_RIGHT  = Keys.ARROW_RIGHT
DOWN         = Keys.DOWN
ARROW_DOWN   = Keys.ARROW_DOWN
INSERT       = Keys.INSERT
DELETE       = Keys.DELETE
SEMICOLON    = Keys.SEMICOLON
EQUALS       = Keys.EQUALS
NUMPAD0      = Keys.NUMPAD0
NUMPAD1      = Keys.NUMPAD1
NUMPAD2      = Keys.NUMPAD2
NUMPAD3      = Keys.NUMPAD3
NUMPAD4      = Keys.NUMPAD4
NUMPAD5      = Keys.NUMPAD5
NUMPAD6      = Keys.NUMPAD6
NUMPAD7      = Keys.NUMPAD7
NUMPAD8      = Keys.NUMPAD8
NUMPAD9      = Keys.NUMPAD9
MULTIPLY     = Keys.MULTIPLY
ADD          = Keys.ADD
SEPARATOR    = Keys.SEPARATOR
SUBTRACT     = Keys.SUBTRACT
DECIMAL      = Keys.DECIMAL
DIVIDE       = Keys.DIVIDE
F1           = Keys.F1
F2           = Keys.F2
F3           = Keys.F3
F4           = Keys.F4
F5           = Keys.F5
F6           = Keys.F6
F7           = Keys.F7
F8           = Keys.F8
F9           = Keys.F9
F10          = Keys.F10
F11          = Keys.F11
F12          = Keys.F12
META         = Keys.META
COMMAND      = Keys.COMMAND

def click(element):
	"""
	:param element: The element or point to click.
	:type element: str, unicode, :py:class:`HTMLElement`, \
:py:class:`selenium.webdriver.remote.webelement.WebElement` or :py:class:`Point`

	Clicks on the given element or point. Common examples are::

		click("Sign in")
		click(Button("OK"))
		click(Point(200, 300))
		click(ComboBox("File type").top_left + (50, 0))
	"""
	_get_api_impl().click_impl(element)

def doubleclick(element):
	"""
	:param element: The element or point to click.
	:type element: str, unicode, :py:class:`HTMLElement`, \
:py:class:`selenium.webdriver.remote.webelement.WebElement` or :py:class:`Point`

	Performs a double-click on the given element or point. For example::

		doubleclick("Double click here")
		doubleclick(Image("Directories"))
		doubleclick(Point(200, 300))
		doubleclick(TextField("Username").top_left - (0, 20))
	"""
	_get_api_impl().doubleclick_impl(element)

def drag(element, to):
	"""
	:param element: The element or point to drag.
	:type element: str, unicode, :py:class:`HTMLElement`, \
:py:class:`selenium.webdriver.remote.webelement.WebElement` or :py:class:`Point`
	:param to: The element or point to drag to.
	:type to: str, unicode, :py:class:`HTMLElement`, \
:py:class:`selenium.webdriver.remote.webelement.WebElement` or :py:class:`Point`

	Drags the given element or point to the given location. For example::

		drag("Drag me!", to="Drop here.")

	The dragging is performed by hovering the mouse cursor over ``element``,
	pressing and holding the left mouse button, moving the mouse cursor over
	``to``, and then releasing the left mouse button again.

	This function is exclusively used for dragging elements inside one web page.
	If you wish to drag a file from the hard disk onto the browser window (eg.
	to initiate a file upload), use function :py:func:`drag_file`.
	"""
	_get_api_impl().drag_impl(element, to)

def press_mouse_on(element):
	_get_api_impl().press_mouse_on_impl(element)

def release_mouse_over(element):
	_get_api_impl().release_mouse_over_impl(element)

def find_all(predicate):
	"""
	Lets you find all occurrences of the given GUI element predicate. For
	instance, the following statement returns a list of all buttons with label
	"Open"::

		find_all(Button("Open"))

	Other examples are::

		find_all(Window())
		find_all(TextField("Address line 1"))

	The function returns a list of elements of the same type as the passed-in
	parameter. For instance, ``find_all(Button(...))`` yields a list whose
	elements are of type :py:class:`Button`.

	In a typical usage scenario, you want to pick out one of the occurrences
	returned by :py:func:`find_all`. In such cases, :py:func:`list.sort` can
	be very useful. For example, to find the leftmost "Open" button, you can
	write::

		buttons = find_all(Button("Open"))
		leftmost_button = sorted(buttons, key=lambda button: button.x)[0]
	"""
	return _get_api_impl().find_all_impl(predicate)

def scroll_down(num_pixels=100):
	"""
	Scrolls down the page the given number of pixels.
	"""
	_get_api_impl().scroll_down_impl(num_pixels)

def scroll_up(num_pixels=100):
	"""
	Scrolls the the page up the given number of pixels.
	"""
	_get_api_impl().scroll_up_impl(num_pixels)

def scroll_right(num_pixels=100):
	"""
	Scrolls the page to the right the given number of pixels.
	"""
	_get_api_impl().scroll_right_impl(num_pixels)

def scroll_left(num_pixels=100):
	"""
	Scrolls the page to the left the given number of pixels.
	"""
	_get_api_impl().scroll_left_impl(num_pixels)

def hover(element):
	"""
	:param element: The element or point to hover.
	:type element: str, unicode, :py:class:`HTMLElement`, \
:py:class:`selenium.webdriver.remote.webelement.WebElement` or :py:class:`Point`

	Hovers the mouse cursor over the given element or point. For example::

		hover("File size")
		hover(Button("OK"))
		hover(Link("Download"))
		hover(Point(200, 300))
		hover(ComboBox("File type").top_left + (50, 0))
	"""
	_get_api_impl().hover_impl(element)

def rightclick(element):
	"""
	:param element: The element or point to click.
	:type element: str, unicode, :py:class:`HTMLElement`, \
:py:class:`selenium.webdriver.remote.webelement.WebElement` or :py:class:`Point`

	Performs a right click on the given element or point. For example::

		rightclick("Something")
		rightclick(Point(200, 300))
		rightclick(Image("captcha"))
	"""
	_get_api_impl().rightclick_impl(element)

def select(combo_box, value):
	"""
	:param combo_box: The combo box whose value should be changed.
	:type combo_box: str, unicode or :py:class:`ComboBox`
	:param value: The visible value of the combo box to be selected.

	Selects a value from a combo box. For example::

		select("Language", "English")
		select(ComboBox("Language"), "English")
	"""
	_get_api_impl().select_impl(combo_box, value)

def drag_file(file_path, to):
	"""
	Simulates the dragging of a file from the computer over the browser window
	and dropping it over the given element. This allows, for example, to attach
	files to emails in Gmail::

		click("COMPOSE")
		write("example@gmail.com", into="To")
		write("Email subject", into="Subject")
		drag_file(r"C:\\Documents\\notes.txt", to="Drop files here")
	"""
	_get_api_impl().drag_file_impl(file_path, to)

def attach_file(file_path, to=None):
	"""
	:param file_path: The path of the file to be attached.
	:param to: The file input element to which the file should be attached.

	Allows attaching a file to a file input element. For instance::

		attach_file("c:/test.txt", to="Please select a file:")

	The file input element is identified by its label. If you omit the ``to=``
	parameter, then Helium attaches the file to the first file input element it
	finds on the page.
	"""
	_get_api_impl().attach_file_impl(file_path, to=to)

def refresh():
	"""
	Refreshes the current page. If an alert dialog is open, then Helium first
	closes it.
	"""
	_get_api_impl().refresh_impl()

def wait_until(condition_fn, timeout_secs=10, interval_secs=0.5):
	"""
	:param condition_fn: A function taking no arguments that represents the \
	condition to be waited for.
	:param timeout_secs: The timeout, in seconds, after which the condition is \
	deemed to have failed.
	:param interval_secs: The interval, in seconds, at which the condition \
	function is polled to determine whether the wait has succeeded.

	Waits until the given condition function evaluates to true. This is most
	commonly used to wait for an element to exist::

		wait_until(Text("Finished!").exists)

	More elaborate conditions are also possible using Python lambda
	expressions. For instance, to wait until a text no longer exists::

		wait_until(lambda: not Text("Uploading...").exists())

	``wait_until`` raises
	:py:class:`selenium.common.exceptions.TimeoutException` if the condition is
	not satisfied within the given number of seconds. The parameter
	``interval_secs`` specifies the number of seconds Helium waits between
	evaluating the condition function.
	"""
	_get_api_impl().wait_until_impl(condition_fn, timeout_secs, interval_secs)

class Config:
	"""
	This class contains Helium's run-time configuration. To modify Helium's
	behaviour, simply assign to the properties of this class. For instance::

		Config.implicit_wait_secs = 0
	"""
	implicit_wait_secs = 10
	"""
	``implicit_wait_secs`` is Helium's analogue to Selenium's
	``.implicitly_wait(secs)``. Suppose you have a script that executes the
	following command::

		>>> click("Download")

	If the "Download" element is not immediately available, then Helium waits up
	to ``implicit_wait_secs`` for it to appear before raising a
	``LookupError``. This is useful in situations where the page takes slightly
	longer to load, or a GUI element only appears after a certain time.

	To disable Helium's implicit waits, simply execute::

		Config.implicit_wait_secs = 0

	Helium's implicit waits do not affect commands :py:func:`find_all` or
	:py:func:`GUIElement.exists`. Note also that setting
	``implicit_wait_secs`` does not affect the underlying Selenium driver
	(see :py:func:`get_driver`).

	For the best results, it is recommended to not use Selenium's
	``.implicitly_wait(...)`` in conjunction with Helium.
	"""

class GUIElement:
	def __init__(self):
		self._driver = _get_api_impl().require_driver()
		self._args = []
		self._kwargs = OrderedDict()
		self._impl_cached = None
	def exists(self):
		"""
		Evaluates to true if this GUI element exists.
		"""
		return self._impl.exists()
	def with_impl(self, impl):
		result = copy(self)
		result._impl = impl
		return result
	@property
	def _impl(self):
		if self._impl_cached is None:
			impl_class = \
				getattr(helium._impl, self.__class__.__name__ + 'Impl')
			self._impl_cached = impl_class(
				self._driver, *self._args, **self._kwargs
			)
		return self._impl_cached
	@_impl.setter
	def _impl(self, value):
		self._impl_cached = value
	def __repr__(self):
		return self._repr_constructor_args(self._args, self._kwargs)
	def _repr_constructor_args(self, args=None, kwargs=None):
		if args is None:
			args = []
		if kwargs is None:
			kwargs = {}
		return '%s(%s)' % (
			self.__class__.__name__,
			repr_args(self.__init__, args, kwargs, repr)
		)
	def _is_bound(self):
		return self._impl_cached is not None and self._impl_cached._is_bound()

class HTMLElement(GUIElement):
	def __init__(
			self, below=None, to_right_of=None, above=None, to_left_of=None
	):
		super(HTMLElement, self).__init__()
		self._kwargs['below'] = below
		self._kwargs['to_right_of'] = to_right_of
		self._kwargs['above'] = above
		self._kwargs['to_left_of'] = to_left_of
	@property
	def width(self):
		"""
		The width of this HTML element, in pixels.
		"""
		return self._impl.width
	@property
	def height(self):
		"""
		The height of this HTML element, in pixels.
		"""
		return self._impl.height
	@property
	def x(self):
		"""
		The x-coordinate on the page of the top-left point of this HTML element.
		"""
		return self._impl.x
	@property
	def y(self):
		"""
		The y-coordinate on the page of the top-left point of this HTML element.
		"""
		return self._impl.y
	@property
	def top_left(self):
		"""
		The top left corner of this element, as a :py:class:`helium.Point`.
		This point has exactly the coordinates given by this element's `.x` and
		`.y` properties. `top_left` is for instance useful for clicking at an
		offset of an element::

			click(Button("OK").top_left + (30, 15))
		"""
		return self._impl.top_left
	@property
	def web_element(self):
		"""
		The Selenium WebElement corresponding to this element.
		"""
		return self._impl.web_element
	def __repr__(self):
		if self._is_bound():
			try:
				element_html = self.web_element.get_attribute('outerHTML')
			except NoSuchElementException:
				# This can happen when the element is not in the current iframe.
				# We could call `self._impl.first_occurrence.get_attribute(...)`
				# instead of `self.web_element.get_attribute(...)` to avoid it.
				# However, this would change the current frame. That seems too
				# surprising a side effect for a repr(...) call. So we instead
				# catch the error and fall back to the super implementation
				# further below.
				pass
			else:
				return get_easily_readable_snippet(element_html)
		return super(HTMLElement, self).__repr__()

class S(HTMLElement):
	"""
	:param selector: The selector used to identify the HTML element(s).

	A jQuery-style selector for identifying HTML elements by ID, name, CSS
	class, CSS selector or XPath. For example: Say you have an element with
	ID "myId" on a web page, such as ``<div id="myId" .../>``.
	Then you can identify this element using ``S`` as follows::

		S("#myId")

	The parameter which you pass to ``S(...)`` is interpreted by Helium
	according to these rules:

	 * If it starts with an ``@``, then it identifies elements by HTML ``name``.
	   Eg. ``S("@btnName")`` identifies an element with ``name="btnName"``.
	 * If it starts with ``//``, then Helium interprets it as an XPath.
	 * Otherwise, Helium interprets it as a CSS selector. This in particular
	   lets you write ``S("#myId")`` to identify an element with ``id="myId"``,
	   or ``S(".myClass")`` to identify elements with ``class="myClass"``.

	``S`` also makes it possible to read plain text data from a web page. For
	example, suppose you have a table of people's email addresses. Then you
	can read the list of email addresses as follows::

		email_cells = find_all(S("table > tr > td", below="Email"))
		emails = [cell.web_element.text for cell in email_cells]

	Where ``email`` is the column header (``<th>Email</th>``). Similarly to
	``below`` and ``to_right_of``, the keyword parameters ``above`` and
	``to_left_of`` can be used to search for elements above and to the left
	of other web elements.
	"""
	def __init__(self, selector, below=None, to_right_of=None, above=None,
			to_left_of=None):
		super(S, self).__init__(
			below=below, to_right_of=to_right_of, above=above,
			to_left_of=to_left_of
		)
		self._args.append(selector)

class Text(HTMLElement):
	"""
	Lets you identify any text or label on a web page. This is most useful for
	checking whether a particular text exists::

		if Text("Do you want to proceed?").exists():
		    click("Yes")

	``Text`` also makes it possible to read plain text data from a web page. For
	example, suppose you have a table of people's email addresses. Then you
	can read John's email addresses as follows::

		Text(below="Email", to_right_of="John").value

	Similarly to ``below`` and ``to_right_of``, the keyword parameters ``above``
	and ``to_left_of`` can be used to search for texts above and to the left of
	other web elements.
	"""
	def __init__(
			self, text=None, below=None, to_right_of=None, above=None,
			to_left_of=None
	):
		super(Text, self).__init__(
			below=below, to_right_of=to_right_of, above=above,
			to_left_of=to_left_of
		)
		self._args.append(text)
	@property
	def value(self):
		"""
		Returns the current value of this Text object.
		"""
		return self._impl.value

class Link(HTMLElement):
	"""
	Lets you identify a link on a web page. A typical usage of ``Link`` is::

		click(Link("Sign in"))

	You can also read a ``Link``'s properties. This is most typically used to
	check for a link's existence before clicking on it::

		if Link("Sign in").exists():
		    click(Link("Sign in"))

	When there are multiple occurrences of a link on a page, you can
	disambiguate between them using the keyword parameters ``below``,
	``to_right_of``, ``above`` and ``to_left_of``. For instance::

		click(Link("Block User", to_right_of="John Doe"))
	"""
	def __init__(
			self, text=None, below=None, to_right_of=None, above=None,
			to_left_of=None
	):
		super(Link, self).__init__(
			below=below, to_right_of=to_right_of, above=above,
			to_left_of=to_left_of
		)
		self._args.append(text)
	@property
	def href(self):
		"""
		Returns the URL of the page the link goes to.
		"""
		return self._impl.href

class ListItem(HTMLElement):
	"""
	Lets you identify a list item (HTML ``<li>`` element) on a web page. This is
	often useful for interacting with elements of a navigation bar::

		click(ListItem("News Feed"))

	In other cases such as an automated test, you might want to query the
	properties of a ``ListItem``. For example, the following line checks whether
	a list item with text "List item 1" exists, and raises an error if not::

		assert ListItem("List item 1").exists()

	When there are multiple occurrences of a list item on a page, you can
	disambiguate between them using the keyword parameters ``below``,
	``to_right_of``, ``above`` and ``to_left_of``. For instance::

		click(ListItem("List item 1", below="My first list:"))
	"""
	def __init__(
			self, text=None, below=None, to_right_of=None, above=None,
			to_left_of=None
	):
		super(ListItem, self).__init__(
			below=below, to_right_of=to_right_of, above=above,
			to_left_of=to_left_of
		)
		self._args.append(text)

class Button(HTMLElement):
	"""
	Lets you identify a button on a web page. A typical usage of ``Button`` is::

		click(Button("Log In"))

	``Button`` also lets you read a button's properties. For example, the
	following snippet clicks button "OK" only if it exists::

		if Button("OK").exists():
		    click(Button("OK"))

	When there are multiple occurrences of a button on a page, you can
	disambiguate between them using the keyword parameters ``below``,
	``to_right_of``, ``above`` and ``to_left_of``. For instance::

		click(Button("Log In", below=TextField("Password")))
	"""
	def __init__(
			self, text=None, below=None, to_right_of=None, above=None,
			to_left_of=None
	):
		super(Button, self).__init__(
			below=below, to_right_of=to_right_of, above=above,
			to_left_of=to_left_of
		)
		self._args.append(text)
	def is_enabled(self):
		"""
		Returns true if this UI element can currently be interacted with.
		"""
		return self._impl.is_enabled()

class Image(HTMLElement):
	"""
	Lets you identify an image (HTML ``<img>`` element) on a web page.
	Typically, this is done via the image's alt text. For instance::

		click(Image(alt="Helium Logo"))

	You can also query an image's properties. For example, the following snippet
	clicks on the image with alt text "Helium Logo" only if it exists::

		if Image("Helium Logo").exists():
		    click(Image("Helium Logo"))

	When there are multiple occurrences of an image on a page, you can
	disambiguate between them using the keyword parameters ``below``,
	``to_right_of``, ``above`` and ``to_left_of``. For instance::

		click(Image("Helium Logo", to_left_of=ListItem("Download")))
	"""
	def __init__(
			self, alt=None, below=None, to_right_of=None, above=None,
			to_left_of=None
	):
		super(Image, self).__init__(
			below=below, to_right_of=to_right_of, above=above,
			to_left_of=to_left_of
		)
		self._args.append(alt)

class TextField(HTMLElement):
	"""
	Lets you identify a text field on a web page. This is most typically done to
	read the value of a text field. For example::

		TextField("First name").value

	This returns the value of the "First name" text field. If it is empty, the
	empty string "" is returned.

	When there are multiple occurrences of a text field on a page, you can
	disambiguate between them using the keyword parameters ``below``,
	``to_right_of``, ``above`` and ``to_left_of``. For instance::

		TextField("Address line 1", below="Billing Address:").value
	"""
	def __init__(
			self, label=None, below=None, to_right_of=None, above=None,
			to_left_of=None
	):
		super(TextField, self).__init__(
			below=below, to_right_of=to_right_of, above=above,
			to_left_of=to_left_of
		)
		self._args.append(label)
	@property
	def value(self):
		"""
		Returns the current value of this text field. '' if there is no value.
		"""
		return self._impl.value
	def is_enabled(self):
		"""
		Returns true if this UI element can currently be interacted with.

		The difference between a text field being 'enabled' and 'editable' is
		mostly visual: If a text field is not enabled, it is usually greyed out,
		whereas if it is not editable it looks normal. See also ``is_editable``.
		"""
		return self._impl.is_enabled()
	def is_editable(self):
		"""
		Returns true if the value of this UI element can be modified.

		The difference between a text field being 'enabled' and 'editable' is
		mostly visual: If a text field is not enabled, it is usually greyed out,
		whereas if it is not editable it looks normal. See also ``is_enabled``.
		"""
		return self._impl.is_editable()

class ComboBox(HTMLElement):
	"""
	Lets you identify a combo box on a web page. This can for instance be used
	to determine the current value of a combo box::

		ComboBox("Language").value

	A ComboBox may be *editable*, which means that it is possible to type in
	arbitrary values in addition to selecting from a predefined drop-down list
	of values. The property :py:func:`ComboBox.is_editable` can be used to
	determine whether this is the case for a particular combo box instance.

	When there are multiple occurrences of a combo box on a page, you can
	disambiguate between them using the keyword parameters ``below``,
	``to_right_of``, ``above`` and ``to_left_of``. For instance::

		select(ComboBox(to_right_of="John Doe", below="Status"), "Active")

	This sets the Status of John Doe to Active on the page.
	"""
	def __init__(
			self, label=None, below=None, to_right_of=None, above=None,
			to_left_of=None
	):
		super(ComboBox, self).__init__(
			below=below, to_right_of=to_right_of, above=above,
			to_left_of=to_left_of
		)
		self._args.append(label)
	def is_editable(self):
		"""
		Returns whether this combo box allows entering an arbitrary text in
		addition to selecting predefined values from a drop-down list.
		"""
		return self._impl.is_editable()
	@property
	def value(self):
		"""
		Returns the currently selected combo box value.
		"""
		return self._impl.value
	@property
	def options(self):
		"""
		Returns a list of all possible options available to choose from in the
		ComboBox.
		"""
		return self._impl.options

class CheckBox(HTMLElement):
	"""
	Lets you identify a check box on a web page. To tick a currently unselected
	check box, use::

		click(CheckBox("I agree"))

	``CheckBox`` also lets you read the properties of a check box. For example,
	the method :py:func:`CheckBox.is_checked` can be used to only click a check
	box if it isn't already checked::

		if not CheckBox("I agree").is_checked():
		    click(CheckBox("I agree"))

	When there are multiple occurrences of a check box on a page, you can
	disambiguate between them using the keyword parameters ``below``,
	``to_right_of``, ``above`` and ``to_left_of``. For instance::

		click(CheckBox("Stay signed in", below=Button("Sign in")))
	"""
	def __init__(
			self, label=None, below=None, to_right_of=None, above=None,
			to_left_of=None
	):
		super(CheckBox, self).__init__(
			below=below, to_right_of=to_right_of, above=above,
			to_left_of=to_left_of
		)
		self._args.append(label)
	def is_enabled(self):
		"""
		Returns True if this GUI element can currently be interacted with.
		"""
		return self._impl.is_enabled()
	def is_checked(self):
		"""
		Returns True if this GUI element is checked (selected).
		"""
		return self._impl.is_checked()

class RadioButton(HTMLElement):
	"""
	Lets you identify a radio button on a web page. To select a currently
	unselected radio button, use::

		click(RadioButton("Windows"))

	``RadioButton`` also lets you read the properties of a radio button. For
	example, the method :py:func:`RadioButton.is_selected` can be used to only
	click a radio button if it isn't already selected::

		if not RadioButton("Windows").is_selected():
		    click(RadioButton("Windows"))

	When there are multiple occurrences of a radio button on a page, you can
	disambiguate between them using the keyword parameters ``below``,
	``to_right_of``, ``above`` and ``to_left_of``. For instance::

		click(RadioButton("I accept", below="License Agreement"))
	"""
	def __init__(
			self, label=None, below=None, to_right_of=None, above=None,
			to_left_of=None
	):
		super(RadioButton, self).__init__(
			below=below, to_right_of=to_right_of, above=above,
			to_left_of=to_left_of
		)
		self._args.append(label)
	def is_selected(self):
		"""
		Returns true if this radio button is selected.
		"""
		return self._impl.is_selected()

class Window(GUIElement):
	"""
	Lets you identify individual windows of the currently open browser session.
	"""
	def __init__(self, title=None):
		super(Window, self).__init__()
		self._args.append(title)
	@property
	def title(self):
		"""
		Returns the title of this Window.
		"""
		return self._impl.title
	@property
	def handle(self):
		"""
		Returns the Selenium driver window handle assigned to this window. Note
		that this window handle is simply an abstract identifier and bears no
		relationship to the corresponding operating system handle (HWND on
		Windows).
		"""
		return self._impl.handle
	def __repr__(self):
		if self._is_bound():
			return self._repr_constructor_args([self.title])
		else:
			return super(Window, self).__repr__()

class Alert(GUIElement):
	"""
	Lets you identify and interact with JavaScript alert boxes.
	"""
	def __init__(self, search_text=None):
		super(Alert, self).__init__()
		self._args.append(search_text)
	@property
	def text(self):
		"""
		The text displayed in the alert box.
		"""
		return self._impl.text
	def accept(self):
		"""
		Accepts this alert. This typically corresponds to clicking the "OK"
		button inside the alert. The typical way to use this method is::

			>>> Alert().accept()

		This accepts the currently open alert.
		"""
		self._impl.accept()
	def dismiss(self):
		"""
		Dismisses this alert. This typically corresponds to clicking the
		"Cancel" or "Close" button of the alert. The typical way to use this
		method is::

			>>> Alert().dismiss()

		This dismisses the currently open alert.
		"""
		self._impl.dismiss()
	def __repr__(self):
		if self._is_bound():
			return self._repr_constructor_args([self.text])
		else:
			return super(Alert, self).__repr__()

class Point(namedtuple('Point', ['x', 'y'])):
	"""
	A clickable point. To create a ``Point`` at an offset of an existing point,
	use ``+`` and ``-``::

		>>> point = Point(x=10, y=25)
		>>> point + (10, 0)
		Point(x=20, y=25)
		>>> point - (0, 10)
		Point(x=10, y=15)
	"""
	def __new__(cls, x=0, y=0):
		return cls.__bases__[0].__new__(cls, x, y)
	def __init__(self, x=0, y=0):
		# tuple is immutable so we can't do anything here. The initialization
		# happens in __new__(...) above.
		pass
	@property
	def x(self):
		"""
		The x coordinate of the point.
		"""
		return self[0]
	@property
	def y(self):
		"""
		The y coordinate of the point.
		"""
		return self[1]
	def __eq__(self, other):
		return (self.x, self.y) == other
	def __ne__(self, other):
		return not self == other
	def __hash__(self):
		return self.x + 7 * self.y
	def __add__(self, delta):
		dx, dy = delta
		return Point(self.x + dx, self.y + dy)
	def __radd__(self, delta):
		return self.__add__(delta)
	def __sub__(self, delta):
		dx, dy = delta
		return Point(self.x - dx, self.y - dy)
	def __rsub__(self, delta):
		x, y = delta
		return Point(x - self.x, y - self.y)

def switch_to(window):
	"""
	:param window: The title (string) of a browser window or a \
:py:class:`Window` object

	Switches to the given browser window. For example::

		switch_to("Google")

	This searches for a browser window whose title contains "Google", and
	activates it.

	If there are multiple windows with the same title, then you can use
	:py:func:`find_all` to find all open windows, pick out the one you want and
	pass that to ``switch_to``. For example, the following snippet switches to
	the first window in the list of open windows::

		switch_to(find_all(Window())[0])
	"""
	_get_api_impl().switch_to_impl(window)

def kill_browser():
	"""
	Closes the current browser with all associated windows and potentially open
	dialogs. Dialogs opened as a response to the browser closing (eg. "Are you
	sure you want to leave this page?") are also ignored and closed.

	This function is most commonly used to close the browser at the end of an
	automation run::

		start_chrome()
		...
		# Close Chrome:
		kill_browser()
	"""
	_get_api_impl().kill_browser_impl()

def highlight(element):
	"""
	:param element: The element to highlight.

	Highlights the given element on the webpage by drawing a red rectangle
	around it. This is useful for debugging purposes. For example::

		highlight("Helium")
		highlight(Button("Sign in"))
	"""
	_get_api_impl().highlight_impl(element)

def _get_api_impl():
	global _API_IMPL
	if _API_IMPL is None:
		_API_IMPL = APIImpl()
	return _API_IMPL

_API_IMPL = None

================================================
FILE: helium/_impl/__init__.py
================================================
from copy import copy
from helium._impl.match_type import PREFIX_IGNORE_CASE
from helium._impl.selenium_wrappers import WebElementWrapper, \
	WebDriverWrapper, FrameIterator, FramesChangedWhileIterating
from helium._impl.util.dictionary import inverse
from helium._impl.util.system import is_windows, get_canonical_os_name
from helium._impl.util.xpath import lower, predicate, predicate_or
from inspect import getfullargspec, ismethod, isfunction
from selenium.common.exceptions import UnexpectedAlertPresentException, \
	ElementNotVisibleException, MoveTargetOutOfBoundsException, \
	WebDriverException, StaleElementReferenceException, \
	NoAlertPresentException, NoSuchWindowException
from selenium.webdriver.common.by import By
from selenium.webdriver.firefox.service import Service as ServiceFirefox
from selenium.webdriver.remote.webelement import WebElement
from selenium.webdriver.support.wait import WebDriverWait
from selenium.webdriver.support.ui import Select
from selenium.webdriver import Chrome, ChromeOptions, Firefox, FirefoxOptions
from time import sleep, time

import atexit
import re

def might_spawn_window(f):
	def f_decorated(self, *args, **kwargs):
		driver = self.require_driver()
		if driver.is_ie() and AlertImpl(driver).exists():
			# Accessing .window_handles in IE when an alert is present raises an
			# UnexpectedAlertPresentException. When DesiredCapability
			# 'unexpectedAlertBehaviour' is not 'ignore' (the default is
			# 'dismiss'), this leads to the alert being closed. Since we don't
			# want to unintentionally close alert dialogs, we therefore do not
			# access .window_handles in IE when an alert is present.
			return f(self, *args, **kwargs)
		window_handles_before = driver.window_handles[:]
		result = f(self, *args, **kwargs)
		# As above, don't access .window_handles in IE if an alert is present:
		if not (driver.is_ie() and AlertImpl(driver).exists()):
			if driver.is_firefox():
				# Unlike Chrome, Firefox does not wait for new windows to open.
				# Give it a little time to do so:
				sleep(.2)
			new_window_handles = [
				h for h in driver.window_handles
				if h not in window_handles_before
			]
			if new_window_handles:
				driver.switch_to.window(new_window_handles[0])
		return result
	return f_decorated

def handle_unexpected_alert(f):
	def f_decorated(*args, **kwargs):
		try:
			return f(*args, **kwargs)
		except UnexpectedAlertPresentException:
			raise UnexpectedAlertPresentException(
				"This command is not supported when an alert is present. To "
				"accept the alert (this usually corresponds to clicking 'OK') "
				"use `Alert().accept()`. To dismiss the alert (ie. 'cancel' "
				"it), use `Alert().dismiss()`. If the alert contains a text "
				"field, you can use write(...) to set its value. "
				"Eg.: `write('hi there!')`."
			)
	return f_decorated

class APIImpl:
	DRIVER_REQUIRED_MESSAGE = \
		"This operation requires a browser window. Please call one of " \
		"the following functions first:\n" \
		" * start_chrome()\n" \
		" * start_firefox()\n" \
		" * set_driver(...)"
	def __init__(self):
		self.driver = None
	def start_firefox_impl(
		self, url=None, headless=False, options=None, profile=None
	):
		firefox_driver = self._start_firefox_driver(headless, options, profile)
		return self._start(firefox_driver, url)
	def _start_firefox_driver(self, headless, options, profile):
		firefox_options = FirefoxOptions() if options is None else options
		if headless:
			firefox_options.add_argument('--headless')
		kwargs = {
			'options': firefox_options
		}
		if profile:
			firefox_options.profile = profile
		service_log_path = 'nul' if is_windows() else '/dev/null'
		service = ServiceFirefox(log_path=service_log_path)
		result = Firefox(service=service, **kwargs)
		return result
	def start_chrome_impl(
		self, url=None, headless=False, maximize=False, options=None
	):
		chrome_driver = self._start_chrome_driver(headless, maximize, options)
		return self._start(chrome_driver, url)
	def _start_chrome_driver(self, headless, maximize, options):
		chrome_options = self._get_chrome_options(headless, maximize, options)
		result = Chrome(options=chrome_options)
		atexit.register(self._kill_service, result.service)
		return result
	def _get_chrome_options(self, headless, maximize, options):
		result = ChromeOptions() if options is None else options
		# Prevent Chrome's debug logs from appearing in our console window:
		result.add_experimental_option('excludeSwitches', ['enable-logging'])
		if headless:
			result.add_argument('--headless')
		elif maximize:
			result.add_argument('--start-maximized')

		# Chrome 140.0.7339.185 or earlier introduced password leak detection.
		# Writing leaked credentials into an input field sometimes brings up a
		# blocking browser notification "The password you just used was found in
		# a data breach". Prevent this:
		prefs = dict(result.experimental_options.get('prefs', {}))
		if 'profile.password_manager_leak_detection' not in prefs:
			prefs['profile.password_manager_leak_detection'] = False
			result.add_experimental_option('prefs', prefs)

		return result
	def _kill_service(self, service):
		old = service.send_remote_shutdown_command
		service.send_remote_shutdown_command = lambda: None
		try:
			service.stop()
		finally:
			service.send_remote_shutdown_command = old
	def _start(self, browser, url=None):
		self.set_driver_impl(browser)
		if url is not None:
			self.go_to_impl(url)
		return self.get_driver_impl()
	@might_spawn_window
	@handle_unexpected_alert
	def go_to_impl(self, url):
		if '://' not in url:
			url = 'http://' + url
		self.require_driver().get(url)
	def set_driver_impl(self, driver):
		self.driver = WebDriverWrapper(driver)
	def get_driver_impl(self):
		if self.driver is not None:
			return self.driver.unwrap()
	@might_spawn_window
	@handle_unexpected_alert
	def write_impl(self, text, into=None):
		if into is not None:
			from helium import GUIElement
			if isinstance(into, GUIElement):
				into = into._impl
		self._handle_alerts(
			self._write_no_alert, self._write_with_alert, text, into=into
		)
	def _write_no_alert(self, text, into=None):
		if into:
			if isinstance(into, str):
				into = TextFieldImpl(self.require_driver(), into)
			def _write(elt):
				if hasattr(elt, 'clear') and callable(elt.clear):
					elt.clear()
				elt.send_keys(text)
			self._manipulate(into, _write)
		else:
			self.require_driver().switch_to.active_element.send_keys(text)
	def _write_with_alert(self, text, into=None):
		if into is None:
			into = AlertImpl(self.require_driver())
		if not isinstance(into, AlertImpl):
			raise UnexpectedAlertPresentException(
				"into=%r is not allowed when an alert is present." % into
			)
		into._write(text)
	def _handle_alerts(self, no_alert, with_alert, *args, **kwargs):
		driver = self.require_driver()
		if not AlertImpl(driver).exists():
			return no_alert(*args, **kwargs)
		return with_alert(*args, **kwargs)
	@might_spawn_window
	@handle_unexpected_alert
	def press_impl(self, key):
		self.require_driver().switch_to.active_element.send_keys(key)
	def click_impl(self, element):
		self._perform_mouse_action(element, self._click)
	def doubleclick_impl(self, element):
		self._perform_mouse_action(element, self._doubleclick)
	def hover_impl(self, element):
		self._perform_mouse_action(element, self._hover)
	def rightclick_impl(self, element):
		self._perform_mouse_action(element, self._rightclick)
	def press_mouse_on_impl(self, element):
		self._perform_mouse_action(element, self._press_mouse_on)
	def release_mouse_over_impl(self, element):
		self._perform_mouse_action(element, self._release_mouse_over)
	def _click(self, selenium_elt, offset):
		self._move_to_element(selenium_elt, offset).click().perform()
	def _doubleclick(self, selenium_elt, offset):
		self._move_to_element(selenium_elt, offset).double_click().perform()
	def _hover(self, selenium_elt, offset):
		self._move_to_element(selenium_elt, offset).perform()
	def _rightclick(self, selenium_elt, offset):
		self._move_to_element(selenium_elt, offset).context_click().perform()
	def _press_mouse_on(self, selenium_elt, offset):
		self._move_to_element(selenium_elt, offset).click_and_hold().perform()
	def _release_mouse_over(self, selenium_elt, offset):
		self._move_to_element(selenium_elt, offset).release().perform()
	def _move_to_element(self, element, offset):
		result = self.require_driver().action()
		if offset is not None:
			result.move_to_element_with_offset(
				element,
				offset[0] - element.size['width'] / 2,
				offset[1] - element.size['height'] / 2
			)
		else:
			result.move_to_element(element)
		return result
	def drag_impl(self, element, to):
		with DragHelper(self) as drag_helper:
			self._perform_mouse_action(element, drag_helper.start_dragging)
			self._perform_mouse_action(to, drag_helper.drop_on_target)
	@might_spawn_window
	@handle_unexpected_alert
	def _perform_mouse_action(self, element, action):
		element, offset = self._unwrap_clickable_element(element)
		self._manipulate(element, lambda wew: action(wew.unwrap(), offset))
	def _unwrap_clickable_element(self, elt):
		from helium import HTMLElement, Point
		offset = None
		if isinstance(elt, str):
			elt = ClickableText(self.require_driver(), elt)
		elif isinstance(elt, HTMLElement):
			elt = elt._impl
		elif isinstance(elt, Point):
			elt, offset = self._point_to_element_and_offset(elt)
		return elt, offset
	def _point_to_element_and_offset(self, point):
		driver = self.require_driver()
		element = WebElementWrapper(driver.execute_script(
			'return document.elementFromPoint(%r, %r);' % (point.x, point.y)
		))
		offset = point - (element.location.left, element.location.top)
		if offset == (0, 0) and driver.is_firefox():
			# In some CSS settings (eg. test_point.html), the (0, 0) point of
			# buttons in Firefox is not clickable! The reason for this is that
			# Firefox styles buttons to not be perfect squares, but have an
			# indent in the corners. This workaround makes `click(btn.top_left)`
			# work even when this happens:
			offset = (1, 1)
		return element, offset
	@handle_unexpected_alert
	def find_all_impl(self, predicate):
		while True:
			try:
				return [
					predicate.with_impl(bound_gui_elt_impl)
					for bound_gui_elt_impl in predicate._impl.iter_all()
				]
			except FramesChangedWhileIterating:
				# Try again.
				pass
	def scroll_down_impl(self, num_pixels):
		self._scroll_by(0, num_pixels)
	def scroll_up_impl(self, num_pixels):
		self._scroll_by(0, -num_pixels)
	def scroll_right_impl(self, num_pixels):
		self._scroll_by(num_pixels, 0)
	def scroll_left_impl(self, num_pixels):
		self._scroll_by(-num_pixels, 0)
	@handle_unexpected_alert
	def _scroll_by(self, dx_pixels, dy_pixels):
		self.require_driver().execute_script(
			'window.scrollBy(arguments[0], arguments[1]);', dx_pixels, dy_pixels
		)
	@might_spawn_window
	@handle_unexpected_alert
	def select_impl(self, combo_box, value):
		from helium import ComboBox
		if isinstance(combo_box, str):
			combo_box = ComboBoxImpl(self.require_driver(), combo_box)
		elif isinstance(combo_box, ComboBox):
			combo_box = combo_box._impl
		def _select(web_element):
			if isinstance(web_element, WebElementWrapper):
				web_element = web_element.unwrap()
			Select(web_element).select_by_visible_text(value)
		self._manipulate(combo_box, _select)
	def _manipulate(self, gui_or_web_elt, action):
		driver = self.require_driver()
		if hasattr(gui_or_web_elt, 'perform') \
			and callable(gui_or_web_elt.perform):
			driver.last_manipulated_element = gui_or_web_elt.perform(action)
		else:
			if isinstance(gui_or_web_elt, WebElement):
				gui_or_web_elt = WebElementWrapper(gui_or_web_elt)
			action(gui_or_web_elt)
			driver.last_manipulated_element = gui_or_web_elt
	@handle_unexpected_alert
	def drag_file_impl(self, file_path, to):
		to, _ = self._unwrap_clickable_element(to)
		drag_and_drop = DragAndDropFile(self.require_driver(), file_path)
		drag_and_drop.begin()
		try:
			# Some web apps (Gmail in particular) only register for the 'drop'
			# event when user has dragged the file over the document. We
			# therefore simulate this dragging over the document first:
			drag_and_drop.drag_over_document()
			self._manipulate(to, lambda elt: drag_and_drop.drop_on(elt))
		finally:
			drag_and_drop.end()
	@might_spawn_window
	@handle_unexpected_alert
	def attach_file_impl(self, file_path, to=None):
		from helium import Point
		driver = self.require_driver()
		if to is None:
			to = FileInput(driver)
		elif isinstance(to, str):
			to = FileInput(driver, to)
		elif isinstance(to, Point):
			to, _ = self._point_to_element_and_offset(to)
		self._manipulate(to, lambda elt: elt.send_keys(file_path))
	def refresh_impl(self):
		self._handle_alerts(
			self._refresh_no_alert, self._refresh_with_alert
		)
	def _refresh_no_alert(self):
		self.require_driver().refresh()
	def _refresh_with_alert(self):
		AlertImpl(self.require_driver()).accept()
		self._refresh_no_alert()
	def wait_until_impl(self, condition_fn, timeout_secs=10, interval_secs=0.5):
		if ismethod(condition_fn):
			is_bound = condition_fn.__self__ is not None
			args_spec = getfullargspec(condition_fn).args
			unfilled_args = len(args_spec) - (1 if is_bound else 0)
		else:
			if not isfunction(condition_fn):
				condition_fn = condition_fn.__call__
			args_spec = getfullargspec(condition_fn).args
			unfilled_args = len(args_spec)
		def condition(driver):
			try:
				return condition_fn(driver) if unfilled_args else condition_fn()
			except FramesChangedWhileIterating:
				return False
		wait = WebDriverWait(
			self.require_driver().unwrap(), timeout_secs,
			poll_frequency=interval_secs
		)
		wait.until(condition)
	@handle_unexpected_alert
	def switch_to_impl(self, window):
		driver = self.require_driver()
		from helium import Window
		if isinstance(window, str):
			window = WindowImpl(driver, window)
		elif isinstance(window, Window):
			window = window._impl
		driver.switch_to.window(window.handle)
	def kill_browser_impl(self):
		self.require_driver().quit()
		self.driver = None
	@handle_unexpected_alert
	def highlight_impl(self, element):
		driver = self.require_driver()
		from helium import HTMLElement, Text
		if isinstance(element, str):
			element = Text(element)
		if isinstance(element, HTMLElement):
			element = element._impl
		try:
			element = element.first_occurrence
		except AttributeError:
			pass
		previous_style = element.get_attribute("style")
		if isinstance(element, WebElementWrapper):
			element = element.unwrap()
		driver.execute_script(
			"arguments[0].setAttribute("
				"'style', 'border: 2px solid red; font-weight: bold;'"
			");", element
		)
		driver.execute_script(
			"var target = arguments[0];"
			"var previousStyle = arguments[1];"
			"setTimeout("
				"function() {"
					"target.setAttribute('style', previousStyle);"
				"}, 2000"
			");", element, previous_style
		)
	def require_driver(self):
		if not self.driver:
			raise RuntimeError(self.DRIVER_REQUIRED_MESSAGE)
		return self.driver

class DragHelper:
	def __init__(self, api_impl):
		self.api_impl = api_impl
		self.is_html_5_drag = None
	def __enter__(self):
		self._execute_script(
			"window.helium = {};"
			"window.helium.dragHelper = {"
			"    createEvent: function(type) {"
			"        var event = document.createEvent('CustomEvent');"
			"        event.initCustomEvent(type, true, true, null);"
			"        event.dataTransfer = {"
			"            data: {},"
			"            setData: function(type, val) {"
			"                this.data[type] = val;"
			"            },"
			"            getData: function(type) {"
			"                return this.data[type];"
			"            }"
			"        };"
			"        return event;"
			"    }"
			"};"
		)
		return self
	def start_dragging(self, element, offset):
		if self._attempt_html_5_drag(element):
			self.is_html_5_drag = True
		else:
			self.api_impl._press_mouse_on(element, offset)
	def drop_on_target(self, target, offset):
		if self.is_html_5_drag:
			self._complete_html_5_drag(target)
		else:
			self.api_impl._release_mouse_over(target, offset)
	def _attempt_html_5_drag(self, element_to_drag):
		return self._execute_script(
			"var source = arguments[0];"
			"function getDraggableParent(element) {"
			"    var previousParent = null;"
			"    while (element != null && element != previousParent) {"
			"        previousParent = element;"
			"        if ('draggable' in element) {"
			"            var draggable = element.draggable;"
			"            if (draggable === true)"
			"                return element;"
			"            if (typeof draggable == 'string' "
			"                    || draggable instanceof String)"
			"                if (draggable.toLowerCase() == 'true')"
			"                    return element;"
			"        }"
			"        element = element.parentNode;"
			"    }"
			"    return null;"
			"}"
			"var draggableParent = getDraggableParent(source);"
			"if (draggableParent == null)"
			"    return false;"
			"window.helium.dragHelper.draggedElement = draggableParent;"
			"var dragStart = window.helium.dragHelper.createEvent('dragstart');"
			"source.dispatchEvent(dragStart);"
			"window.helium.dragHelper.dataTransfer = dragStart.dataTransfer;"
			"return true;",
			element_to_drag
		)
	def _complete_html_5_drag(self, on):
		self._execute_script(
			"var target = arguments[0];"
			"var drop = window.helium.dragHelper.createEvent('drop');"
			"drop.dataTransfer = window.helium.dragHelper.dataTransfer;"
			"target.dispatchEvent(drop);"
			"var dragEnd = window.helium.dragHelper.createEvent('dragend');"
			"dragEnd.dataTransfer = window.helium.dragHelper.dataTransfer;"
			"window.helium.dragHelper.draggedElement.dispatchEvent(dragEnd);",
			on
		)
	def __exit__(self, *_):
		self._execute_script("delete window.helium;")
	def _execute_script(self, script, *args):
		return self.api_impl.require_driver().execute_script(script, *args)

class DragAndDropFile:
	def __init__(self, driver, file_path):
		self.driver = driver
		self.file_path = file_path
		self.file_input_element = None
		self.dragover_event = None
	def begin(self):
		self._create_file_input_element()
		try:
			self.file_input_element.send_keys(self.file_path)
		except:
			self.end()
			raise
	def _create_file_input_element(self):
		# The input needs to be visible to Selenium to allow sending keys to it
		# in Firefox and IE.
		# According to http://stackoverflow.com/questions/6101461/
		# Selenium criteria whether an element is visible or not are the
		# following:
		#  - visibility != hidden
		#  - display != none (is also checked against every parent element)
		#  - opacity != 0
		#  - height and width are both > 0
		#  - for an input, the attribute type != hidden
		# So let's make sure its all good!
		self.file_input_element = self.driver.execute_script(
			"var input = document.createElement('input');"
			"input.type = 'file';"
			"input.style.display = 'block';"
			"input.style.opacity = '1';"
			"input.style.visibility = 'visible';"
			"input.style.height = '1px';"
			"input.style.width = '1px';"
			"if (document.body.childElementCount > 0) { "
			"  document.body.insertBefore(input, document.body.childNodes[0]);"
			"} else { "
			"  document.body.appendChild(input);"
			"}"
			"return input;"
		)
	def drag_over_document(self):
		# According to the HTML5 spec, we need to dispatch the dragenter event
		# once, and then the dragover event continuously, every 350+-200ms:
		# http://www.w3.org/html/wg/drafts/html/master/editing.html#current-drag
		# -operation
		# Especially IE implements this spec very tightly, and considers the
		# dragging to be over if no dragover event occurs for more than ~1sec.
		# We thus need to ensure that we keep dispatching the dragover event.

		# This line used to read `_dispatch_event(..., to='document')`. However,
		# this doesn't work when adding a photo to a tweet on Twitter.
		# Dispatching the event to document.body fixes this, and also works for
		# Gmail:
		self._dispatch_event('dragenter', to='document.body')
		self.dragover_event = self._prepare_continuous_event(
			'dragover', 'document', interval_msecs=300
		)
		self.dragover_event.start()
	def _dispatch_event(self, event_name, to):
		script, args = self._prepare_dispatch_event(event_name, to)
		self.driver.execute_script(script, *args)
	def _prepare_continuous_event(self, event_name, to, interval_msecs):
		script, args = self._prepare_dispatch_event(event_name, to)
		return JavaScriptInterval(self.driver, script, args, interval_msecs)
	def _prepare_dispatch_event(self, event_name, to):
		script = \
			"var files = arguments[0].files;" \
			"var items = [];" \
			"var types = [];" \
			"for (var i = 0; i < files.length; i++) {" \
			"   items[i] = {kind: 'file', type: files[i].type};" \
			"   types[i] = 'Files';" \
			"}" \
			"var event = document.createEvent('CustomEvent');" \
			"event.initCustomEvent(arguments[1], true, true, 0);" \
			"event.dataTransfer = {" \
			"	files: files," \
			"	items: items," \
			"	types: types" \
			"};" \
			"arguments[2].dispatchEvent(event);"
		if isinstance(to, str):
			script = script.replace('arguments[2]', to)
			args = self.file_input_element, event_name,
		else:
			args = self.file_input_element, event_name, to.unwrap()
		return script, args
	def drop_on(self, target):
		self.dragover_event.stop()
		self._dispatch_event('drop', to=target)
	def end(self):
		if self.file_input_element is not None:
			self.driver.execute_script(
				"arguments[0].parentNode.removeChild(arguments[0]);",
				self.file_input_element
			)
		self.file_input_element = None

class JavaScriptInterval:
	def __init__(self, driver, script, args, interval_msecs):
		self.driver = driver
		self.script = script
		self.args = args
		self.interval_msecs = interval_msecs
		self._interval_id = None
	def start(self):
		setinterval_script = (
			"var originalArguments = arguments;"
			"return setInterval(function() {"
			"	arguments = originalArguments;"
			"	%s"
			"}, %d);"
		) % (self.script, self.interval_msecs)
		self._interval_id = \
			self.driver.execute_script(setinterval_script, *self.args)
	def stop(self):
		self.driver.execute_script(
			"clearInterval(arguments[0]);", self._interval_id
		)
		self._interval_id = None

class GUIElementImpl:
	def __init__(self, driver):
		self._bound_occurrence = None
		self._driver = driver
	def iter_all(self, ignore_frame_changes=False):
		if self._is_bound():
			yield self
		else:
			while True:
				try:
					for occurrence in self.iter_all_occurrences():
						yield self.bound_to_occurrence(occurrence)
				except FramesChangedWhileIterating:
					if not ignore_frame_changes:
						raise
				break
	def _is_bound(self):
		return self._bound_occurrence is not None
	def iter_all_occurrences(self):
		raise NotImplementedError()
	def bound_to_occurrence(self, occurrence):
		result = copy(self)
		result._bound_occurrence = occurrence
		return result
	def exists(self):
		try:
			next(self.iter_all(ignore_frame_changes=True))
		except StopIteration:
			return False
		else:
			return True
	@property
	def first_occurrence(self):
		if not self._is_bound():
			self._bind_to_first_occurrence()
		return self._bound_occurrence
	def _bind_to_first_occurrence(self):
		self.perform(lambda _: None)
		# _perform_no_wait(...) below now sets _bound_occurrence.
	def perform(self, action):
		from helium import Config
		end_time = time() + Config.implicit_wait_secs
		# Try to perform `action` at least once:
		result = self._perform_no_wait(action)
		while result is None and time() < end_time:
			result = self._perform_no_wait(action)
		if result is not None:
			return result
		raise LookupError()
	def _perform_no_wait(self, action):
		for bound_gui_elt_impl in self.iter_all(ignore_frame_changes=True):
			occurrence = bound_gui_elt_impl.first_occurrence
			try:
				action(occurrence)
			except Exception as e:
				if not self.should_ignore_exception(e):
					raise
			else:
				self._bound_occurrence = occurrence
				return occurrence
	def should_ignore_exception(self, exception):
		if isinstance(exception, ElementNotVisibleException):
			return True
		if isinstance(exception, MoveTargetOutOfBoundsException):
			return True
		if isinstance(exception, StaleElementReferenceException):
			return True
		if isinstance(exception, WebDriverException):
			msg = exception.msg
			if 'is not clickable at point' in msg \
				and 'Other element would receive the click' in msg:
				# This can happen when the element has moved.
				return True
		return False

class HTMLElementImpl(GUIElementImpl):
	def __init__(
			self, driver, below=None, to_right_of=None, above=None,
			to_left_of=None
	):
		super(HTMLElementImpl, self).__init__(driver)
		self.below = self._unwrap_element(below)
		self.to_right_of = self._unwrap_element(to_right_of)
		self.above = self._unwrap_element(above)
		self.to_left_of = self._unwrap_element(to_left_of)
		self.matches = PREFIX_IGNORE_CASE()
	def find_anywhere_in_curr_frame(self):
		raise NotImplementedError()
	@property
	def width(self):
		return self.first_occurrence.location.width
	@property
	def height(self):
		return self.first_occurrence.location.height
	@property
	def x(self):
		return self.first_occurrence.location.left
	@property
	def y(self):
		return self.first_occurrence.location.top
	@property
	def top_left(self):
		from helium import Point
		return Point(self.x, self.y)
	@property
	def web_element(self):
		return self.first_occurrence.unwrap()
	def iter_all_occurrences(self):
		self._handle_closed_window()
		self._driver.switch_to.default_content()
		already_yielded = set()
		for frame_index in FrameIterator(self._driver):
			for occurrence in self._find_all_in_curr_frame():
				if occurrence.target in already_yielded:
					# We have seen this element before, but its frame had a
					# different index. This means that the frames have changed.
					# Abort:
					return
				occurrence.frame_index = frame_index
				yield occurrence
				already_yielded.add(occurrence.target)
	def _handle_closed_window(self):
		window_handles = self._driver.window_handles
		try:
			curr_window_handle = self._driver.current_window_handle
		except NoSuchWindowException:
			window_has_been_closed = True
		else:
			window_has_been_closed = curr_window_handle not in window_handles
		if window_has_been_closed:
			self._driver.switch_to.window(window_handles[0])
	def _find_all_in_curr_frame(self):
		search_regions = self._get_search_regions_in_curr_frame()
		for occurrence in self.find_anywhere_in_curr_frame():
			if not occurrence.is_displayed():
				continue
			if self._is_in_any_search_region(occurrence, search_regions):
				yield occurrence
	def _get_search_regions_in_curr_frame(self):
		result = []
		if self.below:
			result.append([
				elt.location.is_above
				for elt in self._resolve_in_curr_frame(self.below)
			])
		if self.to_right_of:
			result.append([
				elt.location.is_to_left_of
				for elt in self._resolve_in_curr_frame(self.to_right_of)
			])
		if self.above:
			result.append([
				elt.location.is_below
				for elt in self._resolve_in_curr_frame(self.above)
			])
		if self.to_left_of:
			result.append([
				elt.location.is_to_right_of
				for elt in self._resolve_in_curr_frame(self.to_left_of)
			])
		return result
	def _resolve_in_curr_frame(self, element):
		if element._is_bound():
			return [element.first_occurrence]
		return element._find_all_in_curr_frame()
	def _is_in_any_search_region(self, element, search_regions):
		for direction in search_regions:
			found = False
			for search_region in direction:
				if search_region(element.location):
					found = True
					break
			if not found:
				return False
		return True
	def _is_enabled(self):
		"""
		Useful for subclasses.
		"""
		return self.first_occurrence.get_attribute('disabled') is None
	def _unwrap_element(self, element):
		if isinstance(element, str):
			return TextImpl(self._driver, element)
		from helium import HTMLElement
		if isinstance(element, HTMLElement):
			return element._impl
		return element

class SImpl(HTMLElementImpl):
	def __init__(self, driver, selector, **kwargs):
		super(SImpl, self).__init__(driver, **kwargs)
		self.selector = selector
	def find_anywhere_in_curr_frame(self):
		wrap = lambda web_elements: list(map(WebElementWrapper, web_elements))
		if self.selector.startswith('@'):
			return wrap(self._driver.find_elements(By.NAME, self.selector[1:]))
		if self.selector.startswith('//'):
			return wrap(self._driver.find_elements(By.XPATH, self.selector))
		return wrap(self._driver.find_elements(By.CSS_SELECTOR, self.selector))

class HTMLElementIdentifiedByXPath(HTMLElementImpl):
	def find_anywhere_in_curr_frame(self):
		x_path = self.get_xpath()
		return self._sort_search_result(
			list(map(
				WebElementWrapper, self._driver.find_elements(By.XPATH, x_path)
			))
		)
	def _sort_search_result(self, search_result):
		keys_to_result_items = []
		for web_elt in search_result:
			try:
				key = self.get_sort_index(web_elt)
			except StaleElementReferenceException:
				pass
			else:
				keys_to_result_items.append((key, web_elt))
		sort_key = lambda tpl: tpl[0]
		keys_to_result_items.sort(key=sort_key)
		result_item = lambda tpl: tpl[1]
		return list(map(result_item, keys_to_result_items))
	def get_xpath(self):
		raise NotImplementedError()
	def get_sort_index(self, web_element):
		return self._driver.get_distance_to_last_manipulated(web_element) + 1

class HTMLElementContainingText(HTMLElementIdentifiedByXPath):
	def __init__(self, driver, text=None, **kwargs):
		super(HTMLElementContainingText, self).__init__(driver, **kwargs)
		self.search_text = text
	def get_xpath(self):
		xpath_base = "//" + self.get_xpath_node_selector() + \
					 predicate(self.matches.xpath('.', self.search_text))
		return '%s[not(self::script)][not(.%s)]' % (xpath_base, xpath_base)
	def get_xpath_node_selector(self):
		return '*'

class TextImpl(HTMLElementContainingText):
	def __init__(self, driver, text=None, include_free_text=True, **kwargs):
		super(TextImpl, self).__init__(driver, text, **kwargs)
		self.include_free_text = include_free_text
	@property
	def value(self):
		return self.first_occurrence.text
	def get_xpath(self):
		button_impl = ButtonImpl(self._driver, self.search_text)
		link_impl = LinkImpl(self._driver, self.search_text)
		components = [
			self._get_search_text_xpath(),
			button_impl.get_input_button_xpath(),
			link_impl.get_xpath()
		]
		if self.search_text and self.include_free_text:
			components.append(
				FreeText(self._driver, self.search_text).get_xpath()
			)
		return ' | '.join(components)
	def _get_search_text_xpath(self):
		if self.search_text:
			result = super(TextImpl, self).get_xpath()
		else:
			no_descendant_with_same_text = \
				"not(.//*[normalize-space(.)=normalize-space(self::*)])"
			result = '//*[text() and %s]' % no_descendant_with_same_text
		return result + "[not(self::option)]" + \
		       ("" if self.include_free_text else "[count(*) <= 1]")

class FreeText(HTMLElementContainingText):
	def get_xpath_node_selector(self):
		return 'text()'
	def get_xpath(self):
		return super(FreeText, self).get_xpath() + '/..'

class LinkImpl(HTMLElementContainingText):
	def get_xpath_node_selector(self):
		return 'a'
	def get_xpath(self):
		return super(LinkImpl, self).get_xpath() + ' | ' + \
			   "//a" + \
			   predicate(self.matches.xpath('@title', self.search_text)) + \
			   ' | ' + "//*[@role='link']" + \
			   predicate(self.matches.xpath('.', self.search_text))
	@property
	def href(self):
		return self.web_element.get_attribute('href')

class ListItemImpl(HTMLElementContainingText):
	def get_xpath_node_selector(self):
		return 'li'

class ButtonImpl(HTMLElementContainingText):
	def get_xpath_node_selector(self):
		return 'button'
	def is_enabled(self):
		aria_disabled = self.first_occurrence.get_attribute('aria-disabled')
		return self._is_enabled() \
			and (not aria_disabled or aria_disabled.lower() == 'false')
	def get_xpath(self):
		has_aria_label = self.matches.xpath('@aria-label', self.search_text)
		has_text = self.matches.xpath('.', self.search_text)
		has_text_or_aria_label = predicate_or(has_aria_label, has_text)
		return ' | '.join([
			super(ButtonImpl, self).get_xpath(), self.get_input_button_xpath(),
			"//*[@role='button']" + has_text_or_aria_label,
			"//button" + predicate(has_aria_label)
		])
	def get_input_button_xpath(self):
		if self.search_text:
			has_value = self.matches.xpath('@value', self.search_text)
			has_label = self.matches.xpath('@label', self.search_text)
			has_aria_label = self.matches.xpath('@aria-label', self.search_text)
			has_title = self.matches.xpath('@title', self.search_text)
			has_text = \
				predicate_or(has_value, has_label, has_aria_label, has_title)
		else:
			has_text = ''
		return "//input[@type='submit' or @type='button']" + has_text

class ImageImpl(HTMLElementIdentifiedByXPath):
	def __init__(self, driver, alt, **kwargs):
		super(ImageImpl, self).__init__(driver, **kwargs)
		self.alt = alt
	def get_xpath(self):
		return "//img" + predicate(self.matches.xpath('@alt', self.alt))

class LabelledElement(HTMLElementImpl):
	SECONDARY_SEARCH_DIMENSION_PENALTY_FACTOR = 1.5
	def __init__(self, driver, label=None, **kwargs):
		super(LabelledElement, self).__init__(driver, **kwargs)
		self.label = label
	def find_anywhere_in_curr_frame(self):
		if not self.label:
			result = self._find_elts()
		else:
			labels = TextImpl(
				self._driver, self.label, include_free_text=False
			).find_anywhere_in_curr_frame()
			if labels:
				result = list(self._filter_elts_belonging_to_labels(
					self._find_elts(), labels
				))
			else:
				result = self._find_elts_by_free_text()
		return sorted(result, key=self._driver.get_distance_to_last_manipulated)
	def _find_elts(self, xpath=None):
		if xpath is None:
			xpath = self.get_xpath()
		return list(map(
			WebElementWrapper, self._driver.find_elements(By.XPATH, xpath)
		))
	def _find_elts_by_free_text(self):
		elt_types = [
			xpath.strip().lstrip('/') for xpath in self.get_xpath().split('|')
		]
		labels = '//text()' + predicate(self.matches.xpath('.', self.label))
		xpath = ' | '.join(
			[(labels + '/%s::' + elt_type + '[1]')
			 % ('preceding-sibling'
			    if 'checkbox' in elt_type or 'radio' in elt_type
			    else 'following')
			 for elt_type in elt_types]
		)
		return self._find_elts(xpath)
	def get_xpath(self):
		raise NotImplementedError()
	def get_primary_search_direction(self):
		return 'to_right_of'
	def get_secondary_search_direction(self):
		return 'below'
	def _filter_elts_belonging_to_labels(self, all_elts, labels):
		for label, elt in self._get_labels_with_explicit_elts(all_elts, labels):
			yield elt
			labels.remove(label)
			all_elts.remove(elt)
		labels_to_elts = self._get_related_elts(all_elts, labels)
		labels_to_elts = self._ensure_at_most_one_label_per_elt(labels_to_elts)
		self._retain_closest(labels_to_elts)
		for elts_for_label in list(labels_to_elts.values()):
			assert len(elts_for_label) <= 1
			if elts_for_label:
				yield next(iter(elts_for_label))
	def _get_labels_with_explicit_elts(self, all_elts, labels):
		for label in labels:
			try:
				if label.tag_name == 'label':
					label_target = label.get_attribute('for')
					if label_target:
						for elt in all_elts:
							elt_id = elt.get_attribute('id')
							if elt_id.lower() == label_target.lower():
								yield label, elt
			except Exception as e:
				if not self.should_ignore_exception(e):
					raise
	def _get_related_elts(self, all_elts, labels):
		result = {}
		for label in labels:
			for elt in all_elts:
				try:
					if self._are_related(elt, label):
						if label not in result:
							result[label] = set()
						result[label].add(elt)
				except Exception as e:
					if not self.should_ignore_exception(e):
						raise
		return result
	def _are_related(self, elt, label):
		if elt.location.intersects(label.location):
			return True
		prim_search_dir = self.get_primary_search_direction()
		sec_search_dir = self.get_secondary_search_direction()
		return label.location.distance_to(elt.location) <= 150 and (
			elt.location.is_in_direction(prim_search_dir, label.location) or
			elt.location.is_in_direction(sec_search_dir, label.location)
		)
	def _ensure_at_most_one_label_per_elt(self, labels_to_elts):
		elts_to_labels = inverse(labels_to_elts)
		self._retain_closest(elts_to_labels)
		return inverse(elts_to_labels)
	def _retain_closest(self, pivots_to_elts):
		for pivot, elts in list(pivots_to_elts.items()):
			if elts:
				closest = self._find_closest(pivot, elts)
				if closest:
					pivots_to_elts[pivot] = {closest}
	def _find_closest(self, to_pivot, among_elts):
		distances = []
		for elt in among_elts:
			try:
				distance = self._compute_distance(elt, to_pivot)
			except Exception as e:
				if not self.should_ignore_exception(e):
					raise
			else:
				distances.append((distance, elt))
		if distances:
			# Provide `key=` to prevent a TypeError that happens when Python
			# attempts to sort on the second items of the tuples when the first
			# items are equal.
			return sorted(distances, key=lambda tpl: tpl[0])[0][1]
	def _compute_distance(self, elt_1, elt_2):
		loc_1 = elt_1.location
		loc_2 = elt_2.location
		if loc_1.is_in_direction(self.get_secondary_search_direction(), loc_2):
			factor = self.SECONDARY_SEARCH_DIMENSION_PENALTY_FACTOR
		else:
			factor = 1
		return factor * loc_1.distance_to(loc_2)

class CompositeElement(HTMLElementImpl):
	def __init__(self, driver, *args, **kwargs):
		super(CompositeElement, self).__init__(driver, **kwargs)
		self.args = [driver] + list(args)
		self.kwargs = kwargs
		self._first_element = None
	@property
	def first_element(self):
		if self._first_element is None:
			self._bind_to_first_occurrence()
			# find_anywhere_in_curr_frame() below now sets _first_element
		return self._first_element
	def find_anywhere_in_curr_frame(self):
		already_yielded = []
		for element in self.get_elements():
			for bound_gui_elt_impl in element.find_anywhere_in_curr_frame():
				if self._first_element is None:
					self._first_element = element
				if bound_gui_elt_impl not in already_yielded:
					yield bound_gui_elt_impl
					already_yielded.append(bound_gui_elt_impl)
	def get_elements(self):
		for element_type in self.get_element_types():
			yield element_type(*self.args, **self.kwargs)
	def get_element_types(self):
		raise NotImplementedError()

class ClickableText(CompositeElement):
	def get_element_types(self):
		return [ButtonImpl, TextImpl, ImageImpl]

class TextFieldImpl(CompositeElement):
	def get_element_types(self):
		return [
			StandardTextFieldWithPlaceholder, StandardTextFieldWithLabel,
			AriaTextFieldWithLabel
		]
	@property
	def value(self):
		return self.first_element.value
	def is_enabled(self):
		return self.first_element.is_enabled()
	def is_editable(self):
		return self.first_element.is_editable()

class StandardTextFieldWithLabel(LabelledElement):
	@property
	def value(self):
		return self.first_occurrence.get_attribute('value') or ''
	def is_enabled(self):
		return self._is_enabled()
	def is_editable(self):
		return self.first_occurrence.get_attribute('readOnly') is None
	def get_xpath(self):
		return \
			"//input[%s='text' or %s='email' or %s='password' or %s='number' " \
			 "or %s='date' or %s='time' or %s='tel' or string-length(@type)=0]"\
			 % ((lower('@type'), ) * 7) + \
			 " | //textarea | //*[@contenteditable='true']"

class AriaTextFieldWithLabel(LabelledElement):
	@property
	def value(self):
		return self.first_occurrence.text
	def is_enabled(self):
		return self._is_enabled()
	def is_editable(self):
		return self.first_occurrence.get_attribute('readOnly') is None
	def get_xpath(self):
		return "//*[@role='textbox']"

class StandardTextFieldWithPlaceholder(HTMLElementIdentifiedByXPath):
	def __init__(self, driver, label, **kwargs):
		super(StandardTextFieldWithPlaceholder, self).__init__(driver, **kwargs)
		self.label = label
	@property
	def value(self):
		return self.first_occurrence.get_attribute('value') or ''
	def is_enabled(self):
		return self._is_enabled()
	def is_editable(self):
		return self.first_occurrence.get_attribute('readOnly') is None
	def get_xpath(self):
		return "(%s)%s" % (
			StandardTextFieldWithLabel(self.label).get_xpath(),
			predicate(self.matches.xpath('@placeholder', self.label))
		)

class FileInput(LabelledElement):
	def get_xpath(self):
		return "//input[@type='file']"

class ComboBoxImpl(CompositeElement):
	def get_element_types(self):
		return [ComboBoxIdentifiedByDisplayedValue, ComboBoxIdentifiedByLabel]
	def is_editable(self):
		return self.first_occurrence.tag_name != 'select'
	@property
	def value(self):
		selected_value = self._select_driver.first_selected_option
		if selected_value:
			return selected_value.text
		return None
	@property
	def options(self):
		return [option.text for option in self._select_driver.options]
	@property
	def _select_driver(self):
		return Select(self.web_element)

class ComboBoxIdentifiedByLabel(LabelledElement):
	def get_xpath(self):
		return "//select | //input[@list]"

class ComboBoxIdentifiedByDisplayedValue(HTMLElementContainingText):
	def get_xpath_node_selector(self):
		return 'option'
	def get_xpath(self):
		option_xpath = \
			super(ComboBoxIdentifiedByDisplayedValue, self).get_xpath()
		return option_xpath + '/ancestor::select[1]'
	def find_anywhere_in_curr_frame(self):
		all_cbs_with_a_matching_value = super(
			ComboBoxIdentifiedByDisplayedValue, self
		).find_anywhere_in_curr_frame()
		result = []
		for cb in all_cbs_with_a_matching_value:
			for selected_option in Select(cb.unwrap()).all_selected_options:
				if self.matches.text(selected_option.text, self.search_text):
					result.append(cb)
					break
		return result

class CheckBoxImpl(LabelledElement):
	def is_enabled(self):
		return self._is_enabled()
	def is_checked(self):
		return self.first_occurrence.get_attribute('checked') is not None
	def get_xpath(self):
		return "//input[@type='checkbox']"
	def get_primary_search_direction(self):
		return 'to_left_of'
	def get_secondary_search_direction(self):
		return 'to_right_of'

class RadioButtonImpl(LabelledElement):
	def is_selected(self):
		return self.first_occurrence.get_attribute('checked') is not None
	def get_xpath(self):
		return "//input[@type='radio']"
	def get_primary_search_direction(self):
		return 'to_left_of'
	def get_secondary_search_direction(self):
		return 'to_right_of'

class WindowImpl(GUIElementImpl):
	def __init__(self, driver, title=None):
		super(WindowImpl, self).__init__(driver)
		self.search_title = title
	def iter_all_occurrences(self):
		result_scores = []
		for handle in self._driver.window_handles:
			window = WindowImpl.SeleniumWindow(self._driver, handle)
			if self.search_title is None:
				result_scores.append((0, window))
			else:
				title = window.title
				if title.startswith(self.search_title):
					score = len(title) - len(self.search_title)
					result_scores.append((score, window))
		score = lambda tpl: tpl[0]
		result_scores.sort(key=score)
		for score, window in result_scores:
			yield window
	@property
	def title(self):
		return self.first_occurrence.title
	@property
	def handle(self):
		return self.first_occurrence.handle
	class SeleniumWindow:
		def __init__(self, driver, handle):
			self.driver = driver
			self.handle = handle
			self._window_handle_before = None
		@property
		def title(self):
			with self:
				return self.driver.title
		def __enter__(self):
			try:
				self._window_handle_before = self.driver.current_window_handle
			except NoSuchWindowException as window_closed:
				do_switch = True
			else:
				do_switch = self._window_handle_before != self.handle
			if do_switch:
				self.driver.switch_to.window(self.handle)
		def __exit__(self, *_):
			if self._window_handle_before and \
				self.driver.current_window_handle != self._window_handle_before:
				self.driver.switch_to.window(self._window_handle_before)

class AlertImpl(GUIElementImpl):
	def __init__(self, driver, search_text=None):
		super(AlertImpl, self).__init__(driver)
		self.search_text = search_text
	def iter_all_occurrences(self):
		try:
			result = self._driver.switch_to.alert
			text = result.text
			if self.search_text is None or text.startswith(self.search_text):
				yield result
		except NoAlertPresentException:
			pass
	@property
	def text(self):
		return self.first_occurrence.text
	def accept(self):
		first_occurrence = self.first_occurrence
		try:
			first_occurrence.accept()
		except WebDriverException as e:
			# Attempt to work around Selenium issue 3544:
			# https://code.google.com/p/selenium/issues/detail?id=3544
			msg = e.msg
			if msg and re.match(
					r"a\.document\.getElementsByTagName\([^\)]*\)\[0\] is "
					r"undefined", msg
			):
				sleep(0.25)
				first_occurrence.accept()
			else:
				raise
	def dismiss(self):
		self.first_occurrence.dismiss()
	def _write(self, text):
		self.first_occurrence.send_keys(text)


================================================
FILE: helium/_impl/match_type.py
================================================
from helium._impl.util.xpath import lower, replace_nbsp

class MatchType:
	def xpath(self, value, text):
		raise NotImplementedError()
	def text(self, value, text):
		raise NotImplementedError()

class PREFIX_IGNORE_CASE(MatchType):
	def xpath(self, value, text):
		if not text:
			return ''
		# Asterisks '*' are sometimes used to mark required fields. Eg.:
		# <label for="title"><span class="red-txt">*</span> Title:</label>
		# The starts-with filter below would be too strict to include such
		# matches. To get around this, we ignore asterisks unless the searched
		# text itself contains one.
		if '*' in text:
			strip_asterisks = value
		else:
			strip_asterisks = "translate(%s, '*', '')" % value

		# if text contains apostrophes (single quotes) then they need to be
		# treated with care
		if "'" in text:
			text = "concat('%s')" % ("',\"'\",'".join(text.split("'")))
		else:
			text = "'%s'" % text

		return "starts-with(normalize-space(%s), %s)" % (
			lower(replace_nbsp(strip_asterisks)), text.lower()
		)
	def text(self, value, text):
		if not text:
			return True
		return value.lower().lstrip().startswith(text.lower())

================================================
FILE: helium/_impl/selenium_wrappers.py
================================================
from helium._impl.util.geom import Rectangle
from selenium.common.exceptions import StaleElementReferenceException, \
	NoSuchFrameException, WebDriverException, NoSuchElementException
from selenium.webdriver.common.action_chains import ActionChains
from urllib.error import URLError
import sys

class Wrapper:
	def __init__(self, target):
		self.target = target
	def __getattr__(self, item):
		return getattr(self.target, item)
	def unwrap(self):
		return self.target
	def __hash__(self):
		return hash(self.target)
	def __eq__(self, other):
		return self.target == other.target
	def __ne__(self, other):
		return not self == other

class WebDriverWrapper(Wrapper):
	def __init__(self, target):
		super(WebDriverWrapper, self).__init__(target)
		self.last_manipulated_element = None
	def action(self):
		return ActionChains(self.target)
	def get_distance_to_last_manipulated(self, web_element):
		if not self.last_manipulated_element:
			return 0
		try:
			if hasattr(self.last_manipulated_element, 'location'):
				last_location = self.last_manipulated_element.location
				return last_location.distance_to(web_element.location)
		except StaleElementReferenceException:
			return 0
		else:
			# No .location. This happens when last_manipulated_element is an
			# Alert or a Window.
			return 0
	def is_firefox(self):
		return self.browser_name == 'firefox'
	@property
	def browser_name(self):
		return self.target.capabilities['browserName']
	def is_ie(self):
		return self.browser_name == 'internet explorer'

def _translate_url_errors_caused_by_server_shutdown(f):
	def f_decorated(*args, **kwargs):
		try:
			return f(*args, **kwargs)
		except URLError as url_error:
			if _is_caused_by_server_shutdown(url_error):
				raise StaleElementReferenceException(
					'The Selenium server this element belonged to is no longer '
					'available.'
				)
			else:
				raise
	return f_decorated

def _is_caused_by_server_shutdown(url_error):
	try:
		CONNECTION_REFUSED = 10061
		return url_error.args[0][0] == CONNECTION_REFUSED
	except (IndexError, TypeError):
		return False

def handle_element_being_in_other_frame(f):
	def f_decorated(self, *args, **kwargs):
		if not self.frame_index:
			return f(self, *args, **kwargs)
		try:
			return f(self, *args, **kwargs)
		except (StaleElementReferenceException, NoSuchElementException) \
			as original_exc:
			try:
				frame_iterator = FrameIterator(self.target.parent)
				frame_iterator.switch_to_frame(self.frame_index)
			except NoSuchFrameException:
				raise original_exc
			else:
				return f(self, *args, **kwargs)
	return f_decorated

class WebElementWrapper:
	def __init__(self, target, frame_index=None):
		self.target = target
		self.frame_index = frame_index
		self._cached_location = None
	@property
	@handle_element_being_in_other_frame
	@_translate_url_errors_caused_by_server_shutdown
	def location(self):
		if self._cached_location is None:
			# Cache access to web_element.location as it's expensive:
			location = self.target.location
			x, y = location['x'], location['y']
			# Cache access to web_element.size as it's expensive:
			size = self.target.size
			width, height = size['width'], size['height']
			self._cached_location = Rectangle(x, y, width, height)
		return self._cached_location
	def is_displayed(self):
		try:
			return self.target.is_displayed() and self.location.intersects(
				Rectangle(0, 0, sys.maxsize, sys.maxsize)
			)
		except StaleElementReferenceException:
			return False
	@handle_element_being_in_other_frame
	def get_attribute(self, attr_name):
		return self.target.get_attribute(attr_name)
	@property
	@handle_element_being_in_other_frame
	def text(self):
		return self.target.text
	@handle_element_being_in_other_frame
	def clear(self):
		self.target.clear()
	@handle_element_being_in_other_frame
	def send_keys(self, keys):
		self.target.send_keys(keys)
	@property
	@handle_element_being_in_other_frame
	def tag_name(self):
		return self.target.tag_name
	def unwrap(self):
		return self.target
	def __repr__(self):
		return '<%s>%s</%s>' % (self.tag_name, self.target.text, self.tag_name)

class FrameIterator:
	def __init__(self, driver, start_frame=None):
		if start_frame is None:
			start_frame = []
		self.driver = driver
		self.start_frame = start_frame
	def __iter__(self):
		yield []
		for new_frame in range(sys.maxsize):
			try:
				self.driver.switch_to.frame(new_frame)
			except WebDriverException:
				break
			else:
				new_start_frame = self.start_frame + [new_frame]
				for result in FrameIterator(self.driver, new_start_frame):
					yield [new_frame] + result
				try:
					self.switch_to_frame(self.start_frame)
				except NoSuchFrameException:
					raise FramesChangedWhileIterating()
	def switch_to_frame(self, frame_index_path):
		self.driver.switch_to.default_content()
		for frame_index in frame_index_path:
			self.driver.switch_to.frame(frame_index)

class FramesChangedWhileIterating(Exception):
	pass

================================================
FILE: helium/_impl/util/__init__.py
================================================


================================================
FILE: helium/_impl/util/dictionary.py
================================================
def inverse(dictionary):
	"""
	{a: {b}} -> {b: {a}}
	"""
	result = {}
	for key, values in dictionary.items():
		for value in values:
			if value not in result:
				result[value] = set()
			result[value].add(key)
	return result

================================================
FILE: helium/_impl/util/geom.py
================================================
from collections import namedtuple
from math import sqrt

class Rectangle:
	def __init__(self, left=0, top=0, width=0, height=0):
		self.left = left
		self.top = top
		self.right = left + width
		self.bottom = top + height
	@classmethod
	def from_w_h(cls, width, height):
		return cls(0, 0, width, height)
	@classmethod
	def from_tuple_l_t_w_h(cls, l_t_w_h=None):
		if l_t_w_h is None:
			l_t_w_h = (0, 0, 0, 0)
		return cls(*l_t_w_h)
	@classmethod
	def from_tuple_w_h(cls, w_h):
		return cls.from_w_h(*w_h)
	@classmethod
	def from_struct_l_t_r_b(cls, struct):
		return cls.from_l_t_r_b(
			struct.left, struct.top, struct.right, struct.bottom
		)
	@classmethod
	def from_l_t_r_b(cls, left, top, right, bottom):
		return cls(left, top, right - left, bottom - top)
	@property
	def width(self):
		return self.right - self.left
	@property
	def height(self):
		return self.bottom - self.top
	@property
	def center(self):
		return Point(self.left + self.width / 2, self.top + self.height / 2)
	@property
	def east(self):
		return self.clip(Point(self.right - 1, self.center.y))
	@property
	def west(self):
		return Point(self.left, self.center.y)
	@property
	def north(self):
		return Point(self.center.x, self.top)
	@property
	def south(self):
		return self.clip(Point(self.center.x, self.bottom - 1))
	@property
	def northeast(self):
		return Point(self.east.x, self.north.y)
	@property
	def southeast(self):
		return Point(self.east.x, self.south.y)
	@property
	def southwest(self):
		return Point(self.west.x, self.south.y)
	@property
	def northwest(self):
		return Point(self.west.x, self.north.y)
	@property
	def area(self):
		if not self:
			return 0
		return self.width * self.height
	def __contains__(self, point):
		return self.left <= point.x < self.right and \
			   self.top <= point.y < self.bottom
	def translate(self, dx, dy):
		self.left += dx
		self.right += dx
		self.top += dy
		self.bottom += dy
		return self
	def clip(self, point):
		return Point(
			min(max(point[0], self.left), max(self.left, self.right - 1)),
			min(max(point[1], self.top), max(self.top, self.bottom - 1))
		)
	def intersect(self, rectangle):
		left = max(self.left, rectangle.left)
		top = max(self.top, rectangle.top)
		right = min(self.right, rectangle.right)
		bottom = min(self.bottom, rectangle.bottom)
		return self.from_l_t_r_b(left, top, right, bottom) or Rectangle()
	def intersects(self, rectangle):
		return bool(self.intersect(rectangle))
	def as_numpy_slice(self):
		return slice(self.top, self.bottom), slice(self.left, self.right)
	def is_to_left_of(self, other):
		self_starts_to_left_of_other = self.left < other.left
		self_overlaps_other_top = self.top <= other.top < self.bottom
		other_overlaps_self_top = other.top <= self.top < other.bottom
		return self_starts_to_left_of_other and (
			self_overlaps_other_top or
			other_overlaps_self_top
		)
	def is_to_right_of(self, other):
		return other.is_to_left_of(self)
	def is_above(self, other):
		self_starts_above_other = self.top < other.top
		self_overlaps_other_left = self.left <= other.left < self.right
		other_overlaps_self_left = other.left <= self.left < other.right
		return self_starts_above_other and (
			self_overlaps_other_left or
			other_overlaps_self_left
		)
	def is_below(self, other):
		return other.is_above(self)
	def is_in_direction(self, in_direction, of_other):
		return getattr(self, 'is_' + in_direction)(of_other)
	def distance_to(self, other):
		leftmost = self if self.left < other.left else other
		rightmost = self if leftmost == other else other
		distance_x = max(0, rightmost.left - leftmost.right)
		topmost = self if self.top < other.top else other
		bottommost = self if topmost == other else other
		distance_y = max(0, bottommost.top - topmost.bottom)
		return sqrt(distance_x ** 2 + distance_y ** 2)
	def __eq__(self, other):
		if not isinstance(other, Rectangle):
			return False
		return self.left == other.left and self.top == other.top and \
			   self.right == other.right and self.bottom == other.bottom
	def __ne__(self, other):
		return not self.__eq__(other)
	def __bool__(self):
		return bool(self.width > 0 and self.height > 0)
	def __repr__(self):
		return type(self).__name__ + '(left=%d, top=%d, width=%d, height=%d)' \
			   % (self.left, self.top, self.width, self.height)
	def __hash__(self):
		return self.left + 7 * self.top + 11 * self.right + 13 * self.bottom

class Point(namedtuple('Point', ['x', 'y'])):
	def __new__(cls, x=0, y=0):
		return cls.__bases__[0].__new__(cls, x, y)
	def __init__(self, x=0, y=0):
		# tuple is immutable so can't do anything here. The initialization
		# happens in __new__(...) above.
		pass
	@classmethod
	def from_tuple(cls, tpl):
		return cls(*tpl)
	def __eq__(self, other):
		return (self.x, self.y) == other
	def __ne__(self, other):
		return not self == other
	def __add__(self, other):
		dx, dy = other
		return Point(self.x + dx, self.y + dy)
	def __radd__(self, other):
		return self.__add__(other)
	def __sub__(self, other):
		dx, dy = other
		return Point(self.x - dx, self.y - dy)
	def __rsub__(self, other):
		x, y = other
		dx, dy = self
		return Point(x - dx, y - dy)
	def __mul__(self, scalar):
		if isinstance(scalar, (int, float)):
			return Point(self.x * scalar, self.y * scalar)
		else:
			raise ValueError("Invalid argument")
	def __rmul__(self, scalar):
		return self.__mul__(scalar)
	def __div__(self, scalar):
		if isinstance(scalar, (int, float)):
			return Point(self.x / scalar, self.y / scalar)
		else:
			raise ValueError("Invalid argument")
	def __bool__(self):
		return bool(self.x) or bool(self.y)

class Direction:
	def __init__(self, unit_vector):
		self.unit_vector = unit_vector
	def iterate_points_starting_at(self, point, offsets):
		for offset in offsets:
			yield point + offset * self.unit_vector
	def is_horizontal(self):
		return bool(self.unit_vector.x)
	def is_vertical(self):
		return not self.is_horizontal()
	@property
	def orthog_vector(self):
		return Point(-self.unit_vector[1], self.unit_vector[0])
	def __eq__(self, other):
		return self.unit_vector == other.unit_vector
	def __repr__(self):
		for module_element in dir(self.__module__):
			if self == getattr(self.__module__, module_element):
				return module_element

NORTH = Direction(Point(0, -1))
EAST = Direction(Point(1, 0))
SOUTH = Direction(Point(0, 1))
WEST = Direction(Point(-1, 0))

================================================
FILE: helium/_impl/util/html.py
================================================
from html.parser import HTMLParser
import re

def strip_tags(html):
	s = TagStripper()
	s.feed(html)
	return s.get_data()

class TagStripper(HTMLParser):
	def __init__(self):
		HTMLParser.__init__(self)
		self.reset()
		self.fed = []
	def handle_data(self, d):
		self.fed.append(d)
	def get_data(self):
		return ''.join(self.fed)

def get_easily_readable_snippet(html):
	html = normalize_whitespace(html)
	try:
		inner_start = html.index('>') + 1
		inner_end = html.rindex('<', inner_start)
	except ValueError:
		return html
	opening_tag = html[:inner_start]
	closing_tag = html[inner_end:]
	inner = html[inner_start:inner_end]
	if '<' in inner or len(inner) > 60:
		return '%s...%s' % (opening_tag, closing_tag)
	else:
		return html

def normalize_whitespace(html):
	result = html.strip()
	# Remove multiple spaces:
	result = re.sub(r'\s+', ' ', result)
	# Remove spaces after opening or before closing tags:
	result = result.replace('> ', '>').replace(' <', '<')
	return result

================================================
FILE: helium/_impl/util/inspect_.py
================================================
from helium._impl.util.lang import isbound
import inspect

def repr_args(f, args=None, kwargs=None, repr_fn=repr):
	if args is None:
		args = []
	if kwargs is None:
		kwargs = {}
	arg_names, _, _, defaults = inspect.getfullargspec(f)[:4]
	if isbound(f):
		# Skip 'self' parameter:
		arg_names = arg_names[1:]
	num_defaults = 0 if defaults is None else len(defaults)
	num_requireds = len(arg_names) - num_defaults
	result = []
	for i, arg_name in enumerate(arg_names):
		has_default = i >= len(arg_names) - num_defaults
		if has_default:
			default_value = defaults[i - num_requireds]
		if i < len(args): # Normal arg
			value = args[i]
			prefix = ''
			value_is_default = has_default and value == default_value
		elif arg_name in kwargs: # Keyword arg
			value = kwargs[arg_name]
			prefix = arg_name + '='
			value_is_default = has_default and value == default_value
		else: # Optional arg without given value
			value_is_default = True
		if not value_is_default:
			result.append(prefix + repr_fn(value))
	for vararg in args[len(arg_names):]:
		result.append(repr_fn(vararg))
	for kwarg in kwargs:
		if kwarg not in arg_names:
			result.append(kwarg + '=' + repr_fn(kwargs[kwarg]))
	return ', '.join(result)

================================================
FILE: helium/_impl/util/lang.py
================================================
class TemporaryAttrValue:
	def __init__(self, obj, attr, value):
		self.obj = obj
		self.attr = attr
		self.value = value
		self.value_before = None
	def __enter__(self):
		self.value_before = getattr(self.obj, self.attr)
		setattr(self.obj, self.attr, self.value)
	def __exit__(self, *_):
		setattr(self.obj, self.attr, self.value_before)
		self.value_before = None

def isbound(method_or_fn):
	try:
		return method_or_fn.__self__ is not None
	except AttributeError: # Python 3
		try:
			return method_or_fn.__self__ is not None
		except AttributeError:
			return False

================================================
FILE: helium/_impl/util/path.py
================================================
from errno import EEXIST
from os.path import split, isdir
from os import makedirs

def get_components(path):
	folders = []
	while True:
		path, folder = split(path)
		if folder != "":
			folders.append(folder)
		else:
			if path != "":
				folders.append(path)
			break
	return list(reversed(folders))

def ensure_exists(path):
	"""http://stackoverflow.com/a/600612/190597 (tzot)"""
	try:
		makedirs(path, exist_ok=True)  # Python>3.2
	except TypeError:
		try:
			makedirs(path)
		except OSError as exc: # Python >2.5
			if exc.errno == EEXIST and isdir(path):
				pass
			else: raise

================================================
FILE: helium/_impl/util/system.py
================================================
"""
Gives information about the current operating system.
"""
import sys

def is_windows():
	return sys.platform in ('win32', 'cygwin')

def is_mac():
	return sys.platform == 'darwin'

def is_linux():
	return sys.platform.startswith('linux')

def get_canonical_os_name():
	if is_windows():
		return 'windows'
	elif is_mac():
		return 'mac'
	elif is_linux():
		return 'linux'

================================================
FILE: helium/_impl/util/xpath.py
================================================
# -*- coding: utf-8 -*-
def lower(text):
	alphabet = 'ABCDEFGHIJKLMNOPQRSTUVWXYZÀÁÂÃÄÅÆÇÈÉÊËÌÍÎÏÐÑÒÓÔÕÖרÙÚÛÜÝ'
	return "translate(%s, '%s', '%s')" % (text, alphabet, alphabet.lower())

def replace_nbsp(text, by=' '):
	return "translate(%s, '\u00a0', %r)" % (text, by)

def predicate(condition):
	return '[%s]' % condition if condition else ''

def predicate_or(*conditions):
	return predicate(' or '.join([c for c in conditions if c]))

================================================
FILE: requirements/base.txt
================================================
# Also update setup.py when you edit this file.
selenium>=4.29.0

================================================
FILE: requirements/docs.txt
================================================
-r base.txt

sphinx-rtd-theme==3.0.2
sphinx==8.2.3

================================================
FILE: requirements/test.txt
================================================
-r base.txt

setuptools<60
psutil
pywin32; platform_system=='Windows'

================================================
FILE: setup.py
================================================
from setuptools import setup, find_packages

setup(
	name = 'helium',
	# Also update docs/conf.py when you change this:
	version = '7.0.0',
	author = 'Michael Herrmann',
	author_email = 'michael+removethisifyouarehuman@herrmann.io',
	description = 'Lighter browser automation based on Selenium.',
	keywords = 'helium selenium browser automation',
	url = 'https://github.com/mherrmann/helium',
	python_requires='>=3',
	packages = find_packages(exclude=['tests', 'tests.*']),
	install_requires = [
		# Also update requirements/base.txt when you make changes here.
		'selenium>=4.16.0'
	],
	package_data = {
		'helium._impl': ['webdrivers/**/*']
	},
	zip_safe = False,
	classifiers=[
		'Development Status :: 5 - Production/Stable',
		'Intended Audience :: Developers',
		'License :: OSI Approved :: MIT License',
		'Topic :: Software Development :: Testing',
		'Topic :: Software Development :: Libraries',
		'Programming Language :: Python',
		'Programming Language :: Python :: 3.5',
		'Programming Language :: Python :: 3.6',
		'Programming Language :: Python :: 3.7',
		'Programming Language :: Python :: 3.8',
		'Programming Language :: Python :: 3.9',
		'Programming Language :: Python :: 3.10',
		'Programming Language :: Python :: 3.11',
		'Operating System :: Microsoft :: Windows',
		'Operating System :: POSIX :: Linux',
		'Operating System :: MacOS :: MacOS X'
	],
	test_suite='tests'
)

================================================
FILE: tests/__init__.py
================================================


================================================
FILE: tests/api/__init__.py
================================================
from helium import start_chrome, start_firefox, go_to, set_driver, \
	kill_browser
from selenium.webdriver import ChromeOptions
from selenium.webdriver.common.by import By
from tests.api.util import get_data_file_url
from time import time, sleep
from unittest import TestCase

import os

def test_browser_name():
	try:
		browser_name = os.environ['TEST_BROWSER']
	except KeyError:
		return 'chrome'
	else:
		return browser_name

class BrowserAT(TestCase):
	@classmethod
	def setUpClass(cls):
		if _TEST_BROWSER is None:
			cls.driver = start_browser()
			cls.started_browser = True
		else:
			cls.driver = _TEST_BROWSER
			cls.started_browser = False
		set_driver(cls.driver)
	def setUp(self):
		go_to(self.get_url())
	def get_url(self):
		return get_data_file_url(self.get_page())
	def get_page(self):
		raise NotImplementedError()
	def read_result_from_browser(self, timeout_secs=3):
		start_time = time()
		while time() < start_time + timeout_secs:
			result = self.driver\
				.find_element(By.ID, 'result').get_attribute('innerHTML')
			if result:
				return result
			sleep(0.2)
		return ''
	def assertFindsEltWithId(self, predicate, id_):
		self.assertEqual(id_, predicate.web_element.get_attribute('id'))
	@classmethod
	def tearDownClass(cls):
		if cls.started_browser:
			kill_browser()

_TEST_BROWSER = None

def setUpModule():
	global _TEST_BROWSER
	_TEST_BROWSER = start_browser()

def tearDownModule():
	global _TEST_BROWSER
	if _TEST_BROWSER is not None:
		kill_browser()
	_TEST_BROWSER = None

def start_browser(url=None):
	browser_name = test_browser_name()
	kwargs = {}
	if browser_name in ('chrome', 'firefox'):
		kwargs['headless'] = True
	if browser_name == 'chrome':
		options = ChromeOptions()
		# Fix the locale for inputting dates and times:
		options.add_argument('lang=de-DE')
		kwargs['options'] = options
	return _TEST_BROWSERS[browser_name](url, **kwargs)

_TEST_BROWSERS = {
	'firefox': start_firefox,
	'chrome': start_chrome
}

================================================
FILE: tests/api/data/default.css
================================================
#result {
    clear: both;
}

================================================
FILE: tests/api/data/js/jquery.ui-contextmenu.js
================================================
/*******************************************************************************
 * jquery.ui-contextmenu.js plugin.
 *
 * jQuery plugin that provides a context menu (based on the jQueryUI menu widget).
 *
 * @see https://github.com/mar10/jquery-ui-contextmenu
 *
 * Copyright (c) 2013, Martin Wendt (http://wwWendt.de). Licensed MIT.
 */
;(function($, window, document, undefined) {
	"use strict";
	var supportSelectstart = "onselectstart" in document.createElement("div");

	/** Return command without leading '#' (default to ""). */
	function normCommand(cmd){
		return (cmd && cmd.match(/^#/)) ? cmd.substring(1) : (cmd || "");
	}


	$.widget("moogle.contextmenu", {
		version: "1.2.2",
		options: {
			delegate: null,       // selector
			hide: { effect: "fadeOut", duration: "fast"},
			ignoreParentSelect: true, // Don't trigger 'select' for sub-menu parents
			menu: null,           // selector or jQuery pointing to <UL>, or a definition hash
			position: null,       // popup positon
			preventSelect: false, // disable text selection of target
			show: { effect: "slideDown", duration: "fast"},
			taphold: false,       // open menu on taphold events (requires external plugins)
			// Events:
			beforeOpen: $.noop,   // menu about to open; return `false` to prevent opening
			blur: $.noop,         // menu option lost focus
			close: $.noop,        // menu was closed
			create: $.noop,       // menu was initialized
			createMenu: $.noop,   // menu was initialized (original UI Menu)
			focus: $.noop,        // menu option got focus
			open: $.noop,         // menu was opened
			select: $.noop        // menu option was selected; return `false` to prevent closing
		},
		/** Constructor */
		_create: function () {
			var eventNames, targetId,
				opts = this.options;

			this.$headStyle = null;
			this.$menu = null;
			this.menuIsTemp = false;
			this.currentTarget = null;

			if(opts.preventSelect){
				// Create a global style for all potential menu targets
				// If the contextmenu was bound to `document`, we apply the
				// selector relative to the <body> tag instead
				targetId = ($(this.element).is(document) ? $("body") : this.element).uniqueId().attr("id");
				this.$headStyle = $("<style class='moogle-contextmenu-style'>")
					.prop("type", "text/css")
					.html("#" + targetId + " " + opts.delegate + " { " +
						"-webkit-user-select: none; " +
						"-khtml-user-select: none; " +
						"-moz-user-select: none; " +
						"-ms-user-select: none; " +
						"user-select: none; " +
						"}")
					.appendTo("head");
				// TODO: the selectstart is not supported by FF?
				if(supportSelectstart){
					this.element.delegate(opts.delegate, "selectstart" + this.eventNamespace, function(event){
						event.preventDefault();
					});
				}
			}
			this._createUiMenu(opts.menu);

			eventNames = "contextmenu" + this.eventNamespace;
			if(opts.taphold){
				eventNames += " taphold" + this.eventNamespace;
			}
			this.element.delegate(opts.delegate, eventNames, $.proxy(this._openMenu, this));
		},
		/** Destructor, called on $().contextmenu("destroy"). */
		_destroy: function(){
			this.element.undelegate(this.eventNamespace);

			this._createUiMenu(null);

			if(this.$headStyle){
				this.$headStyle.remove();
				this.$headStyle = null;
			}
		},
		/** (Re)Create jQuery UI Menu. */
		_createUiMenu: function(menuDef){
			// Remove temporary <ul> if any
			if(this.isOpen()){
				// close without animation, to force async mode
				this._closeMenu(true);
			}

			if(this.menuIsTemp){
				this.$menu.remove(); // this will also destroy ui.menu
			} else if(this.$menu){
				this.$menu.menu("destroy").hide();
			}
			this.$menu = null;
			this.menuIsTemp = false;
			// If a menu definition array was passed, create a hidden <ul>
			// and generate the structure now
			if( ! menuDef ){
				return;
			} else if($.isArray(menuDef)){
				this.$menu = $.moogle.contextmenu.createMenuMarkup(menuDef);
				this.menuIsTemp = true;
			}else if ( typeof menuDef === "string" ){
				this.$menu = $(menuDef);
			}else{
				this.$menu = menuDef;
			}
			// Create - but hide - the jQuery UI Menu widget
			this.$menu
				.hide()
//				.addClass("moogle-contextmenu")
				// Create a menu instance that delegates events to our widget
				.menu({
					blur: $.proxy(this.options.blur, this),
					create: $.proxy(this.options.createMenu, this),
					focus: $.proxy(this.options.focus, this),
					select: $.proxy(function(event, ui){
						// User selected a menu entry
						var retval,
							isParent = (ui.item.has(">a[aria-haspopup='true']").length > 0),
							$a = ui.item.find(">a"),
							actionHandler = $a.data("actionHandler");
						ui.cmd = normCommand($a.attr("href"));
						ui.target = $(this.currentTarget);
						// ignore clicks, if they only open a sub-menu
						if( !isParent || !this.options.ignoreParentSelect){
							retval = this._trigger.call(this, "select", event, ui);
							if( actionHandler ){
								retval = actionHandler.call(this, event, ui);
							}
							if( retval !== false ){
								this._closeMenu.call(this);
							}
							event.preventDefault();
						}
					}, this)
				});
		},
		/** Open popup (called on 'contextmenu' event). */
		_openMenu: function(event){
			var opts = this.options,
				posOption = opts.position,
				self = this,
				ui = {menu: this.$menu, target: $(event.target)};
			this.currentTarget = event.target;
			// Prevent browser from opening the system context menu
			event.preventDefault();

			if( this._trigger("beforeOpen", event, ui) === false ){
				this.currentTarget = null;
				return false;
			}
			ui.menu = this.$menu; // Might have changed in beforeOpen
			// Register global event handlers that close the dropdown-menu
			$(document).bind("keydown" + this.eventNamespace, function(event){
				if( event.which === $.ui.keyCode.ESCAPE ){
					self._closeMenu();
				}
			}).bind("mousedown" + this.eventNamespace + " touchstart" + this.eventNamespace, function(event){
				// Close menu when clicked outside menu
				if( !$(event.target).closest(".ui-menu-item").length ){
					self._closeMenu();
				}
			});

			// required for custom positioning (issue #18 and #13).
			if ($.isFunction(posOption)) {
				posOption = posOption(event, ui);
			}
			posOption = $.extend({
				my: "left top",
				at: "left bottom",
				// if called by 'open' method, event does not have pageX/Y
				of: (event.pageX === undefined) ? event.target : event,
				collision: "fit"
			}, posOption);

			// Finally display the popup
			this.$menu
				.show() // required to fix positioning error
				.css({
					position: "absolute",
					left: 0,
					top: 0
				}).position(posOption)
				.hide(); // hide again, so we can apply nice effects

			this._show(this.$menu, this.options.show, function(){
				self._trigger.call(self, "open", event, ui);
			});
		},
		/** Close popup. */
		_closeMenu: function(immediately){
			var self = this,
				hideOpts = immediately ? false : this.options.hide;

			// Note: we don't want to unbind the 'contextmenu' event
			$(document)
				.unbind("mousedown" + this.eventNamespace)
				.unbind("touchstart" + this.eventNamespace)
				.unbind("keydown" + this.eventNamespace);

			this._hide(this.$menu, hideOpts, function() {
				self._trigger("close");
				self.currentTarget = null;
			});
		},
		/** Handle $().contextmenu("option", key, value) calls. */
		_setOption: function(key, value){
			switch(key){
			case "menu":
				this.replaceMenu(value);
				break;
			}
			$.Widget.prototype._setOption.apply(this, arguments);
		},
		/** Return ui-menu entry (<A> or <LI> tag). */
		_getMenuEntry: function(cmd, wantLi){
			var $entry = this.$menu.find("li a[href=#" + normCommand(cmd) + "]");
			return wantLi ? $entry.closest("li") : $entry;
		},
		/** Close context menu. */
		close: function(){
			if(this.isOpen()){
				this._closeMenu();
			}
		},
		/** Enable or disable the menu command. */
		enableEntry: function(cmd, flag){
			this._getMenuEntry(cmd, true).toggleClass("ui-state-disabled", (flag === false));
		},
		/** Redefine the whole menu. */
		/** Return Menu element (UL). */
		getMenu: function(){
			return this.$menu;
		},
		/** Return true if menu is open. */
		isOpen: function(){
//            return this.$menu && this.$menu.is(":visible");
			return !!this.$menu && !!this.currentTarget;
		},
		/** Open context menu on a specific target (must match options.delegate) */
		open: function(target){
			// Fake a 'contextmenu' event
			var e = jQuery.Event("contextmenu", {target: target.get(0)});
			return this.element.trigger(e);
		},
		replaceMenu: function(data){
			this._createUiMenu(data);
		},
		/** Redefine menu entry (title or all of it). */
		setEntry: function(cmd, titleOrData){
			var $parent,
				$entry = this._getMenuEntry(cmd, false);

			if(typeof titleOrData === "string"){
				// Replace <a> text without removing <span> child
				$entry
					.contents()
					.filter(function(){ return this.nodeType === 3; })
					.first()
					.replaceWith(titleOrData);
			}else{
				$parent = $entry.closest("li").empty();
				$.moogle.contextmenu.createEntryMarkup(titleOrData, $parent);
			}
		},
		/** Show or hide the menu command. */
		showEntry: function(cmd, flag){
			this._getMenuEntry(cmd, true).toggle(flag !== false);
		}
	});

/*
 * Global functions
 */
$.extend($.moogle.contextmenu, {
	/** Convert a menu description into a into a <li> content. */
	createEntryMarkup: function(entry, $parentLi){
		var $a = null;

		if(entry.title.match(/^---/)){
			$parentLi.text(entry.title);
		}else{
			$a = $("<a>", {
				text: "" + entry.title,
				href: "#" + normCommand(entry.cmd)
			}).appendTo($parentLi);
			if( $.isFunction(entry.action) ){
				$a.data("actionHandler", entry.action);
			}
			if(entry.uiIcon){
				$a.append($("<span class='ui-icon'>").addClass(entry.uiIcon));
			}
			if(entry.disabled){
				$parentLi.addClass("ui-state-disabled");
			}
			if($.isPlainObject(entry.data)){
				$a.data(entry.data);
			}
		}
		return $a;
	},
	/** Convert a nested array of command objects into a <ul> structure. */
	createMenuMarkup: function(options, $parentUl){
		var i, menu, $ul, $li;
		if( $parentUl == null ){
			$parentUl = $("<ul class='ui-helper-hidden'>").appendTo("body");
		}
		for(i = 0; i < options.length; i++){
			menu = options[i];
			$li = $("<li>").appendTo($parentUl);

			$.moogle.contextmenu.createEntryMarkup(menu, $li);

			if( $.isArray(menu.children) ){
				$ul = $("<ul>").appendTo($li);
				$.moogle.contextmenu.createMenuMarkup(menu.children, $ul);
			}
		}
		return $parentUl;
	}
});

}(jQuery, window, document));


================================================
FILE: tests/api/data/js/util.js
================================================
function setResult(result) {
    document.getElementById('result').innerHTML = result;
}

================================================
FILE: tests/api/data/test_alert.html
================================================
<!DOCTYPE html>
<html>
<head>
	<title>test_alert</title>
	<script type="text/javascript" src="js/util.js"></script>
	<script type="text/javascript">
		function promptAndSetResult() {
			var promptResult = prompt('Please enter a value', '');
			if (promptResult == null || typeof promptResult == 'undefined') {
				promptResult = '';
			}
			setResult('Value entered: ' + promptResult);
		}
	</script>
</head>
<body>
	<a href="#" onclick="alert('Hello World!'); setResult('Alert displayed');">
		Display alert
	</a>
	<br/>
	<a href="#"
	   onclick="setResult(confirm('Proceed?') ? 'Accepted' : 'Dismissed');">
		Ask for confirmation
	</a>
	<br/>
	<a href="#" onclick="promptAndSetResult();">Prompt for value</a>
	<p id="result"></p>
</body>
</html>

================================================
FILE: tests/api/data/test_aria.html
================================================
<!DOCTYPE html>
<html>
<head>
        <meta http-equiv="Content-Type" content="text/html; charset=utf-8">
		<title>Test page for browser system tests</title>
        <style>

        </style>
	</head>
<body>
	<form>
		<button aria-label="Close">X</button>
	</form>
	<form>
		<button aria-label="Disabled Close" aria-disabled="true">X</button>
	</form>
	<div role="button" aria-label="Attach files" aria-disabled="false" style="display: block; width: 20px; height: 20px; background-color: green;"></div>
	<div role="button" aria-label="Disabled Attach files" aria-disabled="true" style="display: block; width: 20px; height: 20px; background-color: red;"></div>
	<form>
		<input type="submit" value="" aria-label="Submit" />
	</form>
	<table>
		<tr>
			<td><div id="TextboxLabel">Textbox:</div></td>
			<td>
				<div role="textbox" aria-labelledby="TextboxLabel">
					Textbox value
				</div>
			</td>
		</tr>
	</table>
</body>
</html>

================================================
FILE: tests/api/data/test_click.html
================================================
<!DOCTYPE html>
<html>
<head>
	<title>test_click</title>
	<script type="text/javascript" src="js/util.js"></script>
	<style type="text/css">
		/* Taken from twitter.com: */
		.visuallyhidden {
			border: 0;
			clip: rect(0 0 0 0);
			height: 1px;
			margin: -1px;
			overflow: hidden;
			padding: 0;
			position: absolute;
			width: 1px;
		}
	</style>
</head>
<body>
	<!--Having this hidden span here lets us test whether Helium is clever-->
	<!--enough to give preference to buttons over spans when clicking. The-->
	<!--example is inspired by Twitter's "Tweet" button.-->
	<span class="visuallyhidden">Click me!</span>
	<button onclick="setResult('Success!');">Click me!</button>
	<p id="result"></p>
</body>
</html>

================================================
FILE: tests/api/data/test_doubleclick.html
================================================
<!DOCTYPE html>
<html>
<head>
	<title>test_doubleclick</title>
	<script type="text/javascript" src="js/util.js"></script>
</head>
<body>
	<p ondblclick="setResult('Success!');">Doubleclick here.</p>
	<p id="result"></p>
</body>
</html>

================================================
FILE: tests/api/data/test_drag/default.html
================================================
<!DOCTYPE html>
<html>
<head>
	<script type="text/javascript" src="../js/util.js"></script>
	<script type="text/javascript" src="../js/jquery.min.js"></script>
	<script type="text/javascript" src="../js/jquery-ui.min.js"></script>
	<script type="text/javascript">
		$(document).ready(function() {
			$('#draggable').draggable();
			$('.dropTarget').droppable({
				drop: function(ev, ui){
					if (ev.target.id == 'target') {
						setResult('Success!');
					}
				}
			});
		});
	</script>
	<link rel="stylesheet" type="text/css" href="../default.css">
	<link rel="stylesheet" type="text/css" href="test_drag.css">
</head>
<body>
	<div class="dropTarget">
		<p id="draggable">Drag me.</p>
	</div>
	<div id="target" class="dropTarget"></div>
	<p id="result"></p>
</body>
</html>

================================================
FILE: tests/api/data/test_drag/html5.html
================================================
<!DOCTYPE html>
<html>
<head>
	<script type="text/javascript" src="../js/util.js"></script>
	<script type="text/javascript">
		function allowDrop(ev) {
			ev.preventDefault();
		}
		function drag(ev) {
			ev.dataTransfer.setData("Text", ev.target.id);
		}
		function drop(ev) {
			ev.preventDefault();
			var data = ev.dataTransfer.getData("Text");
			ev.target.appendChild(document.getElementById(data));
			if (ev.target.id == 'target') {
				setResult('Success!');
			}
		}
	</script>
	<link rel="stylesheet" type="text/css" href="../default.css">
	<link rel="stylesheet" type="text/css" href="test_drag.css">
</head>
<body>
	<div class="dropTarget" ondrop="drop(event)" ondragover="allowDrop(event)">
		<p id="draggable" draggable="true" ondragstart="drag(event)">
			Drag me.
		</p>
	</div>
	<div id="target"
		 class="dropTarget" ondrop="drop(event)" ondragover="allowDrop(event)">
	</div>
	<p id="result"></p>
</body>
</html>

================================================
FILE: tests/api/data/test_drag/test_drag.css
================================================
.dropTarget {
    float: left;
    width: 100px;
    height: 35px;
    margin: 10px;
    padding: 10px;
    border: 1px solid #aaaaaa;
    text-align: center;
}

.dropTarget p {
    margin-top: 10px;
}

================================================
FILE: tests/api/data/test_file_upload/test_file_upload.html
================================================
<!DOCTYPE html>
<html>
<head>
	<title>test_file_upload</title>
	<script type="text/javascript" src="../js/util.js"></script>
	<script type="text/javascript" src="../js/jquery.min.js"></script>
	<script type="text/javascript" src="../js/jquery-ui.min.js"></script>
	<script type="text/javascript">
		function isExpectedFile(file) {
			return file.name == 'upload_this.png' &&
					file.size == 5417 && file.type == 'image/png';
		}
		function onNormalFileUploadChange(event) {
			var file = event.currentTarget.files[0];
			if (isExpectedFile(file)) {
				setResult("Success!");
			}
		}
		function enableDropEvent(onElement) {
			// If we don't 'preventDefault' on the events 'dragover' and
			// 'dragenter', the 'drop' event never reaches us. This function
			// ensures that the 'drop' event does get passed through. For a
			// reference, see http://www.quirksmode.org/blog/archives/2009/09/
			// the_html5_drag.html
			var preventDefault = function(event) {
				event.preventDefault();
			};
			onElement.on('dragover', preventDefault);
			onElement.on('dragenter', preventDefault);
		}
		/**
		 * The HTML5 spec at http://www.w3.org/html/wg/drafts/html/master/
		 * editing.html#current-drag-operation says that a drag consists of one
		 * dragenter event followed by a dragover event every 350+-200ms.
		 * The code below tests that the dragover event really is broadcast
		 * continuously by only considering dragover events 1000ms after the
		 * first dragenter event.
		 */
		var dragenterTime;
		$(document).on('dragenter', function(event) {
			dragenterTime = new Date().getTime();
			$("#dropFileUploadLabel").html('Now hold for 1 second...');
		});
		$(document).on('dragover', function(event) {
			var currTime = new Date().getTime();
			if (dragenterTime != undefined && currTime - dragenterTime > 1000) {
				var dropFileUpload = $('#dropFileUpload');
				dropFileUpload.addClass('active');
				enableDropEvent(dropFileUpload);
				dropFileUpload.on('drop', function(event) {
					event.preventDefault();
					var file = event.originalEvent.dataTransfer.files[0];
					if (isExpectedFile(file)) {
						setResult('Success!');
					}
				});
			}
		});
	</script>
	<style type="text/css">
		#dropFileUpload {
			border: 1px solid #aaaaaa;
			padding: 3px 10px;
			display: inline;
			visibility: hidden;
		}
		#dropFileUpload.active {
			visibility: visible;
		}
	</style>
</head>
<body>
	<label for="normalFileUpload">Normal file upload:</label>
	<input type="file" id="normalFileUpload"
		   onchange="onNormalFileUploadChange(event)"/>
	<br/>
	<label for="dropFileUpload" id="dropFileUploadLabel">
		Drag a file over this browser window!
	</label>
	<div id="dropFileUpload">Drop the file here!</div>
	<p id="result"></p>
</body>
</html>

================================================
FILE: tests/api/data/test_gui_elements.html
================================================
<html>
	<head>
        <meta http-equiv="Content-Type" content="text/html; charset=utf-8">
		<title>Test page for browser system tests</title>
        <style>
            button {
                width: 100%;
            }
            ul.horizontal {
                list-style-type: none;
                padding: 0;
            }
            ul.horizontal li {
                display: inline;
                background-color: lightblue;
                padding: 0 5px;
            }
            tbody {
                font-size: 11pt;
            }
            td.left {
                text-align: right;
            }
            [contenteditable] {
                border-style: solid;
                border-width: 1px;
            }
            iframe {
                height: 28px;
                width: 100%;
                border: none;
            }
        </style>
	</head>
	<body>
		<form>
			<table style="width: 700px;">
				<tr>
					<td>Empty Text Field:</td>
					<td><input type="text" /></td>
					<td>Another Text Field:</td>
					<td><input type="text" /></td>
				</tr>
				<tr>
					<td>Example Text Field:</td>
					<td>
						<input type="text" value="Lorem ipsum"
							   id="exampleTextFieldId"
							   name="exampleTextFieldName"
							   class="exampleTextFieldClass"/>
					</td>
                    <td>CheckBox</td>
                    <td>
						<input type="checkbox" id="checkBoxId"
							   name="checkBoxName" class="checkBoxClass"/>
					</td>
				</tr>
				<tr>
					<td>Disabled Text Field:</td>
					<td>
                        <input type="text" value="This is disabled" disabled />
                    </td>
                    <td><input type="checkbox"/> LHS CheckBox</td>
                </tr>
                <tr>
					<td>ReadOnly Text Field:</td>
					<td>
                        <input type="text" value="This is read only" readonly />
                    </td>
					<td>Language:</td>
					<td><input type="text" value="English" /></td>
				</tr>
				<tr>
					<td>Język polski:</td>
                    <td>
                        <input type="text"
                               value="Pchnąć w tę łódź jeża lub ośm skrzyń fig"
                               readonly />
                    </td>
                    <td>Language:</td>
                    <td>
                        <select>
                            <option>English</option>
                            <option>Polski</option>
                            <option>Deutsch</option>
                        </select>
                    </td>					
				</tr>
                <tr>
                    <td>Deutsch:</td>
                    <td>
                        <input type="text" value="Heizölrückstoßabdämpfung"/>
                    </td>
                    <td colspan="2">
                        <table style="width: 100%;">
                            <tr>
                                <td style="width: 20%">&nbsp;</td>
                                <td style="width: 40%; text-align: center;">
                                    Column 1
                                </td>
                                <td style="width: 40%; text-align: center;">
                                    Column 2
                                </td>
                            </tr>
                        </table>
                    </td>
                </tr>
                <tr>
                    <td>Drop Down List:</td>
                    <td>
                        <select id="dropDownListId" name="dropDownListName" class="dropDownListClass">
                            <option>Option One</option>
                            <option>Option Two</option>
                            <option>Option Three</option>
                        </select>
                    </td>
					<td colspan="2">
						<table style="width: 100%;">
							<tr>
								<td style="width: 20%">Row 1:</td>
								<td style="width: 40%; text-align: center;">
									<button type="button">
										Duplicate Button
									</button>
								</td>
								<td style="width: 40%; text-align: center;">
									<button type="button">
										Duplicate Button
									</button>
								</td>
							</tr>
						</table>
					</td>
				</tr>
				<tr>
					<td>Editable ComboBox:</td>
					<td>
						<input list="numbers">
						<datalist id="numbers">
							<option value="One"></option>
							<option value="Two"></option>
							<option value="Three"></option>
						</datalist>
					</td>
					<td colspan="2">
						<table style="width: 100%;">
							<tr>
								<td style="width: 20%">Row 2:</td>
								<td style="width: 40%; text-align: center;">
									<button type="button">
										Duplicate Button
									</button>
								</td>
								<td style="width: 40%; text-align: center;">
									<button type="button">
										Duplicate Button
									</button>
								</td>
							</tr>
						</table>
					</td>
				</tr>
                <tr>
                    <td><input type="checkbox"/>Right Labeled CheckBox</td>
                    <td class="left">
                        Left Labeled CheckBox
                        <input type="checkbox"/>
                    </td>
                    <td>
                        <button type="button">Enabled Button</button>
                    </td>
                    <td>
                        <button type="button" disabled >
                            Disabled Button
                        </button>
                    </td>
                </tr>
                <tr>
                    <td>
                        <input type="checkbox" checked />
                        Ticked CheckBox
                    </td>
                    <td class="left">
                        <input type="checkbox" disabled />
                        Disabled CheckBox
                    </td>
                    <td colspan="2" style="padding-left: 8px;">
						<label>
						<!--By setting margin: 0; here, we ensure that the-->
						<!--check box has the same position as the enclosing-->
						<!--label. This is important for the test.-->
						<input type="checkbox" style="margin: 0;"
							   id="checkBoxEnclosedByLabel">
						<span>CheckBox enclosed by label</span>
						</label>
                    </td>
                </tr>
                <tr>
                    <td>
                        <input type="radio" checked id="radioButton1id" name="radioGroup1" class="radioButton1class"/>
                        RadioButton 1
                    </td>
                    <td class="left">
                        <input type="radio" name="radioGroup2"/>
                        Left Labeled RadioButton 1
                    </td>
                    <td>
                        <span id="textWithId"
							  name="textWithName"
							  class="textWithClass">
                            Text with id
                        </span>
                    </td>
                    <td>
                        <a id="linkId" name="linkName" class="linkClass"
						   href="">Link with empty href</a>
                        <a id="linkId" name="linkName" class="linkClass"
                           href='http://heliumhq.com/'>heliumhq.com</a>
                    </td>
                </tr>
                <tr>
                    <td>
                        <input type="radio" name="radioGroup1"/>
                        RadioButton 2
                    </td>
                    <td class="left">
                        <input type="radio" checked name="radioGroup2"/>
                        Left Labeled RadioButton 2
                    </td>
                    <td>Input type=<b>T</b>ext:</td>
                    <td><input type="Text"/></td>
                </tr>
                <tr>
                    <td>
                        <select>
                            <option>Select a value...</option>
                            <option>Value 1</option>
                        </select>
                    </td>
                    <td>&nbsp;Text with leading &amp;nbsp;</td>
                    <td>
						<input type="submit" value="Submit Button"
							   id="submitButtonId" name="submitButtonName"
							   class="submitButtonClass"/>
					</td>
                    <td><input type="button" value="Input Button"/></td>
                </tr>
                <tr>
                    <td><input type="text" placeholder="Placeholder Text Field" /></td>
                    <td><input placeholder="Placeholder Text Field without type" /></td>
                    <td><div role="button">DIV with role=button</div></td>
                    <td><button>Button tag without type</button></td>
                </tr>
                <tr>
                    <td><span role='link'>Span with role=link</span></td>
                    <td><img src="img/dolphin.jpg" alt="Dolphin" id="imageId" name="imageName"
                             class="imageClass"/></td>
                    <td>
                        <a href="#" title="Link with title">
                            <img src="img/link_with_title.png" />
                        </a>
                    </td>
                    <td>
                        <input type="submit" title="submitButtonTitle" value="Submit Button with title"/>
                    </td>
                </tr>
                <tr>
                    <td colspan="2">
                        <label for="requiredTextField">
                            <span style="color: #C00;">*</span> Required Text Field:
                        </label>
                        <input type="text" id="requiredTextField" />
                    </td>
                    <td>
                        Input type=tel:
                    </td>
                    <td>
                        <input type="tel" id="inputTypeTel" />
                    </td>
                </tr>
                <tr>
                    <td>contenteditable Paragraph:</td>
                    <td>
                        <p contenteditable="true" id="contenteditableParagraphId" />
                    </td>
                    <td>TextField in iframe:</td>
                    <td><iframe src='test_gui_elements_iframe.html'></iframe></td>
                </tr>
				<tr>
					<td>
						<div class="simple-text">
							Your email's been sent!
						</div>
					</td>
					<td>
						<div class="simple-text">
							Single'quote. Double"quote.
						</div>
					</td>
					<td>
						<div class="simple-text">
							He said "double quotes".
						</div>
					</td>
					<td>VERÖFFENTLICHEN</td>
				</tr>
				<tr>
					<td>
						<select id="combo1">
							<option value="">Combo1</option>
						</select>
						<select id="combo2"></select>
					</td>
                </tr>
                <tr>
                    <td>
                        Input type=date:
                    </td>
                    <td>
                        <input type="date" id="inputTypeDate" />
                    </td>
                    <td>
                        Input type=time:
                    </td>
                    <td>
                        <input type="time" id="inputTypeTime" />
                    </td>
				</tr>
			</table>
		</form>
		<label for="scrollable1">HTML Unordered List:</label>
		<div id="scrollable1" style="overflow: auto;">
			<ul style="list-style-type: none; float: left; margin: 0; padding-left: 15px;">
				<li id="listItem1Id" name="listItem1Name"
					class="listItem1Class">ListItem 1</li>
				<li>ListItem 2</li>
			</ul>
		</div>
        <dl>
            <dt>
                <label for="labelledTextField">Labelled Text Field:</label>
            </dt>
            <dd>
                <input type="text" id="labelledTextField" />
            </dd>
        </dl>
		<p style="float: left; margin-right: 10px;">EUR/USD</p>
		<p style="float: left;">1.3487</p>
		<br />
		Free text not surrounded by tags
		<br />
		Text field labelled by free text:
		<br />
        <input type="text" value="TF labelled by free text" />
        <div class="example">
          <h3>Checkboxes</h3>
          <p>
            <form>
              <input type="checkbox"> unchecked<br/>
              <input type="checkbox" checked> checked
            </form>
          </p>
        </div>
        <div class="example">
          <h3>Radio buttons</h3>
          <p>
            <form>
              <input type="radio" name="sex" value="male" checked> male<br/>
              <input type="radio" name="sex" value="female"> female
            </form>
          </p>
        </div>  
	</body>
</html>

================================================
FILE: tests/api/data/test_gui_elements_iframe.html
================================================
<html>
	<head>
        <meta http-equiv="Content-Type" content="text/html; charset=utf-8">
        <style>
            body {
                margin: 0;
            }
        </style>
	</head>
	<body>
        <input type="text" id="textfieldInIframeId" />
	</body>
</html>

================================================
FILE: tests/api/data/test_hover.html
================================================
<!DOCTYPE html>
<html>
<head>
	<title>test_hover</title>
	<style type="text/css">
		#nav, #nav ul {
			padding: 0;
			margin: 0;
			list-style: none;
		}
		#nav a {
			display: block;
			width: 10em;
		}
		#nav li {
			float: left;
			width: 10em;
		}
		#nav li ul {
			position: absolute;
			width: 10em;
			left: -999em;
		}
		#nav li:hover ul {
			left: auto;
		}
	</style>
	<link rel="stylesheet" type="text/css" href="default.css">
	<script type="text/javascript" src="js/util.js"></script>
</head>
<body>
	<ul id="nav">
		<li>
			<a href="#" onmouseover="setResult('Dropdown 1');">Dropdown 1</a>
			<ul>
				<li>
					<a href="#" onmouseover="setResult('Dropdown 1 - Item A');">
						Item A
					</a>
				</li>
				<li>
					<a href="#" onmouseover="setResult('Dropdown 1 - Item B');">
						Item B
					</a>
				</li>
				<li>
					<a href="#" onmouseover="setResult('Dropdown 1 - Item C');">
						Item C
					</a>
				</li>
				<li>
					<a href="#" onmouseover="setResult('Dropdown 1 - Item D');">
						Item D
					</a>
				</li>
			</ul>
		</li>
		<li>
			<a href="#" onmouseover="setResult('Dropdown 2');"
			   onmouseout="setResult('');">Dropdown 2</a>
			<ul>
				<li>
					<a href="#" onmouseover="setResult('Dropdown 2 - Item A');">
						Item A
					</a>
				</li>
				<li>
					<a href="#" onmouseover="setResult('Dropdown 2 - Item B');">
						Item B
					</a>
				</li>
				<li>
					<a href="#" onmouseover="setResult('Dropdown 2 - Item C');">
						Item C
					</a>
				</li>
				<li>
					<a href="#" onmouseover="setResult('Dropdown 2 - Item D');">
						Item D
					</a>
				</li>
			</ul>
		</li>
	</ul>
	<p id="result" style="position: absolute; top: 100px;"></p>
</body>
</html>

================================================
FILE: tests/api/data/test_iframe/iframe.html
================================================
<!DOCTYPE html>
<html>
<head>
    <title>test_iframe - iframe</title>
</head>
<body>
This text is inside an iframe.
<iframe src="nested_iframe.html"></iframe>
</body>
</html>

================================================
FILE: tests/api/data/test_iframe/main.html
================================================
<!DOCTYPE html>
<html>
<head>
    <title>test_iframe - main</title>
</head>
<body>
<iframe src="iframe.html"></iframe>
</body>
</html>

================================================
FILE: tests/api/data/test_iframe/nested_iframe.html
================================================
<!DOCTYPE html>
<html>
<head>
    <title>test_iframe - nested iframe</title>
</head>
<body>
This text is inside a nested iframe.
</body>
</html>

================================================
FILE: tests/api/data/test_implicit_wait.html
================================================
<!DOCTYPE html>
<html>
<head>
	<title>test_auto_wait</title>
	<script type="text/javascript">
		function createSecondButton() {
			var secondButton = document.createElement("button");
			secondButton.innerHTML = "Now click me!";
			secondButton.onclick = createResultPara;
			appendToBody(secondButton);
		}
		function createResultPara() {
			var resultPara = document.createElement("p");
			resultPara.innerHTML = "Success!";
			resultPara.id = "result";
			appendToBody(resultPara);
		}
		function appendToBody(element) {
			var body = document.getElementsByTagName('body')[0];
			body.appendChild(element);
		}
	</script>
</head>
<body>
	<button onclick="setTimeout(createSecondButton, 5000);">Click me!</button>
</body>
</html>

================================================
FILE: tests/api/data/test_leaked_password.html
================================================
<!DOCTYPE html>
<html>
<head>
	<title>Leaked Password Test</title>
</head>
<body>
	<form method="GET">
		<table>
			<tr>
				<td>Username:</td>
				<td><input id="username" name="username" type="text"/></td>
			</tr>
			<tr>
				<td>Password:</td>
				<td><input id="password" name="password" type="password"/></td>
			</tr>
			<tr>
				<td colspan="2">
					<button type="submit" id="submitBtn">Submit</button>
				</td>
			</tr>
		</table>
	</form>
	<div id="result" style="margin-top: 20px;"></div>

	<script>
		window.onload = function() {
			var params = new URLSearchParams(window.location.search);
			var username = params.get('username');
			var password = params.get('password');
			var resultDiv = document.getElementById('result');

			if (username && password) {
				resultDiv.innerHTML = 
					'You logged in with ' + username + ':' + password;
			}
		};
	</script>
</body>
</html>

================================================
FILE: tests/api/data/test_point.html
================================================
<!DOCTYPE html>
<html>
<head>
	<title>test_point</title>
	<script type="text/javascript" src="js/util.js"></script>
	<script type="text/javascript" src="js/jquery.min.js"></script>
	<script type="text/javascript">
		function getMouseOffset(event){
			// Taken / modified from http://stackoverflow.com/a/5932203/1839209.
			var totalOffsetX = 0;
			var totalOffsetY = 0;
			var currElement = event.target;

			do {
				totalOffsetX += currElement.offsetLeft - currElement.scrollLeft;
				totalOffsetY += currElement.offsetTop - currElement.scrollTop;
			}
			while(currElement = currElement.offsetParent);

			return {x:event.pageX - totalOffsetX, y:event.pageY - totalOffsetY}
		}
		function updateResult(evt, action) {
			var offset = getMouseOffset(evt);
			setResult(
				evt.target.innerHTML + ' ' + action + ' at offset (' +
				offset.x + ', ' + offset.y + ').'
			);
		}
		$(document).ready(function() {
			$("button").click(function(evt) {
				updateResult(evt, 'clicked');
				$("button").off("mousemove");
			});
			$("button").mousemove(function(evt) {
				updateResult(evt, 'hovered');
			});
			$("button").mousedown(function(evt) {
				if (evt.which == 3) {
					updateResult(evt, 'rightclicked');
					$("button").off("mousemove");
				}
			});
			$("button").contextmenu(function(evt) {
				evt.preventDefault();
				evt.stopPropagation();
				return false;
			});
			$("button").dblclick(function(evt) {
				updateResult(evt, 'doubleclicked');
				$("button").off("mousemove");
			});
		});
	</script>
	<style type="text/css">
		body, table, tbody, tr, td, button {
			padding: 0;
			margin: 0;
		}
		button {
			width: 100px;
			height: 30px;
		}
	</style>
</head>
<body>
<table style="width: 200px; height: 60px;">
	<tr>
		<td><button>Button 1</button></td>
		<td><button>Button 2</button></td>
	</tr>
	<tr>
		<td><button>Button 3</button></td>
		<td><button>Button 4</button></td>
	</tr>
</table>
<p id="result"></p>
</body>
</html>

================================================
FILE: tests/api/data/test_rightclick.html
================================================
<!DOCTYPE html>
<html>
<head>
	<title>test_rightclick</title>
	<link type="text/css" rel="stylesheet"
		  href="http://code.jquery.com/ui/1.10.1/themes/base/jquery-ui.css" />
	<script type="text/javascript" src="js/util.js"></script>
	<script type="text/javascript" src="js/jquery.min.js"></script>
	<script type="text/javascript" src="js/jquery-ui.min.js"></script>
	<script type="text/javascript" src="js/jquery.ui-contextmenu.js"></script>
	<script type="text/javascript">
		$(document).ready(function() {
			$('#normalRightclick').mousedown(function(event) {
				if (event.which == 3) {
					setResult("Normal rightclick performed.");
				}
			});
			// Disable context menu "Inspect element" etc.:
			document.getElementById('normalRightclick').oncontextmenu =
					function(event) {
						event.preventDefault();
						event.stopPropagation();
						return false;
					};
			$("#contextMenu").contextmenu({
				menu: [
					{title: "Normal item", cmd: "Normal item"},
					{title: "Item with sub items", cmd: "Item with sub items",
						children: [
							{title: "Sub item 1", cmd: "Sub item 1"},
							{title: "Sub item 2", cmd: "Sub item 2"}
						]
					}],
				// Don't "slide in". On Chrome, Helium sometimes clicks too
				// early (when the default "sliding in" is not yet complete), in
				// which case the test fails:
				show: 0,
				select: function(event, ui) {
					setResult(ui.cmd + ' selected.');
				}
			});
		});
	</script>
</head>
<body>
	<p id="normalRightclick">Perform a normal rightclick here.</p>
	<p id="contextMenu">Rightclick here for context menu.</p>
	<p id="result"></p>
</body>
</html>

================================================
FILE: tests/api/data/test_scroll.html
================================================
<!DOCTYPE html>
<html>
<head>
	<title></title>
</head>
<body>
	<div style="width: 5000px; height: 5000px; margin: 10px;
		padding: 10px; border: 1px solid #aaa
Download .txt
gitextract_83h9li4j/

├── .gitattributes
├── .gitignore
├── .readthedocs.yaml
├── LICENSE.txt
├── NOTICE.txt
├── README.md
├── docs/
│   ├── Makefile
│   ├── README.md
│   ├── api.rst
│   ├── cheatsheet.md
│   ├── conf.py
│   ├── contributors.rst
│   ├── index.rst
│   ├── installation.rst
│   └── make.bat
├── helium/
│   ├── __init__.py
│   └── _impl/
│       ├── __init__.py
│       ├── match_type.py
│       ├── selenium_wrappers.py
│       └── util/
│           ├── __init__.py
│           ├── dictionary.py
│           ├── geom.py
│           ├── html.py
│           ├── inspect_.py
│           ├── lang.py
│           ├── path.py
│           ├── system.py
│           └── xpath.py
├── requirements/
│   ├── base.txt
│   ├── docs.txt
│   └── test.txt
├── setup.py
└── tests/
    ├── __init__.py
    ├── api/
    │   ├── __init__.py
    │   ├── data/
    │   │   ├── default.css
    │   │   ├── js/
    │   │   │   ├── jquery.ui-contextmenu.js
    │   │   │   └── util.js
    │   │   ├── test_alert.html
    │   │   ├── test_aria.html
    │   │   ├── test_click.html
    │   │   ├── test_doubleclick.html
    │   │   ├── test_drag/
    │   │   │   ├── default.html
    │   │   │   ├── html5.html
    │   │   │   └── test_drag.css
    │   │   ├── test_file_upload/
    │   │   │   └── test_file_upload.html
    │   │   ├── test_gui_elements.html
    │   │   ├── test_gui_elements_iframe.html
    │   │   ├── test_hover.html
    │   │   ├── test_iframe/
    │   │   │   ├── iframe.html
    │   │   │   ├── main.html
    │   │   │   └── nested_iframe.html
    │   │   ├── test_implicit_wait.html
    │   │   ├── test_leaked_password.html
    │   │   ├── test_point.html
    │   │   ├── test_rightclick.html
    │   │   ├── test_scroll.html
    │   │   ├── test_start_go_to.html
    │   │   ├── test_tables.html
    │   │   ├── test_text_impl.html
    │   │   ├── test_wait_until.html
    │   │   ├── test_window/
    │   │   │   ├── popup.html
    │   │   │   └── test_window.html
    │   │   ├── test_window_handling/
    │   │   │   ├── main.html
    │   │   │   ├── main_immediate_popup.html
    │   │   │   └── popup.html
    │   │   └── test_write.html
    │   ├── test_alert.py
    │   ├── test_aria.py
    │   ├── test_chrome_options.py
    │   ├── test_click.py
    │   ├── test_doubleclick.py
    │   ├── test_drag.py
    │   ├── test_file_upload.py
    │   ├── test_find_all.py
    │   ├── test_gui_elements.py
    │   ├── test_highlight.py
    │   ├── test_hover.py
    │   ├── test_iframe.py
    │   ├── test_implicit_wait.py
    │   ├── test_kill_service_at_exit.py
    │   ├── test_kill_service_at_exit_chrome.py
    │   ├── test_leaked_password.py
    │   ├── test_no_driver.py
    │   ├── test_point.py
    │   ├── test_press.py
    │   ├── test_repr.py
    │   ├── test_rightclick.py
    │   ├── test_s.py
    │   ├── test_scroll.py
    │   ├── test_start_go_to.py
    │   ├── test_tables.py
    │   ├── test_text_impl.py
    │   ├── test_wait_until.py
    │   ├── test_window.py
    │   ├── test_window_handling.py
    │   ├── test_write.py
    │   └── util.py
    └── unit/
        ├── __init__.py
        └── test__impl/
            ├── __init__.py
            ├── test_selenium_wrappers.py
            └── test_util/
                ├── __init__.py
                ├── test_dictionary.py
                ├── test_html.py
                └── test_xpath.py
Download .txt
SYMBOL INDEX (916 symbols across 50 files)

FILE: helium/__init__.py
  function start_chrome (line 22) | def start_chrome(url=None, headless=False, maximize=False, options=None):
  function start_firefox (line 67) | def start_firefox(url=None, headless=False, options=None, profile=None):
  function go_to (line 131) | def go_to(url):
  function set_driver (line 142) | def set_driver(driver):
  function get_driver (line 149) | def get_driver():
  function write (line 157) | def write(text, into=None):
  function press (line 175) | def press(key):
  function click (line 267) | def click(element):
  function doubleclick (line 282) | def doubleclick(element):
  function drag (line 297) | def drag(element, to):
  function press_mouse_on (line 320) | def press_mouse_on(element):
  function release_mouse_over (line 323) | def release_mouse_over(element):
  function find_all (line 326) | def find_all(predicate):
  function scroll_down (line 353) | def scroll_down(num_pixels=100):
  function scroll_up (line 359) | def scroll_up(num_pixels=100):
  function scroll_right (line 365) | def scroll_right(num_pixels=100):
  function scroll_left (line 371) | def scroll_left(num_pixels=100):
  function hover (line 377) | def hover(element):
  function rightclick (line 393) | def rightclick(element):
  function select (line 407) | def select(combo_box, value):
  function drag_file (line 420) | def drag_file(file_path, to):
  function attach_file (line 433) | def attach_file(file_path, to=None):
  function refresh (line 448) | def refresh():
  function wait_until (line 455) | def wait_until(condition_fn, timeout_secs=10, interval_secs=0.5):
  class Config (line 482) | class Config:
  class GUIElement (line 515) | class GUIElement:
    method __init__ (line 516) | def __init__(self):
    method exists (line 521) | def exists(self):
    method with_impl (line 526) | def with_impl(self, impl):
    method _impl (line 531) | def _impl(self):
    method _impl (line 540) | def _impl(self, value):
    method __repr__ (line 542) | def __repr__(self):
    method _repr_constructor_args (line 544) | def _repr_constructor_args(self, args=None, kwargs=None):
    method _is_bound (line 553) | def _is_bound(self):
  class HTMLElement (line 556) | class HTMLElement(GUIElement):
    method __init__ (line 557) | def __init__(
    method width (line 566) | def width(self):
    method height (line 572) | def height(self):
    method x (line 578) | def x(self):
    method y (line 584) | def y(self):
    method top_left (line 590) | def top_left(self):
    method web_element (line 601) | def web_element(self):
    method __repr__ (line 606) | def __repr__(self):
  class S (line 623) | class S(HTMLElement):
    method __init__ (line 656) | def __init__(self, selector, below=None, to_right_of=None, above=None,
  class Text (line 664) | class Text(HTMLElement):
    method __init__ (line 682) | def __init__(
    method value (line 692) | def value(self):
  class Link (line 698) | class Link(HTMLElement):
    method __init__ (line 716) | def __init__(
    method href (line 726) | def href(self):
  class ListItem (line 732) | class ListItem(HTMLElement):
    method __init__ (line 751) | def __init__(
  class Button (line 761) | class Button(HTMLElement):
    method __init__ (line 779) | def __init__(
    method is_enabled (line 788) | def is_enabled(self):
  class Image (line 794) | class Image(HTMLElement):
    method __init__ (line 813) | def __init__(
  class TextField (line 823) | class TextField(HTMLElement):
    method __init__ (line 839) | def __init__(
    method value (line 849) | def value(self):
    method is_enabled (line 854) | def is_enabled(self):
    method is_editable (line 863) | def is_editable(self):
  class ComboBox (line 873) | class ComboBox(HTMLElement):
    method __init__ (line 893) | def __init__(
    method is_editable (line 902) | def is_editable(self):
    method value (line 909) | def value(self):
    method options (line 915) | def options(self):
  class CheckBox (line 922) | class CheckBox(HTMLElement):
    method __init__ (line 942) | def __init__(
    method is_enabled (line 951) | def is_enabled(self):
    method is_checked (line 956) | def is_checked(self):
  class RadioButton (line 962) | class RadioButton(HTMLElement):
    method __init__ (line 982) | def __init__(
    method is_selected (line 991) | def is_selected(self):
  class Window (line 997) | class Window(GUIElement):
    method __init__ (line 1001) | def __init__(self, title=None):
    method title (line 1005) | def title(self):
    method handle (line 1011) | def handle(self):
    method __repr__ (line 1019) | def __repr__(self):
  class Alert (line 1025) | class Alert(GUIElement):
    method __init__ (line 1029) | def __init__(self, search_text=None):
    method text (line 1033) | def text(self):
    method accept (line 1038) | def accept(self):
    method dismiss (line 1048) | def dismiss(self):
    method __repr__ (line 1059) | def __repr__(self):
  class Point (line 1065) | class Point(namedtuple('Point', ['x', 'y'])):
    method __new__ (line 1076) | def __new__(cls, x=0, y=0):
    method __init__ (line 1078) | def __init__(self, x=0, y=0):
    method x (line 1083) | def x(self):
    method y (line 1089) | def y(self):
    method __eq__ (line 1094) | def __eq__(self, other):
    method __ne__ (line 1096) | def __ne__(self, other):
    method __hash__ (line 1098) | def __hash__(self):
    method __add__ (line 1100) | def __add__(self, delta):
    method __radd__ (line 1103) | def __radd__(self, delta):
    method __sub__ (line 1105) | def __sub__(self, delta):
    method __rsub__ (line 1108) | def __rsub__(self, delta):
  function switch_to (line 1112) | def switch_to(window):
  function kill_browser (line 1133) | def kill_browser():
  function highlight (line 1149) | def highlight(element):
  function _get_api_impl (line 1161) | def _get_api_impl():

FILE: helium/_impl/__init__.py
  function might_spawn_window (line 24) | def might_spawn_window(f):
  function handle_unexpected_alert (line 52) | def handle_unexpected_alert(f):
  class APIImpl (line 67) | class APIImpl:
    method __init__ (line 74) | def __init__(self):
    method start_firefox_impl (line 76) | def start_firefox_impl(
    method _start_firefox_driver (line 81) | def _start_firefox_driver(self, headless, options, profile):
    method start_chrome_impl (line 94) | def start_chrome_impl(
    method _start_chrome_driver (line 99) | def _start_chrome_driver(self, headless, maximize, options):
    method _get_chrome_options (line 104) | def _get_chrome_options(self, headless, maximize, options):
    method _kill_service (line 123) | def _kill_service(self, service):
    method _start (line 130) | def _start(self, browser, url=None):
    method go_to_impl (line 137) | def go_to_impl(self, url):
    method set_driver_impl (line 141) | def set_driver_impl(self, driver):
    method get_driver_impl (line 143) | def get_driver_impl(self):
    method write_impl (line 148) | def write_impl(self, text, into=None):
    method _write_no_alert (line 156) | def _write_no_alert(self, text, into=None):
    method _write_with_alert (line 167) | def _write_with_alert(self, text, into=None):
    method _handle_alerts (line 175) | def _handle_alerts(self, no_alert, with_alert, *args, **kwargs):
    method press_impl (line 182) | def press_impl(self, key):
    method click_impl (line 184) | def click_impl(self, element):
    method doubleclick_impl (line 186) | def doubleclick_impl(self, element):
    method hover_impl (line 188) | def hover_impl(self, element):
    method rightclick_impl (line 190) | def rightclick_impl(self, element):
    method press_mouse_on_impl (line 192) | def press_mouse_on_impl(self, element):
    method release_mouse_over_impl (line 194) | def release_mouse_over_impl(self, element):
    method _click (line 196) | def _click(self, selenium_elt, offset):
    method _doubleclick (line 198) | def _doubleclick(self, selenium_elt, offset):
    method _hover (line 200) | def _hover(self, selenium_elt, offset):
    method _rightclick (line 202) | def _rightclick(self, selenium_elt, offset):
    method _press_mouse_on (line 204) | def _press_mouse_on(self, selenium_elt, offset):
    method _release_mouse_over (line 206) | def _release_mouse_over(self, selenium_elt, offset):
    method _move_to_element (line 208) | def _move_to_element(self, element, offset):
    method drag_impl (line 219) | def drag_impl(self, element, to):
    method _perform_mouse_action (line 225) | def _perform_mouse_action(self, element, action):
    method _unwrap_clickable_element (line 228) | def _unwrap_clickable_element(self, elt):
    method _point_to_element_and_offset (line 238) | def _point_to_element_and_offset(self, point):
    method find_all_impl (line 253) | def find_all_impl(self, predicate):
    method scroll_down_impl (line 263) | def scroll_down_impl(self, num_pixels):
    method scroll_up_impl (line 265) | def scroll_up_impl(self, num_pixels):
    method scroll_right_impl (line 267) | def scroll_right_impl(self, num_pixels):
    method scroll_left_impl (line 269) | def scroll_left_impl(self, num_pixels):
    method _scroll_by (line 272) | def _scroll_by(self, dx_pixels, dy_pixels):
    method select_impl (line 278) | def select_impl(self, combo_box, value):
    method _manipulate (line 289) | def _manipulate(self, gui_or_web_elt, action):
    method drag_file_impl (line 300) | def drag_file_impl(self, file_path, to):
    method attach_file_impl (line 314) | def attach_file_impl(self, file_path, to=None):
    method refresh_impl (line 324) | def refresh_impl(self):
    method _refresh_no_alert (line 328) | def _refresh_no_alert(self):
    method _refresh_with_alert (line 330) | def _refresh_with_alert(self):
    method wait_until_impl (line 333) | def wait_until_impl(self, condition_fn, timeout_secs=10, interval_secs...
    method switch_to_impl (line 354) | def switch_to_impl(self, window):
    method kill_browser_impl (line 362) | def kill_browser_impl(self):
    method highlight_impl (line 366) | def highlight_impl(self, element):
    method require_driver (line 394) | def require_driver(self):
  class DragHelper (line 399) | class DragHelper:
    method __init__ (line 400) | def __init__(self, api_impl):
    method __enter__ (line 403) | def __enter__(self):
    method start_dragging (line 424) | def start_dragging(self, element, offset):
    method drop_on_target (line 429) | def drop_on_target(self, target, offset):
    method _attempt_html_5_drag (line 434) | def _attempt_html_5_drag(self, element_to_drag):
    method _complete_html_5_drag (line 464) | def _complete_html_5_drag(self, on):
    method __exit__ (line 475) | def __exit__(self, *_):
    method _execute_script (line 477) | def _execute_script(self, script, *args):
  class DragAndDropFile (line 480) | class DragAndDropFile:
    method __init__ (line 481) | def __init__(self, driver, file_path):
    method begin (line 486) | def begin(self):
    method _create_file_input_element (line 493) | def _create_file_input_element(self):
    method drag_over_document (line 520) | def drag_over_document(self):
    method _dispatch_event (line 538) | def _dispatch_event(self, event_name, to):
    method _prepare_continuous_event (line 541) | def _prepare_continuous_event(self, event_name, to, interval_msecs):
    method _prepare_dispatch_event (line 544) | def _prepare_dispatch_event(self, event_name, to):
    method drop_on (line 567) | def drop_on(self, target):
    method end (line 570) | def end(self):
  class JavaScriptInterval (line 578) | class JavaScriptInterval:
    method __init__ (line 579) | def __init__(self, driver, script, args, interval_msecs):
    method start (line 585) | def start(self):
    method stop (line 595) | def stop(self):
  class GUIElementImpl (line 601) | class GUIElementImpl:
    method __init__ (line 602) | def __init__(self, driver):
    method iter_all (line 605) | def iter_all(self, ignore_frame_changes=False):
    method _is_bound (line 617) | def _is_bound(self):
    method iter_all_occurrences (line 619) | def iter_all_occurrences(self):
    method bound_to_occurrence (line 621) | def bound_to_occurrence(self, occurrence):
    method exists (line 625) | def exists(self):
    method first_occurrence (line 633) | def first_occurrence(self):
    method _bind_to_first_occurrence (line 637) | def _bind_to_first_occurrence(self):
    method perform (line 640) | def perform(self, action):
    method _perform_no_wait (line 650) | def _perform_no_wait(self, action):
    method should_ignore_exception (line 661) | def should_ignore_exception(self, exception):
  class HTMLElementImpl (line 676) | class HTMLElementImpl(GUIElementImpl):
    method __init__ (line 677) | def __init__(
    method find_anywhere_in_curr_frame (line 687) | def find_anywhere_in_curr_frame(self):
    method width (line 690) | def width(self):
    method height (line 693) | def height(self):
    method x (line 696) | def x(self):
    method y (line 699) | def y(self):
    method top_left (line 702) | def top_left(self):
    method web_element (line 706) | def web_element(self):
    method iter_all_occurrences (line 708) | def iter_all_occurrences(self):
    method _handle_closed_window (line 722) | def _handle_closed_window(self):
    method _find_all_in_curr_frame (line 732) | def _find_all_in_curr_frame(self):
    method _get_search_regions_in_curr_frame (line 739) | def _get_search_regions_in_curr_frame(self):
    method _resolve_in_curr_frame (line 762) | def _resolve_in_curr_frame(self, element):
    method _is_in_any_search_region (line 766) | def _is_in_any_search_region(self, element, search_regions):
    method _is_enabled (line 776) | def _is_enabled(self):
    method _unwrap_element (line 781) | def _unwrap_element(self, element):
  class SImpl (line 789) | class SImpl(HTMLElementImpl):
    method __init__ (line 790) | def __init__(self, driver, selector, **kwargs):
    method find_anywhere_in_curr_frame (line 793) | def find_anywhere_in_curr_frame(self):
  class HTMLElementIdentifiedByXPath (line 801) | class HTMLElementIdentifiedByXPath(HTMLElementImpl):
    method find_anywhere_in_curr_frame (line 802) | def find_anywhere_in_curr_frame(self):
    method _sort_search_result (line 809) | def _sort_search_result(self, search_result):
    method get_xpath (line 822) | def get_xpath(self):
    method get_sort_index (line 824) | def get_sort_index(self, web_element):
  class HTMLElementContainingText (line 827) | class HTMLElementContainingText(HTMLElementIdentifiedByXPath):
    method __init__ (line 828) | def __init__(self, driver, text=None, **kwargs):
    method get_xpath (line 831) | def get_xpath(self):
    method get_xpath_node_selector (line 835) | def get_xpath_node_selector(self):
  class TextImpl (line 838) | class TextImpl(HTMLElementContainingText):
    method __init__ (line 839) | def __init__(self, driver, text=None, include_free_text=True, **kwargs):
    method value (line 843) | def value(self):
    method get_xpath (line 845) | def get_xpath(self):
    method _get_search_text_xpath (line 858) | def _get_search_text_xpath(self):
  class FreeText (line 868) | class FreeText(HTMLElementContainingText):
    method get_xpath_node_selector (line 869) | def get_xpath_node_selector(self):
    method get_xpath (line 871) | def get_xpath(self):
  class LinkImpl (line 874) | class LinkImpl(HTMLElementContainingText):
    method get_xpath_node_selector (line 875) | def get_xpath_node_selector(self):
    method get_xpath (line 877) | def get_xpath(self):
    method href (line 884) | def href(self):
  class ListItemImpl (line 887) | class ListItemImpl(HTMLElementContainingText):
    method get_xpath_node_selector (line 888) | def get_xpath_node_selector(self):
  class ButtonImpl (line 891) | class ButtonImpl(HTMLElementContainingText):
    method get_xpath_node_selector (line 892) | def get_xpath_node_selector(self):
    method is_enabled (line 894) | def is_enabled(self):
    method get_xpath (line 898) | def get_xpath(self):
    method get_input_button_xpath (line 907) | def get_input_button_xpath(self):
  class ImageImpl (line 919) | class ImageImpl(HTMLElementIdentifiedByXPath):
    method __init__ (line 920) | def __init__(self, driver, alt, **kwargs):
    method get_xpath (line 923) | def get_xpath(self):
  class LabelledElement (line 926) | class LabelledElement(HTMLElementImpl):
    method __init__ (line 928) | def __init__(self, driver, label=None, **kwargs):
    method find_anywhere_in_curr_frame (line 931) | def find_anywhere_in_curr_frame(self):
    method _find_elts (line 945) | def _find_elts(self, xpath=None):
    method _find_elts_by_free_text (line 951) | def _find_elts_by_free_text(self):
    method get_xpath (line 964) | def get_xpath(self):
    method get_primary_search_direction (line 966) | def get_primary_search_direction(self):
    method get_secondary_search_direction (line 968) | def get_secondary_search_direction(self):
    method _filter_elts_belonging_to_labels (line 970) | def _filter_elts_belonging_to_labels(self, all_elts, labels):
    method _get_labels_with_explicit_elts (line 982) | def _get_labels_with_explicit_elts(self, all_elts, labels):
    method _get_related_elts (line 995) | def _get_related_elts(self, all_elts, labels):
    method _are_related (line 1008) | def _are_related(self, elt, label):
    method _ensure_at_most_one_label_per_elt (line 1017) | def _ensure_at_most_one_label_per_elt(self, labels_to_elts):
    method _retain_closest (line 1021) | def _retain_closest(self, pivots_to_elts):
    method _find_closest (line 1027) | def _find_closest(self, to_pivot, among_elts):
    method _compute_distance (line 1042) | def _compute_distance(self, elt_1, elt_2):
  class CompositeElement (line 1051) | class CompositeElement(HTMLElementImpl):
    method __init__ (line 1052) | def __init__(self, driver, *args, **kwargs):
    method first_element (line 1058) | def first_element(self):
    method find_anywhere_in_curr_frame (line 1063) | def find_anywhere_in_curr_frame(self):
    method get_elements (line 1072) | def get_elements(self):
    method get_element_types (line 1075) | def get_element_types(self):
  class ClickableText (line 1078) | class ClickableText(CompositeElement):
    method get_element_types (line 1079) | def get_element_types(self):
  class TextFieldImpl (line 1082) | class TextFieldImpl(CompositeElement):
    method get_element_types (line 1083) | def get_element_types(self):
    method value (line 1089) | def value(self):
    method is_enabled (line 1091) | def is_enabled(self):
    method is_editable (line 1093) | def is_editable(self):
  class StandardTextFieldWithLabel (line 1096) | class StandardTextFieldWithLabel(LabelledElement):
    method value (line 1098) | def value(self):
    method is_enabled (line 1100) | def is_enabled(self):
    method is_editable (line 1102) | def is_editable(self):
    method get_xpath (line 1104) | def get_xpath(self):
  class AriaTextFieldWithLabel (line 1111) | class AriaTextFieldWithLabel(LabelledElement):
    method value (line 1113) | def value(self):
    method is_enabled (line 1115) | def is_enabled(self):
    method is_editable (line 1117) | def is_editable(self):
    method get_xpath (line 1119) | def get_xpath(self):
  class StandardTextFieldWithPlaceholder (line 1122) | class StandardTextFieldWithPlaceholder(HTMLElementIdentifiedByXPath):
    method __init__ (line 1123) | def __init__(self, driver, label, **kwargs):
    method value (line 1127) | def value(self):
    method is_enabled (line 1129) | def is_enabled(self):
    method is_editable (line 1131) | def is_editable(self):
    method get_xpath (line 1133) | def get_xpath(self):
  class FileInput (line 1139) | class FileInput(LabelledElement):
    method get_xpath (line 1140) | def get_xpath(self):
  class ComboBoxImpl (line 1143) | class ComboBoxImpl(CompositeElement):
    method get_element_types (line 1144) | def get_element_types(self):
    method is_editable (line 1146) | def is_editable(self):
    method value (line 1149) | def value(self):
    method options (line 1155) | def options(self):
    method _select_driver (line 1158) | def _select_driver(self):
  class ComboBoxIdentifiedByLabel (line 1161) | class ComboBoxIdentifiedByLabel(LabelledElement):
    method get_xpath (line 1162) | def get_xpath(self):
  class ComboBoxIdentifiedByDisplayedValue (line 1165) | class ComboBoxIdentifiedByDisplayedValue(HTMLElementContainingText):
    method get_xpath_node_selector (line 1166) | def get_xpath_node_selector(self):
    method get_xpath (line 1168) | def get_xpath(self):
    method find_anywhere_in_curr_frame (line 1172) | def find_anywhere_in_curr_frame(self):
  class CheckBoxImpl (line 1184) | class CheckBoxImpl(LabelledElement):
    method is_enabled (line 1185) | def is_enabled(self):
    method is_checked (line 1187) | def is_checked(self):
    method get_xpath (line 1189) | def get_xpath(self):
    method get_primary_search_direction (line 1191) | def get_primary_search_direction(self):
    method get_secondary_search_direction (line 1193) | def get_secondary_search_direction(self):
  class RadioButtonImpl (line 1196) | class RadioButtonImpl(LabelledElement):
    method is_selected (line 1197) | def is_selected(self):
    method get_xpath (line 1199) | def get_xpath(self):
    method get_primary_search_direction (line 1201) | def get_primary_search_direction(self):
    method get_secondary_search_direction (line 1203) | def get_secondary_search_direction(self):
  class WindowImpl (line 1206) | class WindowImpl(GUIElementImpl):
    method __init__ (line 1207) | def __init__(self, driver, title=None):
    method iter_all_occurrences (line 1210) | def iter_all_occurrences(self):
    method title (line 1226) | def title(self):
    method handle (line 1229) | def handle(self):
    class SeleniumWindow (line 1231) | class SeleniumWindow:
      method __init__ (line 1232) | def __init__(self, driver, handle):
      method title (line 1237) | def title(self):
      method __enter__ (line 1240) | def __enter__(self):
      method __exit__ (line 1249) | def __exit__(self, *_):
  class AlertImpl (line 1254) | class AlertImpl(GUIElementImpl):
    method __init__ (line 1255) | def __init__(self, driver, search_text=None):
    method iter_all_occurrences (line 1258) | def iter_all_occurrences(self):
    method text (line 1267) | def text(self):
    method accept (line 1269) | def accept(self):
    method dismiss (line 1285) | def dismiss(self):
    method _write (line 1287) | def _write(self, text):

FILE: helium/_impl/match_type.py
  class MatchType (line 3) | class MatchType:
    method xpath (line 4) | def xpath(self, value, text):
    method text (line 6) | def text(self, value, text):
  class PREFIX_IGNORE_CASE (line 9) | class PREFIX_IGNORE_CASE(MatchType):
    method xpath (line 10) | def xpath(self, value, text):
    method text (line 33) | def text(self, value, text):

FILE: helium/_impl/selenium_wrappers.py
  class Wrapper (line 8) | class Wrapper:
    method __init__ (line 9) | def __init__(self, target):
    method __getattr__ (line 11) | def __getattr__(self, item):
    method unwrap (line 13) | def unwrap(self):
    method __hash__ (line 15) | def __hash__(self):
    method __eq__ (line 17) | def __eq__(self, other):
    method __ne__ (line 19) | def __ne__(self, other):
  class WebDriverWrapper (line 22) | class WebDriverWrapper(Wrapper):
    method __init__ (line 23) | def __init__(self, target):
    method action (line 26) | def action(self):
    method get_distance_to_last_manipulated (line 28) | def get_distance_to_last_manipulated(self, web_element):
    method is_firefox (line 41) | def is_firefox(self):
    method browser_name (line 44) | def browser_name(self):
    method is_ie (line 46) | def is_ie(self):
  function _translate_url_errors_caused_by_server_shutdown (line 49) | def _translate_url_errors_caused_by_server_shutdown(f):
  function _is_caused_by_server_shutdown (line 63) | def _is_caused_by_server_shutdown(url_error):
  function handle_element_being_in_other_frame (line 70) | def handle_element_being_in_other_frame(f):
  class WebElementWrapper (line 87) | class WebElementWrapper:
    method __init__ (line 88) | def __init__(self, target, frame_index=None):
    method location (line 95) | def location(self):
    method is_displayed (line 105) | def is_displayed(self):
    method get_attribute (line 113) | def get_attribute(self, attr_name):
    method text (line 117) | def text(self):
    method clear (line 120) | def clear(self):
    method send_keys (line 123) | def send_keys(self, keys):
    method tag_name (line 127) | def tag_name(self):
    method unwrap (line 129) | def unwrap(self):
    method __repr__ (line 131) | def __repr__(self):
  class FrameIterator (line 134) | class FrameIterator:
    method __init__ (line 135) | def __init__(self, driver, start_frame=None):
    method __iter__ (line 140) | def __iter__(self):
    method switch_to_frame (line 155) | def switch_to_frame(self, frame_index_path):
  class FramesChangedWhileIterating (line 160) | class FramesChangedWhileIterating(Exception):

FILE: helium/_impl/util/dictionary.py
  function inverse (line 1) | def inverse(dictionary):

FILE: helium/_impl/util/geom.py
  class Rectangle (line 4) | class Rectangle:
    method __init__ (line 5) | def __init__(self, left=0, top=0, width=0, height=0):
    method from_w_h (line 11) | def from_w_h(cls, width, height):
    method from_tuple_l_t_w_h (line 14) | def from_tuple_l_t_w_h(cls, l_t_w_h=None):
    method from_tuple_w_h (line 19) | def from_tuple_w_h(cls, w_h):
    method from_struct_l_t_r_b (line 22) | def from_struct_l_t_r_b(cls, struct):
    method from_l_t_r_b (line 27) | def from_l_t_r_b(cls, left, top, right, bottom):
    method width (line 30) | def width(self):
    method height (line 33) | def height(self):
    method center (line 36) | def center(self):
    method east (line 39) | def east(self):
    method west (line 42) | def west(self):
    method north (line 45) | def north(self):
    method south (line 48) | def south(self):
    method northeast (line 51) | def northeast(self):
    method southeast (line 54) | def southeast(self):
    method southwest (line 57) | def southwest(self):
    method northwest (line 60) | def northwest(self):
    method area (line 63) | def area(self):
    method __contains__ (line 67) | def __contains__(self, point):
    method translate (line 70) | def translate(self, dx, dy):
    method clip (line 76) | def clip(self, point):
    method intersect (line 81) | def intersect(self, rectangle):
    method intersects (line 87) | def intersects(self, rectangle):
    method as_numpy_slice (line 89) | def as_numpy_slice(self):
    method is_to_left_of (line 91) | def is_to_left_of(self, other):
    method is_to_right_of (line 99) | def is_to_right_of(self, other):
    method is_above (line 101) | def is_above(self, other):
    method is_below (line 109) | def is_below(self, other):
    method is_in_direction (line 111) | def is_in_direction(self, in_direction, of_other):
    method distance_to (line 113) | def distance_to(self, other):
    method __eq__ (line 121) | def __eq__(self, other):
    method __ne__ (line 126) | def __ne__(self, other):
    method __bool__ (line 128) | def __bool__(self):
    method __repr__ (line 130) | def __repr__(self):
    method __hash__ (line 133) | def __hash__(self):
  class Point (line 136) | class Point(namedtuple('Point', ['x', 'y'])):
    method __new__ (line 137) | def __new__(cls, x=0, y=0):
    method __init__ (line 139) | def __init__(self, x=0, y=0):
    method from_tuple (line 144) | def from_tuple(cls, tpl):
    method __eq__ (line 146) | def __eq__(self, other):
    method __ne__ (line 148) | def __ne__(self, other):
    method __add__ (line 150) | def __add__(self, other):
    method __radd__ (line 153) | def __radd__(self, other):
    method __sub__ (line 155) | def __sub__(self, other):
    method __rsub__ (line 158) | def __rsub__(self, other):
    method __mul__ (line 162) | def __mul__(self, scalar):
    method __rmul__ (line 167) | def __rmul__(self, scalar):
    method __div__ (line 169) | def __div__(self, scalar):
    method __bool__ (line 174) | def __bool__(self):
  class Direction (line 177) | class Direction:
    method __init__ (line 178) | def __init__(self, unit_vector):
    method iterate_points_starting_at (line 180) | def iterate_points_starting_at(self, point, offsets):
    method is_horizontal (line 183) | def is_horizontal(self):
    method is_vertical (line 185) | def is_vertical(self):
    method orthog_vector (line 188) | def orthog_vector(self):
    method __eq__ (line 190) | def __eq__(self, other):
    method __repr__ (line 192) | def __repr__(self):

FILE: helium/_impl/util/html.py
  function strip_tags (line 4) | def strip_tags(html):
  class TagStripper (line 9) | class TagStripper(HTMLParser):
    method __init__ (line 10) | def __init__(self):
    method handle_data (line 14) | def handle_data(self, d):
    method get_data (line 16) | def get_data(self):
  function get_easily_readable_snippet (line 19) | def get_easily_readable_snippet(html):
  function normalize_whitespace (line 34) | def normalize_whitespace(html):

FILE: helium/_impl/util/inspect_.py
  function repr_args (line 4) | def repr_args(f, args=None, kwargs=None, repr_fn=repr):

FILE: helium/_impl/util/lang.py
  class TemporaryAttrValue (line 1) | class TemporaryAttrValue:
    method __init__ (line 2) | def __init__(self, obj, attr, value):
    method __enter__ (line 7) | def __enter__(self):
    method __exit__ (line 10) | def __exit__(self, *_):
  function isbound (line 14) | def isbound(method_or_fn):

FILE: helium/_impl/util/path.py
  function get_components (line 5) | def get_components(path):
  function ensure_exists (line 17) | def ensure_exists(path):

FILE: helium/_impl/util/system.py
  function is_windows (line 6) | def is_windows():
  function is_mac (line 9) | def is_mac():
  function is_linux (line 12) | def is_linux():
  function get_canonical_os_name (line 15) | def get_canonical_os_name():

FILE: helium/_impl/util/xpath.py
  function lower (line 2) | def lower(text):
  function replace_nbsp (line 6) | def replace_nbsp(text, by=' '):
  function predicate (line 9) | def predicate(condition):
  function predicate_or (line 12) | def predicate_or(*conditions):

FILE: tests/api/__init__.py
  function test_browser_name (line 11) | def test_browser_name():
  class BrowserAT (line 19) | class BrowserAT(TestCase):
    method setUpClass (line 21) | def setUpClass(cls):
    method setUp (line 29) | def setUp(self):
    method get_url (line 31) | def get_url(self):
    method get_page (line 33) | def get_page(self):
    method read_result_from_browser (line 35) | def read_result_from_browser(self, timeout_secs=3):
    method assertFindsEltWithId (line 44) | def assertFindsEltWithId(self, predicate, id_):
    method tearDownClass (line 47) | def tearDownClass(cls):
  function setUpModule (line 53) | def setUpModule():
  function tearDownModule (line 57) | def tearDownModule():
  function start_browser (line 63) | def start_browser(url=None):

FILE: tests/api/data/js/jquery.ui-contextmenu.js
  function normCommand (line 15) | function normCommand(cmd){

FILE: tests/api/data/js/util.js
  function setResult (line 1) | function setResult(result) {

FILE: tests/api/test_alert.py
  class AlertAT (line 12) | class AlertAT():
    method get_page (line 21) | def get_page(self):
    method get_link_to_open_alert (line 23) | def get_link_to_open_alert(self):
    method get_expected_alert_text (line 25) | def get_expected_alert_text(self):
    method get_expected_alert_accepted_result (line 27) | def get_expected_alert_accepted_result(self):
    method get_expected_alert_dismissed_result (line 29) | def get_expected_alert_dismissed_result(self):
    method setUp (line 31) | def setUp(self):
    method tearDown (line 35) | def tearDown(self):
    method test_alert_exists (line 42) | def test_alert_exists(self):
    method test_alert_text_exists (line 44) | def test_alert_text_exists(self):
    method test_alert_text_not_exists (line 46) | def test_alert_text_not_exists(self):
    method test_alert_text (line 48) | def test_alert_text(self):
    method test_alert_accept (line 50) | def test_alert_accept(self):
    method test_alert_dismiss (line 58) | def test_alert_dismiss(self):
    method test_click_with_open_alert_raises_exception (line 61) | def test_click_with_open_alert_raises_exception(self):
    method test_press_with_open_alert_raises_exception (line 68) | def test_press_with_open_alert_raises_exception(self):
    method _expect_result (line 81) | def _expect_result(self, expected_result, timeout_secs=1):
    method _get_unhandled_alert_exception_msg (line 89) | def _get_unhandled_alert_exception_msg(self, e):
  class AlertTest (line 98) | class AlertTest(AlertAT, BrowserAT):
    method get_link_to_open_alert (line 99) | def get_link_to_open_alert(self):
    method get_expected_alert_text (line 101) | def get_expected_alert_text(self):
    method get_expected_alert_accepted_result (line 103) | def get_expected_alert_accepted_result(self):
  class ConfirmationDialogTest (line 106) | class ConfirmationDialogTest(AlertAT, BrowserAT):
    method get_link_to_open_alert (line 107) | def get_link_to_open_alert(self):
    method get_expected_alert_text (line 109) | def get_expected_alert_text(self):
    method get_expected_alert_accepted_result (line 111) | def get_expected_alert_accepted_result(self):
    method get_expected_alert_dismissed_result (line 113) | def get_expected_alert_dismissed_result(self):
  class PromptTest (line 116) | class PromptTest(AlertAT, BrowserAT):
    method get_link_to_open_alert (line 117) | def get_link_to_open_alert(self):
    method get_expected_alert_text (line 119) | def get_expected_alert_text(self):
    method get_expected_alert_accepted_result (line 121) | def get_expected_alert_accepted_result(self):
    method test_write_value (line 123) | def test_write_value(self):
    method test_write_into_label_raises_exception (line 127) | def test_write_into_label_raises_exception(self):
    method test_write_into_text_field_raises_exception (line 134) | def test_write_into_text_field_raises_exception(self):
    method test_write_into_non_existent_label_raises_exception (line 141) | def test_write_into_non_existent_label_raises_exception(self):
    method test_write_into_alert (line 148) | def test_write_into_alert(self):
    method test_write_into_labelled_alert (line 152) | def test_write_into_labelled_alert(self):
    method test_write_into_non_existent_alert (line 156) | def test_write_into_non_existent_alert(self):

FILE: tests/api/test_aria.py
  class AriaTest (line 4) | class AriaTest(BrowserAT):
    method get_page (line 5) | def get_page(self):
    method test_aria_label_button_exists (line 7) | def test_aria_label_button_exists(self):
    method test_aria_label_button_is_enabled (line 9) | def test_aria_label_button_is_enabled(self):
    method test_aria_label_disabled_button_is_enabled (line 11) | def test_aria_label_disabled_button_is_enabled(self):
    method test_aria_label_non_existent_button (line 13) | def test_aria_label_non_existent_button(self):
    method test_aria_label_div_button_exists (line 15) | def test_aria_label_div_button_exists(self):
    method test_aria_label_div_button_is_enabled (line 17) | def test_aria_label_div_button_is_enabled(self):
    method test_aria_label_div_disabled_button_is_enabled (line 19) | def test_aria_label_div_disabled_button_is_enabled(self):
    method test_aria_label_submit_button_exists (line 21) | def test_aria_label_submit_button_exists(self):
    method test_aria_textbox_exists (line 23) | def test_aria_textbox_exists(self):
    method test_aria_textbox_value (line 25) | def test_aria_textbox_value(self):

FILE: tests/api/test_chrome_options.py
  class ChromeOptionsTest (line 11) | class ChromeOptionsTest(TestCase):
    method test_start_chrome_does_not_override_custom_prefs (line 12) | def test_start_chrome_does_not_override_custom_prefs(self):
    method test_start_chrome_respects_custom_password_leak_detection (line 20) | def test_start_chrome_respects_custom_password_leak_detection(self):
  function chrome_with_prefs (line 28) | def chrome_with_prefs(prefs):

FILE: tests/api/test_click.py
  class ClickTest (line 5) | class ClickTest(BrowserAT):
    method get_page (line 6) | def get_page(self):
    method test_click (line 8) | def test_click(self):
    method test_click_non_existent_element (line 11) | def test_click_non_existent_element(self):

FILE: tests/api/test_doubleclick.py
  class DoubleclickTest (line 4) | class DoubleclickTest(BrowserAT):
    method get_page (line 5) | def get_page(self):
    method test_double_click (line 7) | def test_double_click(self):

FILE: tests/api/test_drag.py
  class DragTest (line 5) | class DragTest(BrowserAT):
    method setUp (line 6) | def setUp(self):
    method get_page (line 9) | def get_page(self):
    method test_drag (line 11) | def test_drag(self):
    method test_drag_to_point (line 14) | def test_drag_to_point(self):
  class Html5DragIT (line 25) | class Html5DragIT(BrowserAT):
    method get_page (line 26) | def get_page(self):
    method test_html5_drag (line 28) | def test_html5_drag(self):

FILE: tests/api/test_file_upload.py
  class FileUploadTest (line 5) | class FileUploadTest(BrowserAT):
    method get_page (line 6) | def get_page(self):
    method setUp (line 8) | def setUp(self):
    method test_normal_file_upload_is_not_text_field (line 13) | def test_normal_file_upload_is_not_text_field(self):
    method test_attach_file_to_normal_file_upload (line 15) | def test_attach_file_to_normal_file_upload(self):
    method test_attach_file_no_to (line 18) | def test_attach_file_no_to(self):
    method test_attach_file_to_point (line 21) | def test_attach_file_to_point(self):
    method test_drag_file_to_appearing_drop_area (line 27) | def test_drag_file_to_appearing_drop_area(self):

FILE: tests/api/test_find_all.py
  class FindAllTest (line 5) | class FindAllTest(BrowserAT):
    method get_page (line 6) | def get_page(self):
    method test_find_all_duplicate_button (line 8) | def test_find_all_duplicate_button(self):
    method test_find_all_duplicate_button_to_right_of (line 10) | def test_find_all_duplicate_button_to_right_of(self):
    method test_find_all_duplicate_button_below_to_right_of (line 14) | def test_find_all_duplicate_button_below_to_right_of(self):
    method test_find_all_nested_search_areas (line 20) | def test_find_all_nested_search_areas(self):
    method test_find_all_non_existent_button (line 29) | def test_find_all_non_existent_button(self):
    method test_find_all_yields_api_elements (line 31) | def test_find_all_yields_api_elements(self):
    method test_interact_with_found_elements (line 35) | def test_interact_with_found_elements(self):
    method test_bound_element_as_spatial_constraint (line 55) | def test_bound_element_as_spatial_constraint(self):
    method test_very_nested_search_areas (line 65) | def test_very_nested_search_areas(self):

FILE: tests/api/test_gui_elements.py
  class GUIElementsTest (line 6) | class GUIElementsTest(BrowserAT):
    method get_page (line 7) | def get_page(self):
    method setUpClass (line 10) | def setUpClass(cls):
    method tearDownClass (line 16) | def tearDownClass(cls):
    method test_button_exists (line 21) | def test_button_exists(self):
    method test_submit_button_exists (line 23) | def test_submit_button_exists(self):
    method test_submit_button_exists_lower_case (line 25) | def test_submit_button_exists_lower_case(self):
    method test_input_button_exists (line 27) | def test_input_button_exists(self):
    method test_button_not_exists (line 29) | def test_button_not_exists(self):
    method test_text_field_does_not_exist_as_button (line 31) | def test_text_field_does_not_exist_as_button(self):
    method test_enabled_button (line 33) | def test_enabled_button(self):
    method test_disabled_button (line 35) | def test_disabled_button(self):
    method test_button_no_text (line 37) | def test_button_no_text(self):
    method test_div_button_exists (line 39) | def test_div_button_exists(self):
    method test_button_tag_button_exists (line 41) | def test_button_tag_button_exists(self):
    method test_submit_button_can_be_found_by_title (line 43) | def test_submit_button_can_be_found_by_title(self):
    method test_text_field_exists (line 47) | def test_text_field_exists(self):
    method test_text_field_lower_case_exists (line 49) | def test_text_field_lower_case_exists(self):
    method test_text_field_in_second_col_exists (line 51) | def test_text_field_in_second_col_exists(self):
    method test_text_field_not_exists (line 53) | def test_text_field_not_exists(self):
    method test_text_field_is_editable_false (line 55) | def test_text_field_is_editable_false(self):
    method test_text_field_is_editable (line 57) | def test_text_field_is_editable(self):
    method test_text_field_is_enabled (line 59) | def test_text_field_is_enabled(self):
    method test_text_field_is_enabled_false (line 61) | def test_text_field_is_enabled_false(self):
    method test_text_field_value (line 63) | def test_text_field_value(self):
    method test_text_field_with_placeholder_exists (line 65) | def test_text_field_with_placeholder_exists(self):
    method test_text_field_no_type_specified_with_placeholder_exists (line 67) | def test_text_field_no_type_specified_with_placeholder_exists(self):
    method test_empty_text_field_value (line 71) | def test_empty_text_field_value(self):
    method test_read_readonly_text_field (line 73) | def test_read_readonly_text_field(self):
    method test_read_disabled_text_field (line 77) | def test_read_disabled_text_field(self):
    method test_read_german_text_field (line 81) | def test_read_german_text_field(self):
    method test_text_field_input_type_upper_case_text (line 85) | def test_text_field_input_type_upper_case_text(self):
    method test_write_into_labelled_text_field (line 87) | def test_write_into_labelled_text_field(self):
    method test_required_text_field_marked_with_asterisk_exists (line 90) | def test_required_text_field_marked_with_asterisk_exists(self):
    method test_text_field_labelled_by_free_text (line 92) | def test_text_field_labelled_by_free_text(self):
    method test_input_type_tel (line 97) | def test_input_type_tel(self):
    method test_input_type_date (line 99) | def test_input_type_date(self):
    method test_input_type_time (line 101) | def test_input_type_time(self):
    method test_text_field_to_right_of_text_field (line 103) | def test_text_field_to_right_of_text_field(self):
    method test_contenteditable_paragrapth (line 108) | def test_contenteditable_paragrapth(self):
    method test_combo_box_exists (line 114) | def test_combo_box_exists(self):
    method test_combo_box_exists_lower_case (line 116) | def test_combo_box_exists_lower_case(self):
    method test_drop_down_list_is_editable_false (line 118) | def test_drop_down_list_is_editable_false(self):
    method test_editable_combo_box_is_editable (line 120) | def test_editable_combo_box_is_editable(self):
    method test_combo_box_options (line 122) | def test_combo_box_options(self):
    method test_reads_value_of_combo_box (line 127) | def test_reads_value_of_combo_box(self):
    method test_select_value_from_combo_box (line 129) | def test_select_value_from_combo_box(self):
    method test_combo_box_identified_by_value (line 135) | def test_combo_box_identified_by_value(self):
    method test_combo_box_preceded_by_combo_with_name_as_label (line 143) | def test_combo_box_preceded_by_combo_with_name_as_label(self):
    method test_check_box_exists (line 149) | def test_check_box_exists(self):
    method test_check_box_exists_lower_case (line 151) | def test_check_box_exists_lower_case(self):
    method test_left_hand_side_check_box_exists (line 153) | def test_left_hand_side_check_box_exists(self):
    method test_check_box_not_exists (line 155) | def test_check_box_not_exists(self):
    method test_text_field_does_not_exist_as_check_box (line 157) | def test_text_field_does_not_exist_as_check_box(self):
    method test_ticked_check_box_exists (line 159) | def test_ticked_check_box_exists(self):
    method test_ticked_check_box_is_enabled (line 161) | def test_ticked_check_box_is_enabled(self):
    method test_right_labelled_check_box_exists (line 163) | def test_right_labelled_check_box_exists(self):
    method test_left_labelled_check_box_exists (line 165) | def test_left_labelled_check_box_exists(self):
    method test_disabled_check_box_exists (line 167) | def test_disabled_check_box_exists(self):
    method test_ticked_check_box_is_checked (line 169) | def test_ticked_check_box_is_checked(self):
    method test_right_labelled_check_box_is_not_checked (line 171) | def test_right_labelled_check_box_is_not_checked(self):
    method test_left_labelled_check_box_is_not_checked (line 173) | def test_left_labelled_check_box_is_not_checked(self):
    method test_disabled_check_box_is_not_checked (line 175) | def test_disabled_check_box_is_not_checked(self):
    method test_untick_check_box (line 177) | def test_untick_check_box(self):
    method test_disabled_check_box_is_not_enabled (line 181) | def test_disabled_check_box_is_not_enabled(self):
    method test_check_box_enclosed_by_label (line 183) | def test_check_box_enclosed_by_label(self):
    method test_checkboxes_labelled_by_free_text (line 187) | def test_checkboxes_labelled_by_free_text(self):
    method test_first_radio_button_exists (line 194) | def test_first_radio_button_exists(self):
    method test_first_radio_button_exists_lower_case (line 196) | def test_first_radio_button_exists_lower_case(self):
    method test_second_radio_button_exists (line 198) | def test_second_radio_button_exists(self):
    method test_left_labelled_radio_button_one_exists (line 200) | def test_left_labelled_radio_button_one_exists(self):
    method test_left_labelled_radio_button_two_exists (line 202) | def test_left_labelled_radio_button_two_exists(self):
    method test_first_radio_button_is_selected (line 204) | def test_first_radio_button_is_selected(self):
    method test_second_radio_button_is_not_selected (line 206) | def test_second_radio_button_is_not_selected(self):
    method test_select_second_radio_button (line 208) | def test_select_second_radio_button(self):
    method test_radio_button_not_exists (line 212) | def test_radio_button_not_exists(self):
    method test_text_field_is_not_a_radio_button (line 214) | def test_text_field_is_not_a_radio_button(self):
    method test_radiobuttons_labelled_by_free_text (line 216) | def test_radiobuttons_labelled_by_free_text(self):
    method test_text_exists_submit_button (line 223) | def test_text_exists_submit_button(self):
    method test_text_exists_submit_button_lower_case (line 225) | def test_text_exists_submit_button_lower_case(self):
    method test_text_exists_link_with_title (line 227) | def test_text_exists_link_with_title(self):
    method test_text_exists_link_with_title_lower_case (line 229) | def test_text_exists_link_with_title_lower_case(self):
    method test_text_with_leading_nbsp_exists (line 231) | def test_text_with_leading_nbsp_exists(self):
    method test_read_text_value (line 233) | def test_read_text_value(self):
    method test_free_text_not_surrounded_by_tags_exists (line 235) | def test_free_text_not_surrounded_by_tags_exists(self):
    method test_text_with_apostrophe (line 237) | def test_text_with_apostrophe(self):
    method test_text_with_double_quotes (line 239) | def test_text_with_double_quotes(self):
    method test_text_with_single_and_double_quotes (line 241) | def test_text_with_single_and_double_quotes(self):
    method test_text_uppercase_umlaut (line 243) | def test_text_uppercase_umlaut(self):
    method test_link_exists (line 247) | def test_link_exists(self):
    method test_link_with_title_exists (line 249) | def test_link_with_title_exists(self):
    method test_link_no_text (line 251) | def test_link_no_text(self):
    method test_span_with_role_link_exists_as_link (line 253) | def test_span_with_role_link_exists_as_link(self):
    method test_link_href (line 255) | def test_link_href(self):
    method test_link_empty_href (line 257) | def test_link_empty_href(self):
    method test_list_item_no_text (line 261) | def test_list_item_no_text(self):
    method test_image_not_exists (line 267) | def test_image_not_exists(self):
    method test_image_exists (line 269) | def test_image_exists(self):
    method test_text_field_combo_box_with_same_name (line 273) | def test_text_field_combo_box_with_same_name(self):

FILE: tests/api/test_highlight.py
  class HighlightTest (line 5) | class HighlightTest(BrowserAT):
    method get_page (line 6) | def get_page(self):
    method test_highlight (line 8) | def test_highlight(self):
    method test_highlight_string (line 12) | def test_highlight_string(self):
    method test_highlight_nonexistent (line 15) | def test_highlight_nonexistent(self):
    method _check_is_highlighted (line 19) | def _check_is_highlighted(self, html_element):

FILE: tests/api/test_hover.py
  class HoverTest (line 6) | class HoverTest(BrowserAT):
    method get_page (line 7) | def get_page(self):
    method setUp (line 9) | def setUp(self):
    method _move_mouse_cursor_to_origin (line 15) | def _move_mouse_cursor_to_origin(self):
    method test_hover_one (line 20) | def test_hover_one(self):
    method test_hover_two_consecutively (line 28) | def test_hover_two_consecutively(self):
    method test_hover_hidden (line 37) | def test_hover_hidden(self):

FILE: tests/api/test_iframe.py
  class IframeTest (line 4) | class IframeTest(BrowserAT):
    method get_page (line 5) | def get_page(self):
    method test_test_text_in_iframe_exists (line 7) | def test_test_text_in_iframe_exists(self):
    method test_text_in_nested_iframe_exists (line 9) | def test_text_in_nested_iframe_exists(self):
    method test_finds_element_in_parent_iframe (line 11) | def test_finds_element_in_parent_iframe(self):
    method test_access_attributes_across_iframes (line 16) | def test_access_attributes_across_iframes(self):
    method test_repr (line 21) | def test_repr(self):

FILE: tests/api/test_implicit_wait.py
  class ImplicitWaitTest (line 6) | class ImplicitWaitTest(BrowserAT):
    method get_page (line 7) | def get_page(self):
    method test_click_text_implicit_wait (line 9) | def test_click_text_implicit_wait(self):
    method test_click_text_no_implicit_wait (line 16) | def test_click_text_no_implicit_wait(self):
    method test_click_text_too_small_implicit_wait_secs (line 20) | def test_click_text_too_small_implicit_wait_secs(self):

FILE: tests/api/test_kill_service_at_exit.py
  class KillServiceAtExitAT (line 5) | class KillServiceAtExitAT:
    method test_kill_service_at_exit (line 6) | def test_kill_service_at_exit(self):
    method start_browser_in_sub_process (line 9) | def start_browser_in_sub_process(self):
    method get_new_running_services (line 11) | def get_new_running_services(self):
    method setUp (line 14) | def setUp(self):
    method tearDown (line 17) | def tearDown(self):
    method get_new_running_browsers (line 30) | def get_new_running_browsers(self):
    method get_running_services (line 33) | def get_running_services(self):
    method get_running_browsers (line 35) | def get_running_browsers(self):
    method _get_running_processes (line 37) | def _get_running_processes(self, image_names):
    method get_service_process_names (line 43) | def get_service_process_names(self):
    method get_browser_process_name (line 45) | def get_browser_process_name(self):
    method start_browser (line 47) | def start_browser(self):

FILE: tests/api/test_kill_service_at_exit_chrome.py
  class KillServiceAtExitChromeTest (line 9) | class KillServiceAtExitChromeTest(KillServiceAtExitAT, TestCase):
    method get_service_process_names (line 10) | def get_service_process_names(self):
    method get_browser_process_name (line 14) | def get_browser_process_name(self):
    method start_browser_in_sub_process (line 16) | def start_browser_in_sub_process(self):
  class ChromeInSubProcess (line 20) | class ChromeInSubProcess(InSubProcess):
    method main (line 22) | def main(cls):

FILE: tests/api/test_leaked_password.py
  class LeakedPasswordTest (line 4) | class LeakedPasswordTest(BrowserAT):
    method get_page (line 5) | def get_page(self):
    method test_submit_leaked_password (line 7) | def test_submit_leaked_password(self):

FILE: tests/api/test_no_driver.py
  class NoDriverTest (line 5) | class NoDriverTest(TestCase):
    method test_go_to_requires_driver (line 6) | def test_go_to_requires_driver(self):
    method test_write_requires_driver (line 8) | def test_write_requires_driver(self):
    method test_press_requires_driver (line 10) | def test_press_requires_driver(self):
    method test_click_requires_driver (line 12) | def test_click_requires_driver(self):
    method test_doubleclick_requires_driver (line 14) | def test_doubleclick_requires_driver(self):
    method test_drag_requires_driver (line 16) | def test_drag_requires_driver(self):
    method test_find_all_requires_driver (line 18) | def test_find_all_requires_driver(self):
    method test_scroll_down_requires_driver (line 20) | def test_scroll_down_requires_driver(self):
    method test_scroll_up_requires_driver (line 22) | def test_scroll_up_requires_driver(self):
    method test_scroll_right_requires_driver (line 24) | def test_scroll_right_requires_driver(self):
    method test_scroll_left_requires_driver (line 26) | def test_scroll_left_requires_driver(self):
    method test_hover_requires_driver (line 28) | def test_hover_requires_driver(self):
    method test_rightclick_requires_driver (line 30) | def test_rightclick_requires_driver(self):
    method test_select_requires_driver (line 32) | def test_select_requires_driver(self):
    method test_drag_file_requires_driver (line 34) | def test_drag_file_requires_driver(self):
    method test_attach_file_requires_driver (line 38) | def test_attach_file_requires_driver(self):
    method test_refresh_requires_driver (line 40) | def test_refresh_requires_driver(self):
    method test_wait_until_requires_driver (line 42) | def test_wait_until_requires_driver(self):
    method test_switch_to_requires_driver (line 44) | def test_switch_to_requires_driver(self):
    method test_kill_browser_requires_driver (line 46) | def test_kill_browser_requires_driver(self):
    method test_highlight_requires_driver (line 48) | def test_highlight_requires_driver(self):
    method test_s_requires_driver (line 50) | def test_s_requires_driver(self):
    method test_text_requires_driver (line 52) | def test_text_requires_driver(self):
    method test_link_requires_driver (line 54) | def test_link_requires_driver(self):
    method test_list_item_requires_driver (line 56) | def test_list_item_requires_driver(self):
    method test_button_requires_driver (line 58) | def test_button_requires_driver(self):
    method test_image_requires_driver (line 60) | def test_image_requires_driver(self):
    method test_text_field_requires_driver (line 62) | def test_text_field_requires_driver(self):
    method test_combo_box_requires_driver (line 64) | def test_combo_box_requires_driver(self):
    method test_check_box_requires_driver (line 66) | def test_check_box_requires_driver(self):
    method test_radio_button_requires_driver (line 68) | def test_radio_button_requires_driver(self):
    method test_window_requires_driver (line 70) | def test_window_requires_driver(self):
    method test_alert_requires_driver (line 72) | def test_alert_requires_driver(self):
    method _check_requires_driver (line 74) | def _check_requires_driver(self, function):

FILE: tests/api/test_point.py
  class PointTest (line 5) | class PointTest(BrowserAT):
    method get_page (line 14) | def get_page(self):
    method setUp (line 16) | def setUp(self):
    method test_top_left (line 27) | def test_top_left(self):
    method assert_is_in_range (line 31) | def assert_is_in_range(self, expected, point, delta):
    method assert_around (line 37) | def assert_around(self, expected, actual, delta, msg=None):
    method test_click_top_left (line 41) | def test_click_top_left(self):
    method test_click_point (line 46) | def test_click_point(self):
    method test_click_top_left_offset (line 51) | def test_click_top_left_offset(self):
    method test_hover_top_left (line 54) | def test_hover_top_left(self):
    method test_hover_point (line 59) | def test_hover_point(self):
    method test_hover_top_left_offset (line 64) | def test_hover_top_left_offset(self):
    method test_rightclick_top_left (line 67) | def test_rightclick_top_left(self):
    method test_rightclick_point (line 72) | def test_rightclick_point(self):
    method test_rightclick_top_left_offset (line 77) | def test_rightclick_top_left_offset(self):
    method test_doubleclick_top_left (line 82) | def test_doubleclick_top_left(self):
    method test_doubleclick_point (line 87) | def test_doubleclick_point(self):
    method test_doubleclick_top_left_offset (line 92) | def test_doubleclick_top_left_offset(self):
    method test_drag_point (line 95) | def test_drag_point(self):
    method assert_result_is (line 100) | def assert_result_is(self, expected, offset_delta=(0, 0)):
    method _extract_offset (line 123) | def _extract_offset(self, result_in_browser):

FILE: tests/api/test_press.py
  class PressTest (line 4) | class PressTest(BrowserAT):
    method get_page (line 5) | def get_page(self):
    method test_press_single_character (line 7) | def test_press_single_character(self):
    method test_press_upper_case_character (line 10) | def test_press_upper_case_character(self):
    method test_press_shift_plus_lower_case_character (line 13) | def test_press_shift_plus_lower_case_character(self):

FILE: tests/api/test_repr.py
  class UnboundReprTest (line 7) | class UnboundReprTest(BrowserAT):
    method get_page (line 8) | def get_page(self):
    method test_unbound_s_repr (line 10) | def test_unbound_s_repr(self):
    method test_unbound_s_repr_below (line 14) | def test_unbound_s_repr_below(self):
    method test_unbound_text_repr (line 18) | def test_unbound_text_repr(self):
    method test_unbound_link_repr (line 22) | def test_unbound_link_repr(self):
    method test_unbound_list_item_repr (line 26) | def test_unbound_list_item_repr(self):
    method test_unbound_button_repr (line 30) | def test_unbound_button_repr(self):
    method test_unbound_image_repr (line 34) | def test_unbound_image_repr(self):
    method test_unbound_text_field_repr (line 38) | def test_unbound_text_field_repr(self):
    method test_unbound_combo_box_repr (line 42) | def test_unbound_combo_box_repr(self):
    method test_unbound_check_box_repr (line 46) | def test_unbound_check_box_repr(self):
    method test_unbound_radio_button_repr (line 50) | def test_unbound_radio_button_repr(self):
    method test_unbound_window_repr (line 54) | def test_unbound_window_repr(self):
    method test_unbound_alert_repr (line 58) | def test_unbound_alert_repr(self):
    method test_unbound_alert_repr_with_search_text (line 62) | def test_unbound_alert_repr_with_search_text(self):
  class BoundReprTest (line 67) | class BoundReprTest(BrowserAT):
    method get_page (line 68) | def get_page(self):
    method test_bound_s_repr (line 70) | def test_bound_s_repr(self):
    method test_bound_s_repr_long_content (line 77) | def test_bound_s_repr_long_content(self):
    method test_bound_button_repr (line 80) | def test_bound_button_repr(self):
    method test_bound_link_repr_nested_tag (line 85) | def test_bound_link_repr_nested_tag(self):
    method test_bound_repr_duplicate_button (line 90) | def test_bound_repr_duplicate_button(self):
    method test_bound_window_repr (line 98) | def test_bound_window_repr(self):
    method test_bound_window_repr_with_search_text (line 103) | def test_bound_window_repr_with_search_text(self):
    method _bind (line 108) | def _bind(self, predicate):
    method _assertHtmlEltWithMultipleAttributesEquals (line 119) | def _assertHtmlEltWithMultipleAttributesEquals(self, expected, actual):
  class BoundAlertReprTest (line 128) | class BoundAlertReprTest(BrowserAT):
    method get_page (line 129) | def get_page(self):
    method setUp (line 131) | def setUp(self):
    method test_bound_alert_repr (line 134) | def test_bound_alert_repr(self):
    method test_bound_alert_repr_with_partial_search_text (line 139) | def test_bound_alert_repr_with_partial_search_text(self):
    method tearDown (line 144) | def tearDown(self):

FILE: tests/api/test_rightclick.py
  class RightclickTest (line 4) | class RightclickTest(BrowserAT):
    method get_page (line 5) | def get_page(self):
    method test_simple_rightclick (line 7) | def test_simple_rightclick(self):
    method test_rightclick_select_normal_item (line 12) | def test_rightclick_select_normal_item(self):

FILE: tests/api/test_s.py
  class STest (line 4) | class STest(BrowserAT):
    method get_page (line 5) | def get_page(self):
    method test_find_by_id (line 7) | def test_find_by_id(self):
    method test_find_by_name (line 9) | def test_find_by_name(self):
    method test_find_by_class (line 11) | def test_find_by_class(self):
    method test_find_by_xpath (line 13) | def test_find_by_xpath(self):
    method test_find_by_css_selector (line 17) | def test_find_by_css_selector(self):

FILE: tests/api/test_scroll.py
  class ScrollTest (line 4) | class ScrollTest(BrowserAT):
    method get_page (line 5) | def get_page(self):
    method test_scroll_up_when_at_top_of_page (line 7) | def test_scroll_up_when_at_top_of_page(self):
    method test_scroll_down (line 10) | def test_scroll_down(self):
    method test_scroll_down_then_up (line 13) | def test_scroll_down_then_up(self):
    method test_scroll_down_then_up_pixels (line 17) | def test_scroll_down_then_up_pixels(self):
    method test_scroll_left_when_at_start_of_page (line 21) | def test_scroll_left_when_at_start_of_page(self):
    method test_scroll_right (line 24) | def test_scroll_right(self):
    method test_scroll_right_then_left (line 27) | def test_scroll_right_then_left(self):
    method test_scroll_right_then_left_pixels (line 31) | def test_scroll_right_then_left_pixels(self):
    method tearDown (line 35) | def tearDown(self):
    method assert_scroll_position_equals (line 40) | def assert_scroll_position_equals(self, x, y):

FILE: tests/api/test_start_go_to.py
  class StartGoToTest (line 7) | class StartGoToTest(TestCase):
    method setUp (line 8) | def setUp(self):
    method test_go_to (line 11) | def test_go_to(self):
    method assertUrlEquals (line 15) | def assertUrlEquals(self, expected, actual):
    method test_start_with_url (line 19) | def test_start_with_url(self):
    method tearDown (line 22) | def tearDown(self):

FILE: tests/api/test_tables.py
  class TablesTest (line 4) | class TablesTest(BrowserAT):
    method get_page (line 5) | def get_page(self):
    method test_s_below_above (line 7) | def test_s_below_above(self):
    method test_s_read_table_column (line 21) | def test_s_read_table_column(self):
    method test_text_below_to_left_of (line 28) | def test_text_below_to_left_of(self):

FILE: tests/api/test_text_impl.py
  class TextImplTest (line 6) | class TextImplTest(BrowserAT):
    method get_page (line 7) | def get_page(self):
    method test_empty_search_text_xpath (line 9) | def test_empty_search_text_xpath(self):

FILE: tests/api/test_wait_until.py
  class WaitUntilTest (line 9) | class WaitUntilTest(BrowserAT):
    method get_page (line 10) | def get_page(self):
    method test_wait_until_text_exists (line 12) | def test_wait_until_text_exists(self):
    method test_wait_until_presence_of_element_located (line 18) | def test_wait_until_presence_of_element_located(self):
    method test_wait_until_lambda_expires (line 24) | def test_wait_until_lambda_expires(self):
    method test_wait_until_lambda_with_driver_expires (line 27) | def test_wait_until_lambda_with_driver_expires(self):

FILE: tests/api/test_window.py
  class WindowTest (line 5) | class WindowTest(BrowserAT):
    method get_page (line 6) | def get_page(self):
    method test_window_exists (line 8) | def test_window_exists(self):
    method test_window_not_exists (line 10) | def test_window_not_exists(self):
    method test_no_arg_window_exists (line 12) | def test_no_arg_window_exists(self):
    method test_handle (line 14) | def test_handle(self):
    method test_title (line 16) | def test_title(self):
  class MultipleWindowTest (line 19) | class MultipleWindowTest(WindowTest):
    method setUpClass (line 25) | def setUpClass(cls):
    method test_popup_window_exists (line 30) | def test_popup_window_exists(self):
    method setUp (line 32) | def setUp(self):
    method tearDownClass (line 36) | def tearDownClass(cls):

FILE: tests/api/test_window_handling.py
  class WindowHandlingTest (line 7) | class WindowHandlingTest(BrowserAT):
    method get_page (line 8) | def get_page(self):
    method test_write_writes_in_active_window (line 10) | def test_write_writes_in_active_window(self):
    method test_write_searches_in_active_window (line 16) | def test_write_searches_in_active_window(self):
    method test_switch_to_search_text_field (line 22) | def test_switch_to_search_text_field(self):
    method test_handles_closed_window_gracefully (line 30) | def test_handles_closed_window_gracefully(self):
    method test_switch_to_after_window_closed (line 35) | def test_switch_to_after_window_closed(self):
    method setUp (line 39) | def setUp(self):
    method tearDown (line 42) | def tearDown(self):
    method _get_value (line 49) | def _get_value(self, element_id):
    method _open_popup (line 51) | def _open_popup(self):
    method _is_in_popup (line 54) | def _is_in_popup(self):
  class WindowHandlingOnStartBrowserTest (line 57) | class WindowHandlingOnStartBrowserTest(BrowserAT):
    method get_page (line 58) | def get_page(self):
    method test_switches_to_popup (line 61) | def test_switches_to_popup(self):

FILE: tests/api/test_write.py
  class WriteTest (line 4) | class WriteTest(BrowserAT):
    method get_page (line 5) | def get_page(self):
    method test_write (line 7) | def test_write(self):
    method test_write_into (line 12) | def test_write_into(self):
    method test_write_into_text_field_to_right_of (line 17) | def test_write_into_text_field_to_right_of(self):
    method test_write_into_input_type_date (line 22) | def test_write_into_input_type_date(self):
    method test_write_into_input_type_time (line 26) | def test_write_into_input_type_time(self):

FILE: tests/api/util.py
  function get_data_file (line 8) | def get_data_file(*rel_path):
  function get_data_file_url (line 11) | def get_data_file_url(data_file):
  class InSubProcess (line 14) | class InSubProcess:
    method __init__ (line 19) | def __init__(self):
    method __enter__ (line 21) | def __enter__(self):
    method wait_for_sub_process (line 29) | def wait_for_sub_process(self):
    method synchronize_with_parent_process (line 35) | def synchronize_with_parent_process(cls):
    method __exit__ (line 41) | def __exit__(self, *args):

FILE: tests/unit/test__impl/test_selenium_wrappers.py
  class FrameIteratorTest (line 6) | class FrameIteratorTest(TestCase):
    method test_only_main_frame (line 7) | def test_only_main_frame(self):
    method test_one_frame (line 9) | def test_one_frame(self):
    method test_two_frames (line 12) | def test_two_frames(self):
    method test_nested_frame (line 15) | def test_nested_frame(self):
    method test_complex (line 18) | def test_complex(self):
    method test_disappearing_frame (line 21) | def test_disappearing_frame(self):
  class StubWebDriver (line 35) | class StubWebDriver:
    method __init__ (line 36) | def __init__(self, *frames):
  class StubTargetLocator (line 41) | class StubTargetLocator:
    method __init__ (line 42) | def __init__(self, driver):
    method default_content (line 44) | def default_content(self):
    method frame (line 46) | def frame(self, index):
  class Frame (line 58) | class Frame:
    method __init__ (line 59) | def __init__(self, *children):
  class TargetLocatorFailingAfterNFrameSwitches (line 62) | class TargetLocatorFailingAfterNFrameSwitches(StubTargetLocator):
    method __init__ (line 63) | def __init__(self, driver, num_allowed_frame_switches):
    method frame (line 66) | def frame(self, index):

FILE: tests/unit/test__impl/test_util/test_dictionary.py
  class InverseTest (line 4) | class InverseTest(TestCase):
    method test_inverse_empty (line 5) | def test_inverse_empty(self):
    method test_inverse (line 7) | def test_inverse(self):

FILE: tests/unit/test__impl/test_util/test_html.py
  class GetEasilyReadableSnippetTest (line 5) | class GetEasilyReadableSnippetTest(TestCase):
    method test_no_tag (line 6) | def test_no_tag(self):
    method test_completely_empty_tag (line 10) | def test_completely_empty_tag(self):
    method test_empty_tag_with_attributes (line 12) | def test_empty_tag_with_attributes(self):
    method test_tag_with_nested_tags (line 20) | def test_tag_with_nested_tags(self):
    method test_tag_with_long_content (line 25) | def test_tag_with_long_content(self):
  class NormalizeWhitespaceTest (line 32) | class NormalizeWhitespaceTest(TestCase):
    method test_string_without_whitespace (line 33) | def test_string_without_whitespace(self):
    method test_string_one_whitespace (line 35) | def test_string_one_whitespace(self):
    method test_string_leading_whitespace (line 37) | def test_string_leading_whitespace(self):
    method test_string_complex_whitespace (line 39) | def test_string_complex_whitespace(self):
    method test_tag_with_spaces_around_inner_html (line 43) | def test_tag_with_spaces_around_inner_html(self):

FILE: tests/unit/test__impl/test_util/test_xpath.py
  class PredicateOrTest (line 4) | class PredicateOrTest(TestCase):
    method test_no_args (line 5) | def test_no_args(self):
    method test_one_arg (line 7) | def test_one_arg(self):
    method test_two_args (line 9) | def test_two_args(self):
    method test_one_empty_arg (line 11) | def test_one_empty_arg(self):
    method test_empty_arg_among_normal_args (line 13) | def test_empty_arg_among_normal_args(self):
Condensed preview — 104 files, each showing path, character count, and a content snippet. Download the .json file or copy for the full structured content (293K chars).
[
  {
    "path": ".gitattributes",
    "chars": 28,
    "preview": "* text=auto\n*.sh text eol=lf"
  },
  {
    "path": ".gitignore",
    "chars": 619,
    "preview": "# Byte-compiled / optimized / DLL files\n__pycache__/\n*.py[cod]\n*$py.class\n\n# C extensions\n*.so\n\n# Distribution / packagi"
  },
  {
    "path": ".readthedocs.yaml",
    "chars": 169,
    "preview": "version: 2\n\npython:\n   install:\n     - requirements: requirements/docs.txt\n\nbuild:\n  os: ubuntu-22.04\n  tools:\n    pytho"
  },
  {
    "path": "LICENSE.txt",
    "chars": 1134,
    "preview": "MIT License\n\nCopyright (c) 2020 Michael Herrmann\nCopyright (c) 2013 - 2019 Michael Herrmann & Tytus Dobrzynski\n\nPermissi"
  },
  {
    "path": "NOTICE.txt",
    "chars": 30695,
    "preview": "LICENSES OF THIRD-PARTY LIBRARIES\n=================================\n\nHelium is based on several free and open source sof"
  },
  {
    "path": "README.md",
    "chars": 5901,
    "preview": "# Lighter web automation with Python\n\nHelium is a Python library for automating browsers such as Chrome and Firefox.\nFor"
  },
  {
    "path": "docs/Makefile",
    "chars": 634,
    "preview": "# Minimal makefile for Sphinx documentation\n#\n\n# You can set these variables from the command line, and also\n# from the "
  },
  {
    "path": "docs/README.md",
    "chars": 650,
    "preview": "# Welcome to Helium's documentation\n\nThe documentation is built using\n[sphinx](https://www.sphinx-doc.org/en/master/inde"
  },
  {
    "path": "docs/api.rst",
    "chars": 46,
    "preview": "API\n===\n\n.. automodule:: helium\n    :members:\n"
  },
  {
    "path": "docs/cheatsheet.md",
    "chars": 8871,
    "preview": "# Helium cheatsheet\n\nThis page very quickly teaches you the most important parts of Helium's API.\n\n## Importing\n\nAll of "
  },
  {
    "path": "docs/conf.py",
    "chars": 1075,
    "preview": "import os\nimport sys\nfrom datetime import date\n\nsys.path.insert(0, os.path.abspath('..'))\n\n\n# -- Project information ---"
  },
  {
    "path": "docs/contributors.rst",
    "chars": 436,
    "preview": "Contributors to this project\n============================\n..\n    Please use this format to add your contributions to thi"
  },
  {
    "path": "docs/index.rst",
    "chars": 746,
    "preview": "Welcome to Helium's documentation!\n==================================\n\nHelium is a Python library for automating web sit"
  },
  {
    "path": "docs/installation.rst",
    "chars": 900,
    "preview": "Installation\n============\n\nTo install Helium, you need Python 3 and Chrome or Firefox.\n\nIf you already know Python, then"
  },
  {
    "path": "docs/make.bat",
    "chars": 760,
    "preview": "@ECHO OFF\n\npushd %~dp0\n\nREM Command file for Sphinx documentation\n\nif \"%SPHINXBUILD%\" == \"\" (\n\tset SPHINXBUILD=sphinx-bu"
  },
  {
    "path": "helium/__init__.py",
    "chars": 36034,
    "preview": "\"\"\"\nHelium's API is contained in module ``helium``. It is a simple Python API that\nmakes specifying web automation cases"
  },
  {
    "path": "helium/_impl/__init__.py",
    "chars": 44994,
    "preview": "from copy import copy\nfrom helium._impl.match_type import PREFIX_IGNORE_CASE\nfrom helium._impl.selenium_wrappers import "
  },
  {
    "path": "helium/_impl/match_type.py",
    "chars": 1140,
    "preview": "from helium._impl.util.xpath import lower, replace_nbsp\n\nclass MatchType:\n\tdef xpath(self, value, text):\n\t\traise NotImpl"
  },
  {
    "path": "helium/_impl/selenium_wrappers.py",
    "chars": 4928,
    "preview": "from helium._impl.util.geom import Rectangle\nfrom selenium.common.exceptions import StaleElementReferenceException, \\\n\tN"
  },
  {
    "path": "helium/_impl/util/__init__.py",
    "chars": 0,
    "preview": ""
  },
  {
    "path": "helium/_impl/util/dictionary.py",
    "chars": 226,
    "preview": "def inverse(dictionary):\n\t\"\"\"\n\t{a: {b}} -> {b: {a}}\n\t\"\"\"\n\tresult = {}\n\tfor key, values in dictionary.items():\n\t\tfor valu"
  },
  {
    "path": "helium/_impl/util/geom.py",
    "chars": 6355,
    "preview": "from collections import namedtuple\nfrom math import sqrt\n\nclass Rectangle:\n\tdef __init__(self, left=0, top=0, width=0, h"
  },
  {
    "path": "helium/_impl/util/html.py",
    "chars": 979,
    "preview": "from html.parser import HTMLParser\nimport re\n\ndef strip_tags(html):\n\ts = TagStripper()\n\ts.feed(html)\n\treturn s.get_data("
  },
  {
    "path": "helium/_impl/util/inspect_.py",
    "chars": 1210,
    "preview": "from helium._impl.util.lang import isbound\nimport inspect\n\ndef repr_args(f, args=None, kwargs=None, repr_fn=repr):\n\tif a"
  },
  {
    "path": "helium/_impl/util/lang.py",
    "chars": 570,
    "preview": "class TemporaryAttrValue:\n\tdef __init__(self, obj, attr, value):\n\t\tself.obj = obj\n\t\tself.attr = attr\n\t\tself.value = valu"
  },
  {
    "path": "helium/_impl/util/path.py",
    "chars": 584,
    "preview": "from errno import EEXIST\nfrom os.path import split, isdir\nfrom os import makedirs\n\ndef get_components(path):\n\tfolders = "
  },
  {
    "path": "helium/_impl/util/system.py",
    "chars": 374,
    "preview": "\"\"\"\nGives information about the current operating system.\n\"\"\"\nimport sys\n\ndef is_windows():\n\treturn sys.platform in ('wi"
  },
  {
    "path": "helium/_impl/util/xpath.py",
    "chars": 436,
    "preview": "# -*- coding: utf-8 -*-\ndef lower(text):\n\talphabet = 'ABCDEFGHIJKLMNOPQRSTUVWXYZÀÁÂÃÄÅÆÇÈÉÊËÌÍÎÏÐÑÒÓÔÕÖרÙÚÛÜÝ'\n\treturn "
  },
  {
    "path": "requirements/base.txt",
    "chars": 64,
    "preview": "# Also update setup.py when you edit this file.\nselenium>=4.29.0"
  },
  {
    "path": "requirements/docs.txt",
    "chars": 50,
    "preview": "-r base.txt\n\nsphinx-rtd-theme==3.0.2\nsphinx==8.2.3"
  },
  {
    "path": "requirements/test.txt",
    "chars": 69,
    "preview": "-r base.txt\n\nsetuptools<60\npsutil\npywin32; platform_system=='Windows'"
  },
  {
    "path": "setup.py",
    "chars": 1396,
    "preview": "from setuptools import setup, find_packages\n\nsetup(\n\tname = 'helium',\n\t# Also update docs/conf.py when you change this:\n"
  },
  {
    "path": "tests/__init__.py",
    "chars": 0,
    "preview": ""
  },
  {
    "path": "tests/api/__init__.py",
    "chars": 1957,
    "preview": "from helium import start_chrome, start_firefox, go_to, set_driver, \\\n\tkill_browser\nfrom selenium.webdriver import Chrome"
  },
  {
    "path": "tests/api/data/default.css",
    "chars": 28,
    "preview": "#result {\n    clear: both;\n}"
  },
  {
    "path": "tests/api/data/js/jquery.ui-contextmenu.js",
    "chars": 10637,
    "preview": "/*******************************************************************************\n * jquery.ui-contextmenu.js plugin.\n *\n"
  },
  {
    "path": "tests/api/data/js/util.js",
    "chars": 88,
    "preview": "function setResult(result) {\n    document.getElementById('result').innerHTML = result;\n}"
  },
  {
    "path": "tests/api/data/test_alert.html",
    "chars": 747,
    "preview": "<!DOCTYPE html>\n<html>\n<head>\n\t<title>test_alert</title>\n\t<script type=\"text/javascript\" src=\"js/util.js\"></script>\n\t<sc"
  },
  {
    "path": "tests/api/data/test_aria.html",
    "chars": 934,
    "preview": "<!DOCTYPE html>\n<html>\n<head>\n        <meta http-equiv=\"Content-Type\" content=\"text/html; charset=utf-8\">\n\t\t<title>Test "
  },
  {
    "path": "tests/api/data/test_click.html",
    "chars": 718,
    "preview": "<!DOCTYPE html>\n<html>\n<head>\n\t<title>test_click</title>\n\t<script type=\"text/javascript\" src=\"js/util.js\"></script>\n\t<st"
  },
  {
    "path": "tests/api/data/test_doubleclick.html",
    "chars": 235,
    "preview": "<!DOCTYPE html>\n<html>\n<head>\n\t<title>test_doubleclick</title>\n\t<script type=\"text/javascript\" src=\"js/util.js\"></script"
  },
  {
    "path": "tests/api/data/test_drag/default.html",
    "chars": 779,
    "preview": "<!DOCTYPE html>\n<html>\n<head>\n\t<script type=\"text/javascript\" src=\"../js/util.js\"></script>\n\t<script type=\"text/javascri"
  },
  {
    "path": "tests/api/data/test_drag/html5.html",
    "chars": 932,
    "preview": "<!DOCTYPE html>\n<html>\n<head>\n\t<script type=\"text/javascript\" src=\"../js/util.js\"></script>\n\t<script type=\"text/javascri"
  },
  {
    "path": "tests/api/data/test_drag/test_drag.css",
    "chars": 201,
    "preview": ".dropTarget {\n    float: left;\n    width: 100px;\n    height: 35px;\n    margin: 10px;\n    padding: 10px;\n    border: 1px "
  },
  {
    "path": "tests/api/data/test_file_upload/test_file_upload.html",
    "chars": 2756,
    "preview": "<!DOCTYPE html>\n<html>\n<head>\n\t<title>test_file_upload</title>\n\t<script type=\"text/javascript\" src=\"../js/util.js\"></scr"
  },
  {
    "path": "tests/api/data/test_gui_elements.html",
    "chars": 12683,
    "preview": "<html>\n\t<head>\n        <meta http-equiv=\"Content-Type\" content=\"text/html; charset=utf-8\">\n\t\t<title>Test page for browse"
  },
  {
    "path": "tests/api/data/test_gui_elements_iframe.html",
    "chars": 272,
    "preview": "<html>\n\t<head>\n        <meta http-equiv=\"Content-Type\" content=\"text/html; charset=utf-8\">\n        <style>\n            b"
  },
  {
    "path": "tests/api/data/test_hover.html",
    "chars": 1709,
    "preview": "<!DOCTYPE html>\n<html>\n<head>\n\t<title>test_hover</title>\n\t<style type=\"text/css\">\n\t\t#nav, #nav ul {\n\t\t\tpadding: 0;\n\t\t\tma"
  },
  {
    "path": "tests/api/data/test_iframe/iframe.html",
    "chars": 174,
    "preview": "<!DOCTYPE html>\n<html>\n<head>\n    <title>test_iframe - iframe</title>\n</head>\n<body>\nThis text is inside an iframe.\n<ifr"
  },
  {
    "path": "tests/api/data/test_iframe/main.html",
    "chars": 134,
    "preview": "<!DOCTYPE html>\n<html>\n<head>\n    <title>test_iframe - main</title>\n</head>\n<body>\n<iframe src=\"iframe.html\"></iframe>\n<"
  },
  {
    "path": "tests/api/data/test_iframe/nested_iframe.html",
    "chars": 144,
    "preview": "<!DOCTYPE html>\n<html>\n<head>\n    <title>test_iframe - nested iframe</title>\n</head>\n<body>\nThis text is inside a nested"
  },
  {
    "path": "tests/api/data/test_implicit_wait.html",
    "chars": 731,
    "preview": "<!DOCTYPE html>\n<html>\n<head>\n\t<title>test_auto_wait</title>\n\t<script type=\"text/javascript\">\n\t\tfunction createSecondBut"
  },
  {
    "path": "tests/api/data/test_leaked_password.html",
    "chars": 894,
    "preview": "<!DOCTYPE html>\n<html>\n<head>\n\t<title>Leaked Password Test</title>\n</head>\n<body>\n\t<form method=\"GET\">\n\t\t<table>\n\t\t\t<tr>"
  },
  {
    "path": "tests/api/data/test_point.html",
    "chars": 1952,
    "preview": "<!DOCTYPE html>\n<html>\n<head>\n\t<title>test_point</title>\n\t<script type=\"text/javascript\" src=\"js/util.js\"></script>\n\t<sc"
  },
  {
    "path": "tests/api/data/test_rightclick.html",
    "chars": 1627,
    "preview": "<!DOCTYPE html>\n<html>\n<head>\n\t<title>test_rightclick</title>\n\t<link type=\"text/css\" rel=\"stylesheet\"\n\t\t  href=\"http://c"
  },
  {
    "path": "tests/api/data/test_scroll.html",
    "chars": 223,
    "preview": "<!DOCTYPE html>\n<html>\n<head>\n\t<title></title>\n</head>\n<body>\n\t<div style=\"width: 5000px; height: 5000px; margin: 10px;\n"
  },
  {
    "path": "tests/api/data/test_start_go_to.html",
    "chars": 93,
    "preview": "<!DOCTYPE html>\n<html>\n<head>\n\t<title>test_start_go_to</title>\n</head>\n<body>\n</body>\n</html>"
  },
  {
    "path": "tests/api/data/test_tables.html",
    "chars": 3591,
    "preview": "<html>\n    <body>\n        <div>\n            <h2>Table no. 1</h2>\n            <table>\n                <thead>\n           "
  },
  {
    "path": "tests/api/data/test_text_impl.html",
    "chars": 245,
    "preview": "<!DOCTYPE html>\n<html>\n<body>\n<p>A paragraph</p>\n<div>\n\t<p>A paragraph inside a div</p>\n\t<p>Another paragraph inside the"
  },
  {
    "path": "tests/api/data/test_wait_until.html",
    "chars": 452,
    "preview": "<!DOCTYPE html>\n<html>\n<head>\n\t<title>test_wait_until</title>\n\t<script type=\"text/javascript\">\n\t\tfunction createResultPa"
  },
  {
    "path": "tests/api/data/test_window/popup.html",
    "chars": 96,
    "preview": "<!DOCTYPE html>\n<html>\n<head>\n\t<title>test_window - popup</title>\n</head>\n<body>\n</body>\n</html>"
  },
  {
    "path": "tests/api/data/test_window/test_window.html",
    "chars": 158,
    "preview": "<!DOCTYPE html>\n<html>\n<head>\n\t<title>test_window</title>\n</head>\n<body>\n\t<a href=\"popup.html\" target=\"_blank\">Click her"
  },
  {
    "path": "tests/api/data/test_window_handling/main.html",
    "chars": 516,
    "preview": "<!DOCTYPE html>\n<html>\n<head>\n\t<title>test_window_handling - Main</title>\n\t<script type=\"text/javascript\" src=\"../js/jqu"
  },
  {
    "path": "tests/api/data/test_window_handling/main_immediate_popup.html",
    "chars": 303,
    "preview": "<!DOCTYPE html>\n<html>\n<head>\n\t<title>test_window_handling - Main (immediate popup)</title>\n\t<script type=\"text/javascri"
  },
  {
    "path": "tests/api/data/test_window_handling/popup.html",
    "chars": 477,
    "preview": "<!DOCTYPE html>\n<html>\n<head>\n\t<title>test_window_handling - Popup</title>\n\t<script type=\"text/javascript\" src=\"../js/jq"
  },
  {
    "path": "tests/api/data/test_write.html",
    "chars": 817,
    "preview": "<!DOCTYPE html>\n<html>\n<head>\n\t<title>test_write</title>\n\t<script type=\"text/javascript\" src=\"js/jquery.min.js\"></script"
  },
  {
    "path": "tests/api/test_alert.py",
    "chars": 6013,
    "preview": "from helium import click, Alert, press, ENTER, write, TextField, Config, \\\n\twait_until\nfrom helium._impl.util.lang impor"
  },
  {
    "path": "tests/api/test_aria.py",
    "chars": 1147,
    "preview": "from helium import Button, TextField\nfrom tests.api import BrowserAT\n\nclass AriaTest(BrowserAT):\n\tdef get_page(self):\n\t\t"
  },
  {
    "path": "tests/api/test_chrome_options.py",
    "chars": 1341,
    "preview": "from helium import start_chrome, kill_browser\nfrom os.path import join\nfrom tests.api import test_browser_name\nfrom unit"
  },
  {
    "path": "tests/api/test_click.py",
    "chars": 473,
    "preview": "from helium import click, Config\nfrom helium._impl.util.lang import TemporaryAttrValue\nfrom tests.api import BrowserAT\n\n"
  },
  {
    "path": "tests/api/test_doubleclick.py",
    "chars": 280,
    "preview": "from helium import doubleclick\nfrom tests.api import BrowserAT\n\nclass DoubleclickTest(BrowserAT):\n\tdef get_page(self):\n\t"
  },
  {
    "path": "tests/api/test_drag.py",
    "chars": 1021,
    "preview": "from helium import *\nfrom selenium.webdriver.common.by import By\nfrom tests.api import BrowserAT\n\nclass DragTest(Browser"
  },
  {
    "path": "tests/api/test_file_upload.py",
    "chars": 1152,
    "preview": "from helium import attach_file, drag_file, TextField, Text\nfrom tests.api import BrowserAT\nfrom tests.api.util import ge"
  },
  {
    "path": "tests/api/test_find_all.py",
    "chars": 2627,
    "preview": "from selenium.common.exceptions import StaleElementReferenceException\nfrom helium import find_all, Button, TextField, wr"
  },
  {
    "path": "tests/api/test_gui_elements.py",
    "chars": 12362,
    "preview": "# -*- coding: utf-8 -*-\nfrom helium import Button, TextField, ComboBox, CheckBox, click, \\\n\tRadioButton, write, Text, fi"
  },
  {
    "path": "tests/api/test_highlight.py",
    "chars": 846,
    "preview": "from helium import highlight, Button, Text, Config\nfrom helium._impl.util.lang import TemporaryAttrValue\nfrom tests.api "
  },
  {
    "path": "tests/api/test_hover.py",
    "chars": 1576,
    "preview": "from helium import hover, Config\nfrom helium._impl.util.lang import TemporaryAttrValue\nfrom helium._impl.util.system imp"
  },
  {
    "path": "tests/api/test_iframe.py",
    "chars": 1216,
    "preview": "from helium import Text, get_driver, find_all\nfrom tests.api import BrowserAT\n\nclass IframeTest(BrowserAT):\n\tdef get_pag"
  },
  {
    "path": "tests/api/test_implicit_wait.py",
    "chars": 857,
    "preview": "from helium import click, Config\nfrom helium._impl.util.lang import TemporaryAttrValue\nfrom tests.api import BrowserAT\nf"
  },
  {
    "path": "tests/api/test_kill_service_at_exit.py",
    "chars": 1565,
    "preview": "from psutil import NoSuchProcess\n\nimport psutil\n\nclass KillServiceAtExitAT:\n\tdef test_kill_service_at_exit(self):\n\t\tself"
  },
  {
    "path": "tests/api/test_kill_service_at_exit_chrome.py",
    "chars": 884,
    "preview": "from helium import start_chrome\nfrom helium._impl.util.system import is_windows\nfrom tests.api import test_browser_name\n"
  },
  {
    "path": "tests/api/test_leaked_password.py",
    "chars": 647,
    "preview": "from helium import write, click, Text, wait_until\nfrom tests.api import BrowserAT\n\nclass LeakedPasswordTest(BrowserAT):\n"
  },
  {
    "path": "tests/api/test_no_driver.py",
    "chars": 3577,
    "preview": "from helium import *\nfrom helium._impl import APIImpl\nfrom unittest import TestCase\n\nclass NoDriverTest(TestCase):\n\tdef "
  },
  {
    "path": "tests/api/test_point.py",
    "chars": 4741,
    "preview": "from helium import click, Point, Button, hover, rightclick, doubleclick, drag\nfrom tests.api import BrowserAT, test_brow"
  },
  {
    "path": "tests/api/test_press.py",
    "chars": 532,
    "preview": "from helium import press, TextField, SHIFT\nfrom tests.api import BrowserAT\n\nclass PressTest(BrowserAT):\n\tdef get_page(se"
  },
  {
    "path": "tests/api/test_repr.py",
    "chars": 4654,
    "preview": "from helium import *\nfrom helium import HTMLElement\nfrom tests.api import BrowserAT\n\nimport re\n\nclass UnboundReprTest(Br"
  },
  {
    "path": "tests/api/test_rightclick.py",
    "chars": 535,
    "preview": "from helium import click, rightclick\nfrom tests.api import BrowserAT\n\nclass RightclickTest(BrowserAT):\n\tdef get_page(sel"
  },
  {
    "path": "tests/api/test_s.py",
    "chars": 645,
    "preview": "from helium import S\nfrom tests.api import BrowserAT\n\nclass STest(BrowserAT):\n\tdef get_page(self):\n\t\treturn 'test_gui_el"
  },
  {
    "path": "tests/api/test_scroll.py",
    "chars": 1711,
    "preview": "from helium import scroll_down, scroll_left, scroll_right, scroll_up\nfrom tests.api import BrowserAT\n\nclass ScrollTest(B"
  },
  {
    "path": "tests/api/test_start_go_to.py",
    "chars": 833,
    "preview": "from helium import go_to\nfrom tests.api import start_browser\nfrom tests.api.util import get_data_file_url\nfrom os import"
  },
  {
    "path": "tests/api/test_tables.py",
    "chars": 987,
    "preview": "from helium import *\nfrom tests.api import BrowserAT\n\nclass TablesTest(BrowserAT):\n\tdef get_page(self):\n\t\treturn 'test_t"
  },
  {
    "path": "tests/api/test_text_impl.py",
    "chars": 623,
    "preview": "from helium._impl import TextImpl\nfrom helium._impl.selenium_wrappers import WebDriverWrapper\nfrom selenium.webdriver.co"
  },
  {
    "path": "tests/api/test_wait_until.py",
    "chars": 1087,
    "preview": "from helium import click, wait_until, Text\nfrom tests.api import BrowserAT\nfrom selenium.common.exceptions import Timeou"
  },
  {
    "path": "tests/api/test_window.py",
    "chars": 1457,
    "preview": "from helium import Window, click, go_to, get_driver, wait_until\nfrom tests.api.util import get_data_file_url\nfrom tests."
  },
  {
    "path": "tests/api/test_window_handling.py",
    "chars": 2463,
    "preview": "from helium import write, click, switch_to, TextField, Text, get_driver, \\\n\tLink, wait_until\nfrom selenium.webdriver.com"
  },
  {
    "path": "tests/api/test_write.py",
    "chars": 995,
    "preview": "from helium import write, TextField\nfrom tests.api import BrowserAT\n\nclass WriteTest(BrowserAT):\n\tdef get_page(self):\n\t\t"
  },
  {
    "path": "tests/api/util.py",
    "chars": 1401,
    "preview": "from os.path import dirname, join\nfrom pathlib import Path\nfrom subprocess import Popen, PIPE, STDOUT\n\nimport os\nimport "
  },
  {
    "path": "tests/unit/__init__.py",
    "chars": 0,
    "preview": ""
  },
  {
    "path": "tests/unit/test__impl/__init__.py",
    "chars": 0,
    "preview": ""
  },
  {
    "path": "tests/unit/test__impl/test_selenium_wrappers.py",
    "chars": 2673,
    "preview": "from helium._impl.selenium_wrappers import FrameIterator, \\\n\tFramesChangedWhileIterating\nfrom selenium.common.exceptions"
  },
  {
    "path": "tests/unit/test__impl/test_util/__init__.py",
    "chars": 0,
    "preview": ""
  },
  {
    "path": "tests/unit/test__impl/test_util/test_dictionary.py",
    "chars": 399,
    "preview": "from helium._impl.util.dictionary import inverse\nfrom unittest import TestCase\n\nclass InverseTest(TestCase):\n\tdef test_i"
  },
  {
    "path": "tests/unit/test__impl/test_util/test_html.py",
    "chars": 1654,
    "preview": "from unittest import TestCase\nfrom helium._impl.util.html import normalize_whitespace, \\\n\tget_easily_readable_snippet\n\nc"
  },
  {
    "path": "tests/unit/test__impl/test_util/test_xpath.py",
    "chars": 523,
    "preview": "from helium._impl.util.xpath import predicate_or\nfrom unittest import TestCase\n\nclass PredicateOrTest(TestCase):\n\tdef te"
  }
]

About this extraction

This page contains the full source code of the mherrmann/helium GitHub repository, extracted and formatted as plain text for AI agents and large language models (LLMs). The extraction includes 104 files (258.7 KB), approximately 69.2k tokens, and a symbol index with 916 extracted functions, classes, methods, constants, and types. Use this with OpenClaw, Claude, ChatGPT, Cursor, Windsurf, or any other AI tool that accepts text input. You can copy the full output to your clipboard or download it as a .txt file.

Extracted by GitExtract — free GitHub repo to text converter for AI. Built by Nikandr Surkov.

Copied to clipboard!