seleniumbase / SeleniumBase

📊 Python's all-in-one framework for web crawling, scraping, testing, and reporting. Supports pytest. UC Mode provides stealth. Includes many tools.
https://seleniumbase.io
MIT License
5.15k stars 957 forks source link

ImportError: cannot import name 'Driver' from partially initialized module 'seleniumbase' #2551

Closed bitsByB closed 7 months ago

bitsByB commented 7 months ago

Hi there, just discovered seleniumbase. trying out the first example of the last video:

from seleniumbase import Driver
driver = Driver (uc=True)
try:
    driver.get("https://nowsecure.nl/#relax")
    driver.sleep(4)
    # DO MORE STUFF
finally:
    driver.quit()

getting the error:

from seleniumbase import Driver
ImportError: cannot import name 'Driver' from partially initialized module 'seleniumbase' (most likely due to a circular import) (/home/bangbuild/Documents/dev/cucumber/seleniumbase.py)

what am I missing?! thanks

mdmintz commented 7 months ago

It looks like you overwrote seleniumbase by naming your file seleniumbase.py: /home/bangbuild/Documents/dev/cucumber/seleniumbase.py

Don't name your Python files with the same name as your import libraries!

Also, here's a newer example that uses uc_open_with_reconnect (which lets you customize the reconnect time, and starts with a newer tab for better anti-detection):

from seleniumbase import Driver

driver = Driver(uc=True)
try:
    driver.uc_open_with_reconnect("https://top.gg/", 6)
finally:
    driver.quit()
bitsByB commented 7 months ago

aha! great, thanks a lot @mdmintz very cool! Everything works but I don't see chrome opening right? Michael while I have your attention, what's your favorite resource to point to to learn about this technology ? (besides seleniumbase docs of course ;) cheers

mdmintz commented 7 months ago

"Everything works" ... Great! So what do you mean by "I don't see chrome opening"?

For SeleniumBase UC Mode, here are the docs: help_docs/uc_mode.md

There's also the video: https://www.youtube.com/watch?v=5dMFI3e85ig

A newer video is coming soon.

bitsByB commented 7 months ago

@mdmintz found the --gui option (i'm on linux). Yes I'm watching the video right now, it's how I discovered seleniumbase. I'm digging into the doc as well and your other videos. It's really cool what you have done here! thank you

bitsByB commented 7 months ago

@mdmintz, I see in your doc you talk about behave and not about cucumber... why this choice? what's your take on that?

mdmintz commented 7 months ago

behave is a Python implementation of the Gherkin syntax for BDD. Cucumber is a generic implementation of the Gherkin syntax for BDD.

SeleniumBase utilizes the behave version by adding specific code around browser automation:

SeleniumBase/examples/behave_bdd

You can also generate behave scripts from existing tests: pytest --rec-behave

Or use the Recorder to generate behave scripts: sbase recorder --behave

bitsByB commented 7 months ago

Thanks for the explanation! man I really appreciate you taking the time :) Watching the selenium conf, pretty cool Michael

mdmintz commented 7 months ago

Thank you! GitHub Stars are always appreciated. ⭐

bitsByB commented 7 months ago

aha! of course, done!

bitsByB commented 7 months ago

One last question and I stop bothering you for today! How can I combine the updated code you gave me at the beginning which avoid to be blocked with this other piece I'm working on:

from seleniumbase import Driver

driver = Driver(uc=True)
try:
    driver.uc_open_with_reconnect("https://engage.metmuseum.org/account/login/", 6)
    print(driver.title)
finally:
    driver.quit()

when I run this by itself I'm being blocked on the second page...

from seleniumbase import BaseCase
BaseCase.main(__name__, __file__)

class MyTestClass(BaseCase):
    def test_met_signin(self):
        self.open("https://www.metmuseum.org/")
        self.click(".masthead__membership")
        self.assert_exact_text("Join/Renew", "p.button")
        self.click("button#Member_with_Early_Views_0")

thanks!

mdmintz commented 7 months ago

Like this:

from seleniumbase import BaseCase
BaseCase.main(__name__, __file__, "--uc", "-s")

class MyTestClass(BaseCase):
    def test_met_signin(self):
        if not self.undetectable:
            self.get_new_driver(undetectable=True)
        self.driver.uc_open_with_reconnect("https://www.metmuseum.org/", 2)
        self.click("a.masthead__membership")
        self.assert_exact_text("Join/Renew", "button#Member_with_Early_Views_0")
        self.click("button#Member_with_Early_Views_0")

        breakpoint()

If you run it with python, it will automatically use pytest --uc. If you run it with pytest without the --uc, then the line with get_new_driver will correct it.

Also, I didn't encounter any CAPTCHAs on that site. Maybe they happen later, but if they don't happen at all, then UC Mode wouldn't be needed.

bitsByB commented 7 months ago

Amazing!! thanks so much Michael! have a great one :) I was getting this message: image

bitsByB commented 7 months ago

This thing is so intuitive! Everything works perfectly for a proof of concept!

"""A complete end-to-end test for a membership recurring customer."""
from seleniumbase import BaseCase
BaseCase.main(__name__, __file__, "--uc", "-s")

class MyTestClass(BaseCase):
    def test_recurring_custommer_wrong_login(self):
        if not self.undetectable:
            self.get_new_driver(undetectable=True)
        self.driver.uc_open_with_reconnect("https://www.metmuseum.org/", 2)
        self.click("a.masthead__membership")
        self.assert_exact_text("Join/Renew", "button#Member_with_Early_Views_0")
        self.click("button#Member_with_Early_Views_0")
        self.assert_exact_text("Log in", "button.btn-primary")
        self.type("#LoginRequest_Username", "john@doe.com")
        self.type("#LoginRequest_Password", "fromAMNHwithL@ve")
        self.click("button.btn-primary")

        breakpoint()

How could I link that with Behave and produce a Gherkin .feature file? It's the way around usually right?

mdmintz commented 7 months ago

Have you tried running any of the examples in the examples/behave_bdd/features folder, such as examples/behave_bdd/features/realworld.feature yet? If you haven't, clone SeleniumBase from GitHub and try some of those examples:

> behave realworld.feature

Feature: SeleniumBase scenarios for the RealWorld App # realworld.feature:1

  Scenario: Verify RealWorld App (log in / sign out)  # realworld.feature:3
    Given Open "seleniumbase.io/realworld/login"      # ../../../sbase/steps.py:10
    And Clear Session Storage                         # ../../../sbase/steps.py:669
    When Type "demo_user" into "#username"            # ../../../sbase/steps.py:40
    And Type "secret_pass" into "#password"           # ../../../sbase/steps.py:40
    And Do MFA "GAXG2MTEOR3DMMDG" into "#totpcode"    # ../../../sbase/steps.py:322
    Then Assert exact text "Welcome!" in "h1"         # ../../../sbase/steps.py:157
    And Highlight "img#image1"                        # ../../../sbase/steps.py:184
    And Click 'a:contains("This Page")'               # ../../../sbase/steps.py:27
    And Save screenshot to logs                       # ../../../sbase/steps.py:239
    When Click link "Sign out"                        # ../../../sbase/steps.py:195
    Then Assert element 'a:contains("Sign in")'       # ../../../sbase/steps.py:120
    And Assert text "You have been signed out!"       # ../../../sbase/steps.py:145
   ✅ Scenario Passed!

It can't handle UC Mode yet though, but it can handle any other SeleniumBase actions.

bitsByB commented 7 months ago

I'm on it. If I'm able to pull that off and get the job I owe you big time Michael!

bitsByB commented 7 months ago

How could I target:

image

image

from seleniumbase import BaseCase
BaseCase.main(__name__, __file__, "--uc", "-s")

class MyTestClass(BaseCase):
    def test_met_signin(self):
        if not self.undetectable:
            self.get_new_driver(undetectable=True)
        self.driver.uc_open_with_reconnect("https://www.metmuseum.org/", 2)
        self.click("a.masthead__membership")
        self.assert_exact_text("Join/Renew", "button#Member_with_Early_Views_0")
        self.click("button#Member_with_Early_Views_0")
        self.assert_exact_text("Email", "div.form-item")
        self.type("#LoginRequest_Username", "john@doe.com")
        # self.assert_exact_text("Password", "div.form-item")
        self.type("#LoginRequest_Password", "fromAMNHwithL@ve")
        self.click("button.btn-primary")
        self.assert_exact_text("We couldn't find an account with this email address and password combination. Please check your information and try again. If you forgot your password click on ", "div.error-summary validation-summary-errors")
        # self.assert_true(self.is_element_visible(".text-utility"))

        breakpoint()

I managed to do it in the previous line of code but I find no "grip" on this one... Also I couldn't find in the doc how to add a sleep of 5 seconds at the end before to close the browser. Thanks

mdmintz commented 7 months ago

There's a lot of text under <div class="error-summary validation-summary-errors">, so instead of trying to assert all of it with assert_exact_text(), you could do assert_text() instead for a substring match:

self.assert_text("Please check your information and try again.", "div.validation-summary-errors")

You mentioned something about having "the warning highlighted"? Do you mean --demo mode? That inserts JavaScript into the page, which can make you detected. You can also do self.activate_demo_mode() to activate Demo Mode in the middle of a test.

For the 5-second sleep at the end, just add: self.sleep(5)

bitsByB commented 7 months ago

That's PERFEEECT! So cool, thanks a lot! Still struggling with the behave files but will share with you when I'm stuck :)

bitsByB commented 7 months ago

Morning Michael, I can tick that box by either clicking inside the square or the Non-Residents text But it has this weird state of ::before and ::after (I have noticed it's always complicated when those guys are in the mix and I didn't find anything about it in the docs) I'm trying a whole bunch of ways of clicking it but nothing works. (self.click("#check_other") doesn't work for example) Could you suggest something? Thank you

image

image

mdmintz commented 7 months ago

If that element is in an iframe, you'll need to switch into the iframe first. When you say it doesn't work, I need more details, such as what the error message says.

bitsByB commented 7 months ago

I'm not yet familiar with iframe but I don't see any iframe tags in the devtool. https://tickets.amnh.org/select

python3 demo-amnh.py --demo --gui
=============================== test session starts ================================
platform linux -- Python 3.10.12, pytest-8.0.2, pluggy-1.4.0
rootdir: /home/bangbuild/Documents/dev/Python/seleniumbase
plugins: xdist-3.5.0, rerunfailures-13.0, ordering-0.6, metadata-3.1.1, html-2.0.1, seleniumbase-4.24.2
collected 1 item                                                                   

demo-amnh.py F

===================================== FAILURES =====================================
______________________ MyTestClass.test_purchase_ticket_guest ______________________

self = <demo-amnh.MyTestClass testMethod=test_purchase_ticket_guest>

    def test_purchase_ticket_guest(self):
        if not self.undetectable:
            self.get_new_driver(undetectable=True)
        self.driver.uc_open_with_reconnect("https://tickets.amnh.org/select", 2)
>       self.click("div#check_other")

demo-amnh.py:11: 
_ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ 
../../../../.local/lib/python3.10/site-packages/seleniumbase/fixtures/base_case.py:409: in click
    element = page_actions.wait_for_element_visible(
../../../../.local/lib/python3.10/site-packages/seleniumbase/fixtures/page_actions.py:508: in wait_for_element_visible
    timeout_exception(NoSuchElementException, message)
_ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ 

exception = <class 'selenium.common.exceptions.NoSuchElementException'>
message = 'Element {div#check_other} was not present after 7 seconds!'

    def timeout_exception(exception, message):
        exc, msg = shared_utils.format_exc(exception, message)
>       raise exc(msg)
E       seleniumbase.common.exceptions.NoSuchElementException: Message: 
E        Element {div#check_other} was not present after 7 seconds!

../../../../.local/lib/python3.10/site-packages/seleniumbase/fixtures/page_actions.py:266: NoSuchElementException
- Latest Logs dir: /home/bangbuild/Documents/dev/Python/seleniumbase/latest_logs/ --
============================= short test summary info ==============================
FAILED demo-amnh.py::MyTestClass::test_purchase_ticket_guest - seleniumbase.common.exceptions.NoSuchElementException: Message: 
================================ 1 failed in 11.29s ================================
mdmintz commented 7 months ago

You changed the selector you used between the first time you posted and just now. Show me the error message that appears when you just use #check_other. (It's the ID of the input field, not the div)

bitsByB commented 7 months ago
python3 demo-amnh.py --demo --gui
================== test session starts ==================
platform linux -- Python 3.10.12, pytest-8.0.2, pluggy-1.4.0
rootdir: /home/bangbuild/Documents/dev/Python/seleniumbase
plugins: xdist-3.5.0, rerunfailures-13.0, ordering-0.6, metadata-3.1.1, html-2.0.1, seleniumbase-4.24.2
collected 1 item                                        

demo-amnh.py F

======================= FAILURES ========================
________ MyTestClass.test_purchase_ticket_guest _________

self = <demo-amnh.MyTestClass testMethod=test_purchase_ticket_guest>

    def test_purchase_ticket_guest(self):
        if not self.undetectable:
            self.get_new_driver(undetectable=True)
        self.driver.uc_open_with_reconnect("https://tickets.amnh.org/select", 2)
>       self.click("#check_other")

demo-amnh.py:11: 
_ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _
../../../../.local/lib/python3.10/site-packages/seleniumbase/fixtures/base_case.py:409: in click
    element = page_actions.wait_for_element_visible(
../../../../.local/lib/python3.10/site-packages/seleniumbase/fixtures/page_actions.py:515: in wait_for_element_visible
    timeout_exception(ElementNotVisibleException, message)
_ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _

exception = <class 'selenium.common.exceptions.ElementNotVisibleException'>
message = 'Element {#check_other} was not visible after 7 seconds!'

    def timeout_exception(exception, message):
        exc, msg = shared_utils.format_exc(exception, message)
>       raise exc(msg)
E       seleniumbase.common.exceptions.ElementNotVisibleException: Message: 
E        Element {#check_other} was not visible after 7 seconds!

../../../../.local/lib/python3.10/site-packages/seleniumbase/fixtures/page_actions.py:266: ElementNotVisibleException
- Latest Logs dir: /home/bangbuild/Documents/dev/Python/seleniumbase/latest_logs/ -
================ short test summary info ================
FAILED demo-amnh.py::MyTestClass::test_purchase_ticket_guest - seleniumbase.common.exceptions.ElementNotVisibleExce...
================== 1 failed in 10.50s ===================

self.click("input#check_other") doesn't work neither

mdmintz commented 7 months ago

That helps. Try self.js_click("input#check_other")

bitsByB commented 7 months ago

Let's go! that did the trick! What is the difference between this and the simple .click? Being able to access those ::before and ::after state?

mdmintz commented 7 months ago

js_click(selector) lets you click on "hidden" elements. From your error message, it said ElementNotVisibleException, which is different from NoSuchElementException.

bitsByB commented 7 months ago

I see, thank you so much Michael for your time, I really appreciate your help on this one! I just discovered the recorder! I should be able to bother you way less ;) I watched the video about it and I'm ready to use it but I'm getting this code and the browser window and SeleniumBase recorder App don't pop up...? (it hangs there until I press c + return) How can I have this working? Thanks

[33] > /home/bangbuild/Documents/dev/Python/seleniumbase/TEST_NAME.py(9)
   .
   5         def test_recording(self):
   6             if self.recorder_ext:
   7                 # When done recording actions,
   8                 # type "c", and press [Enter].
   9  ->             import pdb; pdb.set_trace()
 return None
(Pdb+) 

it gives me those errors:

>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>> PDB continue >>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>
F
====================================================================================== FAILURES ======================================================================================
____________________________________________________________________________ RecorderTest.test_recording _____________________________________________________________________________

self = <urllib3.connection.HTTPConnection object at 0x7f6046fe4790>

    def _new_conn(self) -> socket.socket:
        """Establish a socket connection and set nodelay settings on it.

        :return: New socket connection.
        """
        try:
>           sock = connection.create_connection(
                (self._dns_host, self.port),
                self.timeout,
                source_address=self.source_address,
                socket_options=self.socket_options,
            )

../../../../.local/lib/python3.10/site-packages/urllib3/connection.py:198: 
_ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ 
../../../../.local/lib/python3.10/site-packages/urllib3/util/connection.py:85: in create_connection
    raise err
_ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ 

address = ('localhost', 50825), timeout = None, source_address = None, socket_options = [(6, 1, 1)]

    def create_connection(
        address: tuple[str, int],
        timeout: _TYPE_TIMEOUT = _DEFAULT_TIMEOUT,
        source_address: tuple[str, int] | None = None,
        socket_options: _TYPE_SOCKET_OPTIONS | None = None,
    ) -> socket.socket:
        """Connect to *address* and return the socket object.

        Convenience function.  Connect to *address* (a 2-tuple ``(host,
        port)``) and return the socket object.  Passing the optional
        *timeout* parameter will set the timeout on the socket instance
        before attempting to connect.  If no *timeout* is supplied, the
        global default timeout setting returned by :func:`socket.getdefaulttimeout`
        is used.  If *source_address* is set it must be a tuple of (host, port)
        for the socket to bind as a source address before making the connection.
        An host of '' or port 0 tells the OS to use the default.
        """

        host, port = address
        if host.startswith("["):
            host = host.strip("[]")
        err = None

        # Using the value from allowed_gai_family() in the context of getaddrinfo lets
        # us select whether to work with IPv4 DNS records, IPv6 records, or both.
        # The original create_connection function always returns all records.
        family = allowed_gai_family()

        try:
            host.encode("idna")
        except UnicodeError:
            raise LocationParseError(f"'{host}', label empty or too long") from None

        for res in socket.getaddrinfo(host, port, family, socket.SOCK_STREAM):
            af, socktype, proto, canonname, sa = res
            sock = None
            try:
                sock = socket.socket(af, socktype, proto)

                # If provided, set socket level options before connecting.
                _set_socket_options(sock, socket_options)

                if timeout is not _DEFAULT_TIMEOUT:
                    sock.settimeout(timeout)
                if source_address:
                    sock.bind(source_address)
>               sock.connect(sa)
E               ConnectionRefusedError: [Errno 111] Connection refused

../../../../.local/lib/python3.10/site-packages/urllib3/util/connection.py:73: ConnectionRefusedError

The above exception was the direct cause of the following exception:

self = <urllib3.connectionpool.HTTPConnectionPool object at 0x7f6048b40490>, method = 'GET', url = '/session/fb8201d3d37ec55e388d6392adb794ed/window/handles', body = None
headers = {'Accept': 'application/json', 'Connection': 'keep-alive', 'Content-Type': 'application/json;charset=UTF-8', 'User-Agent': 'selenium/4.18.1 (python linux)'}
retries = Retry(total=0, connect=None, read=None, redirect=None, status=None), redirect = False, assert_same_host = False, timeout = <_TYPE_DEFAULT.token: -1>, pool_timeout = None
release_conn = True, chunked = False, body_pos = None, preload_content = True, decode_content = True, response_kw = {}
parsed_url = Url(scheme=None, auth=None, host=None, port=None, path='/session/fb8201d3d37ec55e388d6392adb794ed/window/handles', query=None, fragment=None), destination_scheme = None
conn = None, release_this_conn = True, http_tunnel_required = False, err = None, clean_exit = False

    def urlopen(  # type: ignore[override]
        self,
        method: str,
        url: str,
        body: _TYPE_BODY | None = None,
        headers: typing.Mapping[str, str] | None = None,
        retries: Retry | bool | int | None = None,
        redirect: bool = True,
        assert_same_host: bool = True,
        timeout: _TYPE_TIMEOUT = _DEFAULT_TIMEOUT,
        pool_timeout: int | None = None,
        release_conn: bool | None = None,
        chunked: bool = False,
        body_pos: _TYPE_BODY_POSITION | None = None,
        preload_content: bool = True,
        decode_content: bool = True,
        **response_kw: typing.Any,
    ) -> BaseHTTPResponse:
        """
        Get a connection from the pool and perform an HTTP request. This is the
        lowest level call for making a request, so you'll need to specify all
        the raw details.

        .. note::

           More commonly, it's appropriate to use a convenience method
           such as :meth:`request`.

        .. note::

           `release_conn` will only behave as expected if
           `preload_content=False` because we want to make
           `preload_content=False` the default behaviour someday soon without
           breaking backwards compatibility.

        :param method:
            HTTP request method (such as GET, POST, PUT, etc.)

        :param url:
            The URL to perform the request on.

        :param body:
            Data to send in the request body, either :class:`str`, :class:`bytes`,
            an iterable of :class:`str`/:class:`bytes`, or a file-like object.

        :param headers:
            Dictionary of custom headers to send, such as User-Agent,
            If-None-Match, etc. If None, pool headers are used. If provided,
            these headers completely replace any pool-specific headers.

        :param retries:
            Configure the number of retries to allow before raising a
            :class:`~urllib3.exceptions.MaxRetryError` exception.

            If ``None`` (default) will retry 3 times, see ``Retry.DEFAULT``. Pass a
            :class:`~urllib3.util.retry.Retry` object for fine-grained control
            over different types of retries.
            Pass an integer number to retry connection errors that many times,
            but no other types of errors. Pass zero to never retry.

            If ``False``, then retries are disabled and any exception is raised
            immediately. Also, instead of raising a MaxRetryError on redirects,
            the redirect response will be returned.

        :type retries: :class:`~urllib3.util.retry.Retry`, False, or an int.

        :param redirect:
            If True, automatically handle redirects (status codes 301, 302,
            303, 307, 308). Each redirect counts as a retry. Disabling retries
            will disable redirect, too.

        :param assert_same_host:
            If ``True``, will make sure that the host of the pool requests is
            consistent else will raise HostChangedError. When ``False``, you can
            use the pool on an HTTP proxy and request foreign hosts.

        :param timeout:
            If specified, overrides the default timeout for this one
            request. It may be a float (in seconds) or an instance of
            :class:`urllib3.util.Timeout`.

        :param pool_timeout:
            If set and the pool is set to block=True, then this method will
            block for ``pool_timeout`` seconds and raise EmptyPoolError if no
            connection is available within the time period.

        :param bool preload_content:
            If True, the response's body will be preloaded into memory.

        :param bool decode_content:
            If True, will attempt to decode the body based on the
            'content-encoding' header.

        :param release_conn:
            If False, then the urlopen call will not release the connection
            back into the pool once a response is received (but will release if
            you read the entire contents of the response such as when
            `preload_content=True`). This is useful if you're not preloading
            the response's content immediately. You will need to call
            ``r.release_conn()`` on the response ``r`` to return the connection
            back into the pool. If None, it takes the value of ``preload_content``
            which defaults to ``True``.

        :param bool chunked:
            If True, urllib3 will send the body using chunked transfer
            encoding. Otherwise, urllib3 will send the body using the standard
            content-length form. Defaults to False.

        :param int body_pos:
            Position to seek to in file-like body in the event of a retry or
            redirect. Typically this won't need to be set because urllib3 will
            auto-populate the value when needed.
        """
        parsed_url = parse_url(url)
        destination_scheme = parsed_url.scheme

        if headers is None:
            headers = self.headers

        if not isinstance(retries, Retry):
            retries = Retry.from_int(retries, redirect=redirect, default=self.retries)

        if release_conn is None:
            release_conn = preload_content

        # Check host
        if assert_same_host and not self.is_same_host(url):
            raise HostChangedError(self, url, retries)

        # Ensure that the URL we're connecting to is properly encoded
        if url.startswith("/"):
            url = to_str(_encode_target(url))
        else:
            url = to_str(parsed_url.url)

        conn = None

        # Track whether `conn` needs to be released before
        # returning/raising/recursing. Update this variable if necessary, and
        # leave `release_conn` constant throughout the function. That way, if
        # the function recurses, the original value of `release_conn` will be
        # passed down into the recursive call, and its value will be respected.
        #
        # See issue #651 [1] for details.
        #
        # [1] <https://github.com/urllib3/urllib3/issues/651>
        release_this_conn = release_conn

        http_tunnel_required = connection_requires_http_tunnel(
            self.proxy, self.proxy_config, destination_scheme
        )

        # Merge the proxy headers. Only done when not using HTTP CONNECT. We
        # have to copy the headers dict so we can safely change it without those
        # changes being reflected in anyone else's copy.
        if not http_tunnel_required:
            headers = headers.copy()  # type: ignore[attr-defined]
            headers.update(self.proxy_headers)  # type: ignore[union-attr]

        # Must keep the exception bound to a separate variable or else Python 3
        # complains about UnboundLocalError.
        err = None

        # Keep track of whether we cleanly exited the except block. This
        # ensures we do proper cleanup in finally.
        clean_exit = False

        # Rewind body position, if needed. Record current position
        # for future rewinds in the event of a redirect/retry.
        body_pos = set_file_position(body, body_pos)

        try:
            # Request a connection from the queue.
            timeout_obj = self._get_timeout(timeout)
            conn = self._get_conn(timeout=pool_timeout)

            conn.timeout = timeout_obj.connect_timeout  # type: ignore[assignment]

            # Is this a closed/new connection that requires CONNECT tunnelling?
            if self.proxy is not None and http_tunnel_required and conn.is_closed:
                try:
                    self._prepare_proxy(conn)
                except (BaseSSLError, OSError, SocketTimeout) as e:
                    self._raise_timeout(
                        err=e, url=self.proxy.url, timeout_value=conn.timeout
                    )
                    raise

            # If we're going to release the connection in ``finally:``, then
            # the response doesn't need to know about the connection. Otherwise
            # it will also try to release it and we'll have a double-release
            # mess.
            response_conn = conn if not release_conn else None

            # Make the request on the HTTPConnection object
>           response = self._make_request(
                conn,
                method,
                url,
                timeout=timeout_obj,
                body=body,
                headers=headers,
                chunked=chunked,
                retries=retries,
                response_conn=response_conn,
                preload_content=preload_content,
                decode_content=decode_content,
                **response_kw,
            )

../../../../.local/lib/python3.10/site-packages/urllib3/connectionpool.py:793: 
_ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ 
../../../../.local/lib/python3.10/site-packages/urllib3/connectionpool.py:496: in _make_request
    conn.request(
../../../../.local/lib/python3.10/site-packages/urllib3/connection.py:400: in request
    self.endheaders()
/usr/lib/python3.10/http/client.py:1278: in endheaders
    self._send_output(message_body, encode_chunked=encode_chunked)
/usr/lib/python3.10/http/client.py:1038: in _send_output
    self.send(msg)
/usr/lib/python3.10/http/client.py:976: in send
    self.connect()
../../../../.local/lib/python3.10/site-packages/urllib3/connection.py:238: in connect
    self.sock = self._new_conn()
_ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ 

self = <urllib3.connection.HTTPConnection object at 0x7f6046fe4790>

    def _new_conn(self) -> socket.socket:
        """Establish a socket connection and set nodelay settings on it.

        :return: New socket connection.
        """
        try:
            sock = connection.create_connection(
                (self._dns_host, self.port),
                self.timeout,
                source_address=self.source_address,
                socket_options=self.socket_options,
            )
        except socket.gaierror as e:
            raise NameResolutionError(self.host, self, e) from e
        except SocketTimeout as e:
            raise ConnectTimeoutError(
                self,
                f"Connection to {self.host} timed out. (connect timeout={self.timeout})",
            ) from e

        except OSError as e:
>           raise NewConnectionError(
                self, f"Failed to establish a new connection: {e}"
            ) from e
E           urllib3.exceptions.NewConnectionError: <urllib3.connection.HTTPConnection object at 0x7f6046fe4790>: Failed to establish a new connection: [Errno 111] Connection refused

../../../../.local/lib/python3.10/site-packages/urllib3/connection.py:213: NewConnectionError

The above exception was the direct cause of the following exception:
../../../../.local/lib/python3.10/site-packages/seleniumbase/fixtures/base_case.py:15718: in tearDown
    self.__process_recorded_actions()
../../../../.local/lib/python3.10/site-packages/seleniumbase/fixtures/base_case.py:4579: in __process_recorded_actions
    for window in self.driver.window_handles:
../../../../.local/lib/python3.10/site-packages/selenium/webdriver/remote/webdriver.py:494: in window_handles
    return self.execute(Command.W3C_GET_WINDOW_HANDLES)["value"]
../../../../.local/lib/python3.10/site-packages/selenium/webdriver/remote/webdriver.py:345: in execute
    response = self.command_executor.execute(driver_command, params)
../../../../.local/lib/python3.10/site-packages/selenium/webdriver/remote/remote_connection.py:302: in execute
    return self._request(command_info[0], url, body=data)
../../../../.local/lib/python3.10/site-packages/selenium/webdriver/remote/remote_connection.py:322: in _request
    response = self._conn.request(method, url, body=body, headers=headers)
../../../../.local/lib/python3.10/site-packages/urllib3/_request_methods.py:136: in request
    return self.request_encode_url(
../../../../.local/lib/python3.10/site-packages/urllib3/_request_methods.py:183: in request_encode_url
    return self.urlopen(method, url, **extra_kw)
../../../../.local/lib/python3.10/site-packages/urllib3/poolmanager.py:444: in urlopen
    response = conn.urlopen(method, u.request_uri, **kw)
../../../../.local/lib/python3.10/site-packages/urllib3/connectionpool.py:877: in urlopen
    return self.urlopen(
../../../../.local/lib/python3.10/site-packages/urllib3/connectionpool.py:877: in urlopen
    return self.urlopen(
../../../../.local/lib/python3.10/site-packages/urllib3/connectionpool.py:877: in urlopen
    return self.urlopen(
../../../../.local/lib/python3.10/site-packages/urllib3/connectionpool.py:847: in urlopen
    retries = retries.increment(
_ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ 

self = Retry(total=0, connect=None, read=None, redirect=None, status=None), method = 'GET', url = '/session/fb8201d3d37ec55e388d6392adb794ed/window/handles', response = None
error = NewConnectionError('<urllib3.connection.HTTPConnection object at 0x7f6046fe4790>: Failed to establish a new connection: [Errno 111] Connection refused')
_pool = <urllib3.connectionpool.HTTPConnectionPool object at 0x7f6048b40490>, _stacktrace = <traceback object at 0x7f6046e2d2c0>

    def increment(
        self,
        method: str | None = None,
        url: str | None = None,
        response: BaseHTTPResponse | None = None,
        error: Exception | None = None,
        _pool: ConnectionPool | None = None,
        _stacktrace: TracebackType | None = None,
    ) -> Retry:
        """Return a new Retry object with incremented retry counters.

        :param response: A response object, or None, if the server did not
            return a response.
        :type response: :class:`~urllib3.response.BaseHTTPResponse`
        :param Exception error: An error encountered during the request, or
            None if the response was received successfully.

        :return: A new ``Retry`` object.
        """
        if self.total is False and error:
            # Disabled, indicate to re-raise the error.
            raise reraise(type(error), error, _stacktrace)

        total = self.total
        if total is not None:
            total -= 1

        connect = self.connect
        read = self.read
        redirect = self.redirect
        status_count = self.status
        other = self.other
        cause = "unknown"
        status = None
        redirect_location = None

        if error and self._is_connection_error(error):
            # Connect retry?
            if connect is False:
                raise reraise(type(error), error, _stacktrace)
            elif connect is not None:
                connect -= 1

        elif error and self._is_read_error(error):
            # Read retry?
            if read is False or method is None or not self._is_method_retryable(method):
                raise reraise(type(error), error, _stacktrace)
            elif read is not None:
                read -= 1

        elif error:
            # Other retry?
            if other is not None:
                other -= 1

        elif response and response.get_redirect_location():
            # Redirect retry?
            if redirect is not None:
                redirect -= 1
            cause = "too many redirects"
            response_redirect_location = response.get_redirect_location()
            if response_redirect_location:
                redirect_location = response_redirect_location
            status = response.status

        else:
            # Incrementing because of a server error like a 500 in
            # status_forcelist and the given method is in the allowed_methods
            cause = ResponseError.GENERIC_ERROR
            if response and response.status:
                if status_count is not None:
                    status_count -= 1
                cause = ResponseError.SPECIFIC_ERROR.format(status_code=response.status)
                status = response.status

        history = self.history + (
            RequestHistory(method, url, error, status, redirect_location),
        )

        new_retry = self.new(
            total=total,
            connect=connect,
            read=read,
            redirect=redirect,
            status=status_count,
            other=other,
            history=history,
        )

        if new_retry.is_exhausted():
            reason = error or ResponseError(cause)
>           raise MaxRetryError(_pool, url, reason) from reason  # type: ignore[arg-type]
E           urllib3.exceptions.MaxRetryError: HTTPConnectionPool(host='localhost', port=50825): Max retries exceeded with url: /session/fb8201d3d37ec55e388d6392adb794ed/window/handles (Caused by NewConnectionError('<urllib3.connection.HTTPConnection object at 0x7f6046fe4790>: Failed to establish a new connection: [Errno 111] Connection refused'))

../../../../.local/lib/python3.10/site-packages/urllib3/util/retry.py:515: MaxRetryError
============================================================================== short test summary info ===============================================================================
FAILED TEST_NAME.py::RecorderTest::test_recording - urllib3.exceptions.MaxRetryError: HTTPConnectionPool(host='localhost', port=50825): Max retries exceeded with url: /session/fb8201d3d37ec55e388d6392adb794ed/window/handles (Caus...
1 failed in 209.12s (0:03:29)
Traceback (most recent call last):
  File "/usr/lib/python3.10/runpy.py", line 196, in _run_module_as_main
    return _run_code(code, main_globals, None,
  File "/usr/lib/python3.10/runpy.py", line 86, in _run_code
    exec(code, run_globals)
  File "/home/bangbuild/.local/lib/python3.10/site-packages/seleniumbase/__main__.py", line 21, in <module>
    main()
  File "/home/bangbuild/.local/lib/python3.10/site-packages/seleniumbase/console_scripts/run.py", line 1105, in main
    sb_print.main()
  File "/home/bangbuild/.local/lib/python3.10/site-packages/seleniumbase/console_scripts/sb_print.py", line 132, in main
    with open(
FileNotFoundError: [Errno 2] No such file or directory: '/home/bangbuild/Documents/dev/Python/seleniumbase/recordings/TEST_NAME_rec.py'
Traceback (most recent call last):
  File "/home/bangbuild/.local/bin/sbase", line 8, in <module>
    sys.exit(main())
  File "/home/bangbuild/.local/lib/python3.10/site-packages/seleniumbase/console_scripts/run.py", line 1054, in main
    sb_mkrec.main()
  File "/home/bangbuild/.local/lib/python3.10/site-packages/seleniumbase/console_scripts/sb_mkrec.py", line 231, in main
    shutil.copy(recorded_file, file_path)
  File "/usr/lib/python3.10/shutil.py", line 417, in copy
    copyfile(src, dst, follow_symlinks=follow_symlinks)
  File "/usr/lib/python3.10/shutil.py", line 254, in copyfile
    with open(src, 'rb') as fsrc:
FileNotFoundError: [Errno 2] No such file or directory: '/home/bangbuild/Documents/dev/Python/seleniumbase/recordings/TEST_NAME_rec.py'
bitsByB commented 7 months ago

Looks like there's a bunch of errors:

bitsByB commented 7 months ago

Yes the desktop recorder app definitely not showing up, getting this error:

~/Documents/dev/Python/seleniumbase$ sbase recorder
Traceback (most recent call last):
  File "/home/bangbuild/.local/bin/sbase", line 8, in <module>
    sys.exit(main())
  File "/home/bangbuild/.local/lib/python3.10/site-packages/seleniumbase/console_scripts/run.py", line 1043, in main
    from seleniumbase.console_scripts import sb_recorder
  File "/home/bangbuild/.local/lib/python3.10/site-packages/seleniumbase/console_scripts/sb_recorder.py", line 36, in <module>
    import tkinter as tk  # noqa: E402
ModuleNotFoundError: No module named 'tkinter'

I'm on seleniumbase 4.24.2

~/Documents/dev/Python/seleniumbase$ pip install tkinter
Defaulting to user installation because normal site-packages is not writeable
ERROR: Could not find a version that satisfies the requirement tkinter (from versions: none)
ERROR: No matching distribution found for tkinter
mdmintz commented 7 months ago

tkinter is a built-in Python library for both Mac and Windows. Not sure if it's on Linux. What OS did you use?

To end the recording, make sure you type c in the console and press Enter to continue from the breakpoint after you are done recording. All the browser windows need to remain open during tearDown(), so don't close them manually.

You can activate the Recorder without the GUI by calling:

sbase record SOMETEST.py --url=URL

bitsByB commented 7 months ago

Man it's amazing!!! (got it working with sudo apt install python3.10-tk) Everything is so smooth!! That's it the test path is perfect :+1: Now I need to find how to record this window session to share it... Michael I can't thank you enough for this amazing piece of software, truly magic ! 🙏 Have a great weekend my friend 😃

bitsByB commented 7 months ago

Is there an easy way to screen record the demo mode with SeleniumBase?

mdmintz commented 7 months ago

Video recording info: https://github.com/seleniumbase/SeleniumBase/discussions/2370#discussioncomment-7877316